37 lines
4.9 KiB
TypeScript
37 lines
4.9 KiB
TypeScript
/**
|
||
* 视图局部的数值/路径投影缓存。按元素代次、几何和变换值复用计算,既不缓存可编辑记录,也不决定可见性。
|
||
* 每次 begin/end 标记本帧访问项,剔除不再出现的条目;切换 scope/project/canvas 时整体清空。
|
||
*/
|
||
import type {Geometry,Id,Json} from '../canvas-core/types.ts';
|
||
import {childCanvasTransform,toWorld,transformPath,type Transform} from '../canvas-core/coordinates.ts';
|
||
import {equal,clone,freeze} from '../canvas-core/values.ts';
|
||
/** 仅比较投影使用的四个变换量,普通对象引用变化不必导致重新计算。 */
|
||
const sameTransform=(a:Transform,b:Transform)=>a.x===b.x&&a.y===b.y&&a.scale===b.scale&&a.rotation===b.rotation;
|
||
/** 节点几何的全部数值字段都参与缓存条件。 */
|
||
const sameGeometry=(a:Geometry,b:Geometry)=>a.x===b.x&&a.y===b.y&&a.width===b.width&&a.height===b.height&&a.rotation===b.rotation;
|
||
/** 子画布变换只依赖 presentation 中 contentScale/unitScale,忽略无关样式字段。 */
|
||
const units=(value:Json|undefined)=>value&&typeof value==='object'&&!Array.isArray(value)?[value.contentScale,value.unitScale]:[];
|
||
/** View-local numeric/path projection only. No editable records, history, visibility or task facts. */
|
||
/** 缓存实例应归一个显示上下文;计数器累计实际计算次数供性能诊断,不是模型版本。 */
|
||
export function createGeometryProjectionCache(){
|
||
let owner:object|undefined,project:Id|undefined,canvas:Id|undefined,nodesComputed=0,childrenComputed=0,edgesComputed=0;
|
||
type NodeResult={position:{x:number;y:number};width:number;height:number;rotation:number};
|
||
const nodes=new Map<Id,{incarnation:number;geometry:Geometry;transform:Transform;value:NodeResult}>(),children=new Map<Id,{incarnation:number;geometry:Geometry;transform:Transform;units:unknown;value:Transform}>(),edges=new Map<Id,{incarnation:number;path:Json;transform:Transform;value:Json}>();let seenNodes=new Set<Id>(),seenChildren=new Set<Id>(),seenEdges=new Set<Id>();
|
||
return Object.freeze({
|
||
/** 开始一轮投影并重置 seen;owner 身份隔离不同 renderer/editor,即使 projectId 相同也不能混用。 */
|
||
begin(scope:object,projectId:Id,canvasId:Id){if(owner!==scope||project!==projectId||canvas!==canvasId){nodes.clear();children.clear();edges.clear();owner=scope;project=projectId;canvas=canvasId;}seenNodes=new Set();seenChildren=new Set();seenEdges=new Set();},
|
||
/** 将节点中心转到场景,按变换比例缩放宽高并合成旋转;缓存复制输入数值防止外部修改污染键。 */
|
||
node(id:Id,incarnation:number,g:Geometry,t:Transform):NodeResult{seenNodes.add(id);const before=nodes.get(id);if(before&&before.incarnation===incarnation&&sameGeometry(before.geometry,g)&&sameTransform(before.transform,t))return before.value;const center=toWorld(t,{x:g.x+g.width/2,y:g.y+g.height/2}),width=g.width*t.scale,height=g.height*t.scale,value=freeze({position:{x:center.x-width/2,y:center.y-height/2},width,height,rotation:(g.rotation+t.rotation)%360});nodes.set(id,{incarnation,geometry:{...g},transform:{...t},value});nodesComputed++;return value;},
|
||
/** 计算分区内部坐标到场景的变换,单位比例变化也会使缓存失效。 */
|
||
child(id:Id,incarnation:number,g:Geometry,t:Transform,presentation:Json|undefined):Transform{seenChildren.add(id);const before=children.get(id),currentUnits=units(presentation);if(before&&before.incarnation===incarnation&&sameGeometry(before.geometry,g)&&sameTransform(before.transform,t)&&equal(before.units,currentUnits))return before.value;const value=freeze(childCanvasTransform(t,g,presentation));children.set(id,{incarnation,geometry:{...g},transform:{...t},units:currentUnits,value});childrenComputed++;return value;},
|
||
/** JSON 路径按值比较;先复制/冻结输入再投影,不持有调用方可变路径作为历史缓存。 */
|
||
edge(id:Id,incarnation:number,path:Json,t:Transform):Json{seenEdges.add(id);const before=edges.get(id);if(before&&before.incarnation===incarnation&&equal(before.path,path)&&sameTransform(before.transform,t))return before.value;const captured=freeze(clone(path)),value=freeze(transformPath(captured,t));edges.set(id,{incarnation,path:captured,transform:{...t},value});edgesComputed++;return value;},
|
||
/** 删除本轮未访问的缓存项,避免浏览多个分区后无限保留旧节点和路径。 */
|
||
end(){for(const id of nodes.keys())if(!seenNodes.has(id))nodes.delete(id);for(const id of children.keys())if(!seenChildren.has(id))children.delete(id);for(const id of edges.keys())if(!seenEdges.has(id))edges.delete(id);},
|
||
/** 返回当前缓存大小和累计计算量的冻结快照;begin 不重置累计计数。 */
|
||
stats:()=>Object.freeze({nodesComputed,childrenComputed,edgesComputed,nodes:nodes.size,children:children.size,edges:edges.size}),
|
||
});
|
||
}
|
||
/** 由工厂推导的缓存接口,供投影器注入复用。 */
|
||
export type GeometryProjectionCache=ReturnType<typeof createGeometryProjectionCache>;
|