Files
Endoleap/src/lib/canvas-bindings/scene-projection.ts
T

61 lines
5.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.
/**
* 一个视图拥有的派生场景协调器。组合几何缓存、可信 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();},
});
}