web-font/scripts/test-leafer-incremental.mts
崮生(子虚) b385bc89bc perf(leafer-x-webfont): 脏节点集增量扫描,百万节点画布可正常运行
全量 walk O(全树) 改为事件驱动增量:
- property.change 携带 target(PropertyEvent 五参构造)→ 精确记脏节点
- child.add/remove 携带 child → 整棵子树入脏集(构造期赋值不走 setAttr)
- 订阅集与 Leafer 自家 Watcher 相同(property.change + child.*),
  渲染可靠 = 事件可靠;不再订阅 layout.end(每帧触发,百万级下
  周期性全量 walk 是持续卡顿源)
- 脏节点 >512 退化全量(模板整体替换场景全量更省)
- 全量/增量共用 collect/walkInto/commit,指纹短路保证无变化时
  update 调用归零
- 新增 provider 透传(测试/私有部署)、flushNow(跳过防抖立即提交)、
  updateCalls 计数(量化指纹短路效果)

压力测试(_prof_million.entry.ts,1M Text 节点):
- 初始全量 scan: ~580ms(一次性)
- 打字稳态(emit+flush): 0.024ms/键
- 无文字变化 flush ×100: 0.16ms,update=0
- 内存: 指纹表 O(family 数),与节点数无关

真实 leafer 验证(scripts/test-leafer-incremental.mts):
初始添加/改文字/新增子树全自动捕获,拖拽 50 次 0 请求 0 提交。

附带修复 dev 后端启动:llrt_lr.ts 改动态 import + fsReady
(createRequire 与 tsx ESM loader 模块图隔离导致 implInterface
注册到孤儿实例,mkdir undefined)。
2026-08-15 20:19:27 +08:00

89 lines
4.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 真实 leafer 环境增量路径验证
*
* 桩测试_prof_million.entry.ts验证调度逻辑本测试验证真实 leafer
* 事件驱动链路:真实 @leafer-ui/node + 真实 HTTP 子集 APIlocalhost:8087
* 1. 新增 Textchild.add 子树入脏集)→ 自动触发子集请求
* 2. 改 textproperty.change 增量)→ 只提交新字符
* 3. 不调 refresh() 的全自动路径(验证事件驱动,非手动全量)
*
* 运行npx tsx scripts/test-leafer-incremental.mts
* 前置pnpm dev:backend8087
*/
import { Leafer, Text, Group, useCanvas } from '@leafer-ui/node'
import { Canvas as NapiCanvas, GlobalFonts, loadImage } from '@napi-rs/canvas'
/** 必须在创建任何 Leafer 实例之前初始化 napi canvas 平台 */
useCanvas('napi', { Canvas: NapiCanvas, loadImage })
const { WebFontPlugin } = await import('../packages/leafer-x-webfont/src/index.ts')
const BASE_URL = 'http://localhost:8087'
async function main(): Promise<void> {
console.log('=== 真实 leafer 增量路径验证 ===\n')
let fetchCount = 0
const realFetch = globalThis.fetch
/** 劫持 fetch 计数子集请求数 */
globalThis.fetch = (async (input: RequestInfo | URL, init?: RequestInit) => {
const url = typeof input === 'string' ? input : input instanceof URL ? input.href : input.url
if (url.includes('/api?font=')) fetchCount++
return realFetch(input as never, init as never)
}) as typeof fetch
const leafer = new Leafer({ width: 400, height: 300, fill: '#ffffff' })
const webfont = new WebFontPlugin(leafer as never, { baseUrl: BASE_URL, debounceMs: 0, debug: false })
/* ---------- 1. 事件驱动初始化(不调 refresh纯 waitViewReady 初始 scan ---------- */
const text1 = new Text({ text: '静心', fontFamily: '令东齐伋复刻体.ttf', fontSize: 64, fill: '#000', x: 20, y: 20 })
leafer.add(text1)
await webfont.ready()
console.log(`[1] 初始 TextwaitViewReady 全量 scan: fetch=${fetchCount}, updateCalls=${webfont.updateCalls}`)
console.log(`[1] fontFamily 改写: ${JSON.stringify(text1.fontFamily)}`)
/* ---------- 2. property.change 增量:改文字只提交新字符 ---------- */
const before = fetchCount
text1.text = '静心茶舍新'
await new Promise((r) => setTimeout(r, 300))
console.log(`[2] 改 textproperty.change 增量): 新增 fetch=${fetchCount - before}, updateCalls=${webfont.updateCalls}`)
console.log(`[2] fontFamily 链: ${JSON.stringify(text1.fontFamily)}`)
/* ---------- 3. child.add 增量:新增 Text 自动捕获 ---------- */
const before3 = fetchCount
const text2 = new Text({ text: '新品上市', fontFamily: '令东齐伋复刻体.ttf', fontSize: 32, fill: '#333', x: 20, y: 120 })
leafer.add(text2)
await new Promise((r) => setTimeout(r, 300))
console.log(`[3] 新增 Textchild.add 增量): 新增 fetch=${fetchCount - before3}, updateCalls=${webfont.updateCalls}`)
/* ---------- 4. Group 子树新增(验证子树 walk ---------- */
const before4 = fetchCount
const group = new Group()
group.add(new Text({ text: '全场八折', fontFamily: '令东齐伋复刻体.ttf', fontSize: 24, fill: '#666', x: 20, y: 180 }))
leafer.add(group)
await new Promise((r) => setTimeout(r, 300))
console.log(`[4] 新增 Group 子树(子树 walk: 新增 fetch=${fetchCount - before4}, updateCalls=${webfont.updateCalls}`)
/* ---------- 5. 无文字变化:拖拽模拟(改 x/y 不触发 update ---------- */
const before5 = fetchCount
const calls5 = webfont.updateCalls
for (let i = 0; i < 50; i++) {
text1.x = 20 + i
await new Promise((r) => setTimeout(r, 2))
}
await new Promise((r) => setTimeout(r, 300))
console.log(`[5] 拖拽 50 次(无文字变化): 新增 fetch=${fetchCount - before5}, 新增 updateCalls=${webfont.updateCalls - calls5}`)
/* ---------- 6. 导出验证 ---------- */
await webfont.ready()
const out = (await leafer.export('png', { pixelRatio: 1 })) as { data: string }
console.log(`[6] 导出 PNG: ${Math.round(out.data.length / 1024)}KB (base64)`)
globalThis.fetch = realFetch
webfont.destroy()
leafer.destroy()
console.log('\n=== 验证完成 ===')
}
await main()