/** * 把节点端口/尺寸变化的测量请求合并给 renderer,避免同一轮重复调用更新。 * owner token 区分同 ID 的不同挂载实例;卸载实例的迟到失效不能触发后来新实例的测量。 */ /** One renderer store's transient measurement requests. Not model or history state. */ /** 每个 renderer store 独立创建:owners 记录挂载 token→nodeId,dirty 只记录待测 token。 * defer 默认为微任务;update 接收执行时仍有效的 ID,不接触文档。工厂没有全局 dispose/flush 公开入口。 */ export function createNodeInternalsBatch(update:(ids:string[])=>void,defer:(flush:()=>void)=>void=queueMicrotask){ const owners=new Map(),dirty=new Set();let queued=false; /** 执行时解析仍有效 owner 并按 ID 去重,清 queued/dirty 后 update;重入 invalidate 可安排下一轮。 * update 异常直接传播,本批不自动回队重试;卸载 token 的请求被排除,新 token 须自行 invalidate 才入本批。 */ function flush(){ queued=false;const ids=new Set();for(const owner of dirty){const id=owners.get(owner);if(id!==undefined)ids.add(id);}dirty.clear(); if(ids.size)update([...ids]); } return Object.freeze({ /** 每次挂载取得独立租约,即使节点 ID 相同也能单独 dispose。 */ acquire(id:string){ const owner=Symbol(id);owners.set(owner,id); return Object.freeze({ /** 活跃 token 去重变脏,queued 防重复 defer;多个 token 若同 nodeId,flush 时再合成一个 ID。 * 不捕获 defer 异常:若宿主抛错但未排任务,queued 不自动复位,宿主应使用可靠调度端口。 */ invalidate(){if(!owners.has(owner))return;dirty.add(owner);if(!queued){queued=true;defer(flush);}}, /** 卸载同时移除 owner 和其未执行请求;已排微任务可自然执行为空,不取得新实例权限。 */ dispose(){owners.delete(owner);dirty.delete(owner);}, }); }, }); }