docs(canvas): explain binding input subscriptions and hit queries
This commit is contained in:
@@ -1,22 +1,35 @@
|
||||
/**
|
||||
* 显示层的帧合并队列。把多次“需要重画”合并成去重 key 集合,不延迟命令提交、历史或网络任务。
|
||||
* 队列只拥有调度句柄和失效索引;真实数据随时从上游快照读取,dispose 时丢弃未发布通知。
|
||||
*/
|
||||
import type {ExternalSource} from './selectors.ts';
|
||||
/** Presentation invalidations only. Commands, history and network work never wait here. */
|
||||
/** 宿主注入帧请求/取消能力,可接 requestAnimationFrame 或测试时钟;handle 不保存业务身份。 */
|
||||
export type FrameClock={request(callback:()=>void):unknown;cancel(handle:unknown):void};
|
||||
/** 创建一条可手动 flush 的通知队列;同一 key 在一次批次中只出现一次。 */
|
||||
export function createFrameQueue<K>(clock:FrameClock,publish:(dirty:ReadonlySet<K>)=>void){
|
||||
let dirty=new Set<K>(),scheduled:{handle?:unknown}|undefined,disposed=false;
|
||||
/** 同步排空已有脏项,先换新 Set 再回调,回调中的新失效留给下一批,不会被本批 clear 擦掉。 */
|
||||
function flush(){if(disposed)return;const pending=scheduled;scheduled=undefined;if(pending)clock.cancel(pending.handle);if(!dirty.size)return;const batch=dirty;dirty=new Set();publish(batch);}
|
||||
return Object.freeze({
|
||||
/** 合并 keys 并最多安排一个帧回调;pending 对象身份防止已取消的旧回调误刷后来的批次。 */
|
||||
invalidate(keys:Iterable<K>){if(disposed)return;for(const key of keys)dirty.add(key);if(!dirty.size||scheduled)return;const pending: {handle?:unknown}={};scheduled=pending;try{pending.handle=clock.request(()=>{if(scheduled!==pending)return;scheduled=undefined;flush();});}catch(error){scheduled=undefined;throw error;}},
|
||||
flush,
|
||||
/** 幂等关闭并撤销帧句柄,关闭后失效通知无效果;不会替上游关闭 editor。 */
|
||||
dispose(){if(disposed)return;disposed=true;if(scheduled)clock.cancel(scheduled.handle);scheduled=undefined;dirty.clear();},
|
||||
});
|
||||
}
|
||||
|
||||
/** Coalesce presentation notifications; immediate invalidations flush pending work synchronously. */
|
||||
/** 给普通外部数据源加通知节流:读取始终透传当前快照,每个订阅独有可释放的帧队列。
|
||||
* 上游标 immediate 时同步 flush,常用于不能等下一帧才反映的状态边界。 */
|
||||
export function createFrameSource<S>(source:{getSnapshot():S;subscribe(notify:(immediate?:boolean)=>void):()=>void},clock:FrameClock):ExternalSource<S>{
|
||||
return Object.freeze({getSnapshot:()=>source.getSnapshot(),subscribe(notify:()=>void){
|
||||
const queue=createFrameQueue<number>(clock,()=>notify());
|
||||
let stop:()=>void;
|
||||
// 注册抛错也要撤销可能已经安排的帧,避免一个未建立成功的订阅仍在稍后通知。
|
||||
try{stop=source.subscribe(immediate=>{queue.invalidate([0]);if(immediate)queue.flush();});}catch(error){queue.dispose();throw error;}
|
||||
// 先退订上游,finally 保证上游清理抛错时仍释放本订阅的显示队列。
|
||||
return()=>{try{stop();}finally{queue.dispose();}};
|
||||
}});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user