61 lines
5.1 KiB
TypeScript
61 lines
5.1 KiB
TypeScript
/**
|
||
* 一个视图拥有的派生场景协调器。组合几何缓存、可信 changes 提示和完整/增量投影,
|
||
* 读取固定的 session 快照;不把 viewport、工具状态或同步任务状态当成场景重算条件。
|
||
*/
|
||
import {fault} from '../canvas-core/values.ts';
|
||
import type {Change,Document,Registry} from '../canvas-core/types.ts';
|
||
import type {SessionSnapshot,ViewState} from '../canvas-core/gestures.ts';
|
||
import type {DocumentQueries} from '../canvas-core/queries.ts';
|
||
import {createGeometryProjectionCache} from './geometry-cache.ts';
|
||
import {projectCanvas} from './projection.ts';
|
||
import {indexScene,patchScene,type SceneChangeHint,type SceneInputs} from './scene-changes.ts';
|
||
import type {RendererCatalog} from './renderers.ts';
|
||
|
||
/** One view's derived scene. Viewport/tool/mode and task status do not change scene inputs. */
|
||
/** 每个视图一份 previous/value/layout/pending 与几何缓存;advance 只积写集合,project 才按需算场景。
|
||
* 缓存借用不可变文档/选择/预览/registry/catalog 身份,不能在原对象中改字段后期待自动发现失效。
|
||
* 实例不订阅 editor、不请求帧,宿主负责接线 advance/project 与视图 clear 生命周期。 */
|
||
export function createSceneProjection(){
|
||
let geometry=createGeometryProjectionCache();
|
||
|
||
let previous:SceneInputs|undefined,value:ReturnType<typeof projectCanvas>|undefined,layout:ReturnType<typeof indexScene>|undefined,pending:SceneChangeHint|undefined;
|
||
return Object.freeze({
|
||
/** 累积上次投影文档→当前文档的连续发布链;同一文档对象直接忽略,未有场景时无需积累。
|
||
* 断链只在 previous.document===before 时能重起;空 changes 丢提示,canvas 改动/脏 ID>16384 只标 topology。
|
||
* 这里只影响快路径选择,不重放 Change、不写 doc,也不保证输入 changes 本身语义完整。 */
|
||
advance(before:Document,after:Document,changes:readonly Change[]){
|
||
if(before===after)return;
|
||
if(!previous||previous.document===after){pending=undefined;return;}
|
||
// 只有链尾接上 before 才能继续累计;不连贯时仅从恰好匹配的旧场景重新起链。
|
||
if(pending?.to!==before){pending=previous.document===before?{from:before,to:after,ids:new Set(),topology:false}:undefined;}
|
||
if(!pending)return;pending.to=after;
|
||
if(!changes.length){pending=undefined;return;}
|
||
// canvas 结构改动或超过16384个脏 ID 时不再扩充集合,标拓扑失效并清空大集合。
|
||
for(const change of changes){if(change.kind==='canvas'){pending.topology=true;break;}pending.ids.add(change.kind==='element'?change.id:change.target.id);if(pending.ids.size>16384){pending.topology=true;break;}}
|
||
if(pending.topology)pending.ids.clear();
|
||
},
|
||
/** 固定 document/registry/catalog/显示 canvas 和捕获的 session 输入;相同引用直接复用场景。
|
||
* 可兼容才尝试 patch,undefined 正常回退 projectCanvas;异常不自动吞成另一份成功结果。
|
||
* queries 先查 document/registry 身份;其对象身份不在场景键中,单独更换 queries 不触发重算。
|
||
* 空 preview 规范为 undefined,viewport/tool/mode 不在键中;传入的 sessions 是捕获帧,不读取更新的 live 会话。 */
|
||
project(owner:object,document:Document,view:ViewState,sessions:SessionSnapshot,registry:Registry,catalog:RendererCatalog,queries?:DocumentQueries){
|
||
if(queries&&queries.document!==document)throw fault('stale_query_snapshot');
|
||
if(queries&&queries.registry!==registry)throw fault('query_registry_changed');
|
||
const preview=sessions.gesture?.preview,connection=sessions.connectionGesture;
|
||
const next:SceneInputs={owner,document,registry,catalog,canvasId:view.canvasId,selection:view.selection,preview:preview?.size?preview:undefined,edgeId:connection?.target.id,path:connection?.path};
|
||
if(previous&&Object.keys(next).every(key=>Object.is(next[key as keyof SceneInputs],previous![key as keyof SceneInputs])))return value!;
|
||
// Read the captured session, not a newer live gesture during an older React render.
|
||
// owner/registry/renderer catalog/根 canvas 改变意味着解释上下文变了,不能沿用局部补丁。
|
||
const compatible=previous&&value&&layout&&previous.owner===owner&&previous.registry===registry&&previous.catalog===catalog&&previous.canvasId===view.canvasId;
|
||
const patched=compatible?patchScene(value!,layout!,next,previous!,connection,pending,queries):undefined;
|
||
const result=patched??projectCanvas(document,view.canvasId,registry,view.selection,id=>next.preview?.get(id),connection,catalog,{queries,geometry,owner});
|
||
// 全量结果或节点数变化才重建 layout,其余兼容 patch 沿用数组下标/拓扑索引。
|
||
// 成功后一次推进 previous/value 并清 pending;异常不写这三项,但本轮几何缓存可能已有计算,不承诺整缓存回滚。
|
||
if(!patched||result.nodes.length!==value!.nodes.length)layout=indexScene(result);
|
||
previous=next;value=result;pending=undefined;return result;
|
||
},
|
||
/** 撤销上一帧、写集合和几何缓存,解除对旧文档/视图的引用。 */
|
||
clear(){previous=undefined;value=undefined;layout=undefined;pending=undefined;geometry=createGeometryProjectionCache();},
|
||
});
|
||
}
|