Retain projected paint geometry and grid across view updates
This commit is contained in:
@@ -10,8 +10,7 @@ import { defaultCanvasRenderers } from "./canvas-renderers";
|
||||
import type { Data, ElementRecord, Geometry, Id, Json, Result } from "@/lib/canvas-core/types";
|
||||
import { equal } from "@/lib/canvas-core/values";
|
||||
import {createGeometryProjectionCache} from "@/lib/canvas-bindings/geometry-cache";
|
||||
import {createPaintIndex} from "@/lib/canvas-bindings/paint-index";
|
||||
import {projectedPaintOverflow} from "@/lib/canvas-bindings/projected-paint";
|
||||
import {createProjectedPaintIndex} from "@/lib/canvas-bindings/projected-paint";
|
||||
import {createSessionFrameSource} from "@/lib/canvas-bindings/session-frames";
|
||||
import {routeCanvasKey} from "@/lib/canvas-bindings/input-router";
|
||||
import {hitProjectedCanvas} from "@/lib/canvas-bindings/hit-query";
|
||||
@@ -75,9 +74,10 @@ function CanvasView({ engine, board, viewId, renderers,fitLoading=true }: { engi
|
||||
for (const node of result.nodes) { const cached = measurements.current.get(node.id); if (cached && cached.width === node.width && cached.height === node.height && projectionCache.current!.canRetainMeasurement(engine,root.document,view.canvasId,node)) node.measured = cached.measured; }
|
||||
return projectionCache.current!.reuse(engine,root.document,view.canvasId,result);
|
||||
}, [engine, root.document, sessions, view,renderers,catalog]);
|
||||
const paint=useMemo(()=>{const result=projectedPaintOverflow(projection,engine.editor.registry),index=createPaintIndex();index.sync(result.regions);return{index,unknown:result.unknown};},[projection,engine]);
|
||||
const paintCache=useMemo(()=>createProjectedPaintIndex(),[engine,view.canvasId]);
|
||||
const paint=useMemo(()=>paintCache.sync(projection,engine.editor.registry),[paintCache,projection,engine]);
|
||||
const viewport=useViewport(),surfaceWidth=useStore(state=>state.width),surfaceHeight=useStore(state=>state.height);
|
||||
const paintVisible=paint.unknown.length>0||!surfaceWidth||!surfaceHeight||paint.index.query({x:-viewport.x/viewport.zoom,y:-viewport.y/viewport.zoom,width:surfaceWidth/viewport.zoom,height:surfaceHeight/viewport.zoom}).size>0;
|
||||
const paintVisible=paint.unknown.length>0||!surfaceWidth||!surfaceHeight||paint.query({x:-viewport.x/viewport.zoom,y:-viewport.y/viewport.zoom,width:surfaceWidth/viewport.zoom,height:surfaceHeight/viewport.zoom}).size>0;
|
||||
const nodeTypes=useMemo(()=>({...renderers.nodeTypes,unsupported:UnsupportedNode}),[renderers]),edgeTypes=useMemo(()=>({...renderers.edgeTypes,unsupported:UnsupportedEdge}),[renderers]);
|
||||
const current = useRef({ root, projection, view, engine, sessions }); current.current = { root, projection, view, engine, sessions };
|
||||
const report = useCallback((result: Result | { enabled: boolean; code?: string }) => {
|
||||
|
||||
@@ -17,4 +17,4 @@ export type {FrameClock} from './frame-queue.ts';
|
||||
export {createSessionFrameSource} from './session-frames.ts';
|
||||
export {createPaintIndex} from './paint-index.ts';
|
||||
export type {PaintRegion} from './paint-index.ts';
|
||||
export {projectedPaintOverflow} from './projected-paint.ts';
|
||||
export {projectedPaintOverflow,createProjectedPaintIndex} from './projected-paint.ts';
|
||||
|
||||
@@ -2,7 +2,7 @@ import {rectangleGeometry} from '../canvas-core/geometry.ts';
|
||||
import {toLocal,toWorld} from '../canvas-core/coordinates.ts';
|
||||
import type {Registry} from '../canvas-core/types.ts';
|
||||
import type {projectCanvas} from './projection.ts';
|
||||
import type {PaintRegion} from './paint-index.ts';
|
||||
import {createPaintIndex,type PaintRegion} from './paint-index.ts';
|
||||
/** Extra node paint extents beyond native unrotated React Flow bounds. */
|
||||
export function projectedPaintOverflow(projection:ReturnType<typeof projectCanvas>,registry:Registry){
|
||||
const regions:PaintRegion[]=[],unknown:string[]=[];
|
||||
@@ -17,3 +17,24 @@ export function projectedPaintOverflow(projection:ReturnType<typeof projectCanva
|
||||
}catch{unknown.push(node.id);}
|
||||
return{regions,unknown};
|
||||
}
|
||||
|
||||
/** View-owned incremental paint grid; reuse unaffected node results across Root/session updates. */
|
||||
export function createProjectedPaintIndex(){
|
||||
let registryOwner:Registry|undefined,canvas:string|undefined,project:string|undefined,computed=0;
|
||||
type Projection=ReturnType<typeof projectCanvas>;
|
||||
type Entry={node:Projection['nodes'][number];element:unknown;transform:string;result:ReturnType<typeof projectedPaintOverflow>};
|
||||
const entries=new Map<string,Entry>(),index=createPaintIndex();
|
||||
return Object.freeze({
|
||||
sync(projection:Projection,registry:Registry){
|
||||
if(registryOwner!==registry||canvas!==projection.canvasId||project!==projection.document.projectId){entries.clear();index.clear();registryOwner=registry;canvas=projection.canvasId;project=projection.document.projectId;}
|
||||
const seen=new Set<string>(),regions:PaintRegion[]=[],unknown:string[]=[];
|
||||
for(const node of projection.nodes){seen.add(node.id);const element=projection.document.elements.get(node.id),t=element&&projection.transforms.get(element.ownerCanvasId),transform=t?`${t.x},${t.y},${t.scale},${t.rotation}`:'missing',old=entries.get(node.id);
|
||||
const result=old&&old.node===node&&old.element===element&&old.transform===transform?old.result:projectedPaintOverflow({...projection,nodes:[node]},registry);
|
||||
if(result!==old?.result)computed++;entries.set(node.id,{node,element,transform,result});regions.push(...result.regions);unknown.push(...result.unknown);
|
||||
}
|
||||
for(const id of entries.keys())if(!seen.has(id))entries.delete(id);index.sync(regions);return Object.freeze({query:index.query,unknown:Object.freeze(unknown)});
|
||||
},
|
||||
stats:()=>Object.freeze({computed,entries:entries.size}),
|
||||
clear(){entries.clear();index.clear();registryOwner=undefined;canvas=undefined;project=undefined;},
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user