/** * React Flow 输入对象的结构共享缓存,与计算场景的 scene-projection 分工不同。 * 按元素代次与 owner 判定同一生命周期,复用值相等的 data/style/position 和整个数组,减少显示刷新。 * 缓存属于一个 owner/project/视图 canvas,既不是业务文档,也不是跨项目身份仓库。 */ import type {Document,Id} from '../canvas-core/types.ts'; import {equal,fault} from '../canvas-core/values.ts'; import type {projectCanvas} from './projection.ts'; /** 只处理已经生成的投影,不在这里解释元素组件或计算坐标。 */ type Projection=ReturnType; /** 原对象的生命周期证据;同 ID 的重建或移动 owner 后不能直接继承旧显示对象。 */ type Retained={incarnation:number;ownerCanvasId:Id;value:T}; /** View-owned rendering cache, never a document store. Keep unchanged React Flow input identities. */ /** 创建视图专属结构共享器;reuse 会就地替换传入投影数组中的等价条目,调用方应提供派生数组。 */ export function createProjectionCache(){ let scope:object|undefined,projectId:Id|undefined,canvasId:Id|undefined; let nodeArray:Projection['nodes']|undefined,edgeArray:Projection['edges']|undefined; /** 数组长度与逐项引用完全一致时沿用旧数组,顺序改变也会产生新数组身份。 */ const stable=(previous:T[]|undefined,next:T[])=>previous?.length===next.length&&next.every((value,index)=>value===previous[index])?previous:next; let nodes=new Map>(),edges=new Map>(); /** 对每个显示 ID 核验模型身份,再逐层共享等价字段;返回仅包含本帧元素的新缓存表。 */ function retain(document:Document,values:T[],previous:Map>){ const next=new Map>(); for(let index=0;index)[key],before=(old.value as Record)[key];if(Object.hasOwn(shared,key)&&Object.hasOwn(old.value,key)&&!Object.is(incoming,before)&&equal(incoming,before))shared={...shared,[key]:before};} const retained=sameLifetime&&equal(old.value,shared)?old.value:shared; values[index]=retained;next.set(value.id,{incarnation:record.incarnation,ownerCanvasId:record.ownerCanvasId,value:retained}); } return next; } return Object.freeze({ /** 范围改变先整体清空;保存规范化后的对象表及稳定数组,返回包装后的投影。 */ reuse(owner:object,document:Document,viewCanvasId:Id,projection:Projection):Projection{ if(scope!==owner||projectId!==document.projectId||canvasId!==viewCanvasId){nodes.clear();edges.clear();nodeArray=undefined;edgeArray=undefined;scope=owner;projectId=document.projectId;canvasId=viewCanvasId;} const nextNodes=retain(document,projection.nodes,nodes),nextEdges=retain(document,projection.edges,edges); nodes=nextNodes;edges=nextEdges;nodeArray=stable(nodeArray,projection.nodes);edgeArray=stable(edgeArray,projection.edges); return{...projection,nodes:nodeArray,edges:edgeArray}; }, /** 读取本轮保留的节点显示对象,供 renderer 测量等显示流程使用。 */ getNode(id:Id){return nodes.get(id)?.value;}, /** 只有上下文、生命周期、renderer type、尺寸和 data 均兼容才可沿用旧测量,不能只比较 ID。 */ canRetainMeasurement(owner:object,document:Document,viewCanvasId:Id,value:Projection['nodes'][number]){ const old=nodes.get(value.id),record=document.elements.get(value.id); return scope===owner&&projectId===document.projectId&&canvasId===viewCanvasId&&!!old&&!!record&&old.incarnation===record.incarnation&&old.ownerCanvasId===record.ownerCanvasId&&old.value.type===value.type&&old.value.width===value.width&&old.value.height===value.height&&equal(old.value.data,value.data); }, /** 清空对象/数组和范围身份,调用方无需也不应据此清除业务文档。 */ clear(){nodes.clear();edges.clear();nodeArray=undefined;edgeArray=undefined;scope=undefined;projectId=undefined;canvasId=undefined;}, }); }