@canvas-harness/core 0.1.20 → 0.1.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +152 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +18 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +152 -5
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -2311,6 +2311,17 @@ type RendererOptions = {
|
|
|
2311
2311
|
*/
|
|
2312
2312
|
onOverlayChange?: (mountedIds: NodeId[]) => void;
|
|
2313
2313
|
};
|
|
2314
|
+
/**
|
|
2315
|
+
* Path the last static-layer paint took through the cache tiers.
|
|
2316
|
+
* Returned by {@link Renderer.getLastDrawPath} for test instrumentation.
|
|
2317
|
+
* - `'idle'` no static paint has happened yet
|
|
2318
|
+
* - `'present'` cache hit, 1:1 blit (cheapest path)
|
|
2319
|
+
* - `'extend'` same-zoom pan past margin, shift + strip blit
|
|
2320
|
+
* - `'scaled'` mid-zoom scaled blit (no re-rasterization)
|
|
2321
|
+
* - `'scaled-extend'` mid-zoom-out, scale-blit center + redraw perimeter
|
|
2322
|
+
* - `'full'` full re-render (cache invalidated)
|
|
2323
|
+
*/
|
|
2324
|
+
type StaticDrawPath = 'idle' | 'present' | 'extend' | 'scaled' | 'scaled-extend' | 'full';
|
|
2314
2325
|
type Renderer = {
|
|
2315
2326
|
/** Begin the rAF loop. Idempotent. */
|
|
2316
2327
|
start(): void;
|
|
@@ -2334,6 +2345,13 @@ type Renderer = {
|
|
|
2334
2345
|
stats(): FrameStats;
|
|
2335
2346
|
/** Number of items the most recent paint actually drew. */
|
|
2336
2347
|
lastDrawCount(): number;
|
|
2348
|
+
/**
|
|
2349
|
+
* Path the last static-layer paint took. Test instrumentation —
|
|
2350
|
+
* lets browser tests assert which cache tier fired. `'present'` =
|
|
2351
|
+
* 1:1 blit, `'extend'` = strip-extend + blit, `'scaled'` = mid-zoom
|
|
2352
|
+
* scaled blit, `'full'` = full re-render.
|
|
2353
|
+
*/
|
|
2354
|
+
getLastDrawPath(): StaticDrawPath;
|
|
2337
2355
|
/** Current overlay-mounted custom-node ids. */
|
|
2338
2356
|
getOverlaySet(): NodeId[];
|
|
2339
2357
|
/**
|
package/dist/index.d.ts
CHANGED
|
@@ -2311,6 +2311,17 @@ type RendererOptions = {
|
|
|
2311
2311
|
*/
|
|
2312
2312
|
onOverlayChange?: (mountedIds: NodeId[]) => void;
|
|
2313
2313
|
};
|
|
2314
|
+
/**
|
|
2315
|
+
* Path the last static-layer paint took through the cache tiers.
|
|
2316
|
+
* Returned by {@link Renderer.getLastDrawPath} for test instrumentation.
|
|
2317
|
+
* - `'idle'` no static paint has happened yet
|
|
2318
|
+
* - `'present'` cache hit, 1:1 blit (cheapest path)
|
|
2319
|
+
* - `'extend'` same-zoom pan past margin, shift + strip blit
|
|
2320
|
+
* - `'scaled'` mid-zoom scaled blit (no re-rasterization)
|
|
2321
|
+
* - `'scaled-extend'` mid-zoom-out, scale-blit center + redraw perimeter
|
|
2322
|
+
* - `'full'` full re-render (cache invalidated)
|
|
2323
|
+
*/
|
|
2324
|
+
type StaticDrawPath = 'idle' | 'present' | 'extend' | 'scaled' | 'scaled-extend' | 'full';
|
|
2314
2325
|
type Renderer = {
|
|
2315
2326
|
/** Begin the rAF loop. Idempotent. */
|
|
2316
2327
|
start(): void;
|
|
@@ -2334,6 +2345,13 @@ type Renderer = {
|
|
|
2334
2345
|
stats(): FrameStats;
|
|
2335
2346
|
/** Number of items the most recent paint actually drew. */
|
|
2336
2347
|
lastDrawCount(): number;
|
|
2348
|
+
/**
|
|
2349
|
+
* Path the last static-layer paint took. Test instrumentation —
|
|
2350
|
+
* lets browser tests assert which cache tier fired. `'present'` =
|
|
2351
|
+
* 1:1 blit, `'extend'` = strip-extend + blit, `'scaled'` = mid-zoom
|
|
2352
|
+
* scaled blit, `'full'` = full re-render.
|
|
2353
|
+
*/
|
|
2354
|
+
getLastDrawPath(): StaticDrawPath;
|
|
2337
2355
|
/** Current overlay-mounted custom-node ids. */
|
|
2338
2356
|
getOverlaySet(): NodeId[];
|
|
2339
2357
|
/**
|
package/dist/index.js
CHANGED
|
@@ -4752,6 +4752,59 @@ var buildPath = (type, x, y, w, h, radius) => {
|
|
|
4752
4752
|
}
|
|
4753
4753
|
};
|
|
4754
4754
|
|
|
4755
|
+
// src/render/scene-cache-math.ts
|
|
4756
|
+
var computeCacheSourceRect = (cache5, view) => {
|
|
4757
|
+
const ratio = cache5.camZ / view.camZ;
|
|
4758
|
+
const srcX = Math.round(((view.camX - cache5.camX) * cache5.camZ + cache5.marginCssPx) * cache5.dpr);
|
|
4759
|
+
const srcY = Math.round(((view.camY - cache5.camY) * cache5.camZ + cache5.marginCssPx) * cache5.dpr);
|
|
4760
|
+
const srcW = view.widthCssPx * ratio * cache5.dpr;
|
|
4761
|
+
const srcH = view.heightCssPx * ratio * cache5.dpr;
|
|
4762
|
+
return { srcX, srcY, srcW, srcH };
|
|
4763
|
+
};
|
|
4764
|
+
var cacheCoversViewport = (cache5, view) => {
|
|
4765
|
+
const { srcX, srcY, srcW, srcH } = computeCacheSourceRect(cache5, view);
|
|
4766
|
+
return srcX >= 0 && srcY >= 0 && srcX + srcW <= cache5.widthDevicePx && srcY + srcH <= cache5.heightDevicePx;
|
|
4767
|
+
};
|
|
4768
|
+
var scaleRatioInBounds = (cacheCamZ, viewCamZ, maxRatio) => {
|
|
4769
|
+
if (viewCamZ <= 0 || cacheCamZ <= 0 || maxRatio <= 0) return false;
|
|
4770
|
+
const ratio = viewCamZ >= cacheCamZ ? viewCamZ / cacheCamZ : cacheCamZ / viewCamZ;
|
|
4771
|
+
return ratio <= maxRatio;
|
|
4772
|
+
};
|
|
4773
|
+
var cacheReuseLayout = (cache5, view) => {
|
|
4774
|
+
const ratio = view.camZ / cache5.camZ;
|
|
4775
|
+
const cacheW = cache5.widthDevicePx;
|
|
4776
|
+
const cacheH = cache5.heightDevicePx;
|
|
4777
|
+
const marginDev = cache5.marginCssPx * cache5.dpr;
|
|
4778
|
+
const destW = cacheW * ratio;
|
|
4779
|
+
const destH = cacheH * ratio;
|
|
4780
|
+
const destX = (cache5.camX - view.camX) * view.camZ * cache5.dpr + marginDev * (1 - ratio);
|
|
4781
|
+
const destY = (cache5.camY - view.camY) * view.camZ * cache5.dpr + marginDev * (1 - ratio);
|
|
4782
|
+
const dest = { x: destX, y: destY, w: destW, h: destH };
|
|
4783
|
+
const strips = {
|
|
4784
|
+
top: { x: 0, y: 0, w: cacheW, h: Math.max(0, destY) },
|
|
4785
|
+
bottom: {
|
|
4786
|
+
x: 0,
|
|
4787
|
+
y: destY + destH,
|
|
4788
|
+
w: cacheW,
|
|
4789
|
+
h: Math.max(0, cacheH - destY - destH)
|
|
4790
|
+
},
|
|
4791
|
+
left: { x: 0, y: destY, w: Math.max(0, destX), h: destH },
|
|
4792
|
+
right: {
|
|
4793
|
+
x: destX + destW,
|
|
4794
|
+
y: destY,
|
|
4795
|
+
w: Math.max(0, cacheW - destX - destW),
|
|
4796
|
+
h: destH
|
|
4797
|
+
}
|
|
4798
|
+
};
|
|
4799
|
+
const valid = destX >= 0 && destY >= 0 && destX + destW <= cacheW && destY + destH <= cacheH;
|
|
4800
|
+
return { dest, strips, valid };
|
|
4801
|
+
};
|
|
4802
|
+
var zoomExtendRatioInBounds = (cacheCamZ, viewCamZ, minRatio) => {
|
|
4803
|
+
if (viewCamZ <= 0 || cacheCamZ <= 0 || minRatio <= 0 || minRatio >= 1) return false;
|
|
4804
|
+
const ratio = viewCamZ / cacheCamZ;
|
|
4805
|
+
return ratio >= minRatio && ratio < 1;
|
|
4806
|
+
};
|
|
4807
|
+
|
|
4755
4808
|
// src/render/shapes/content-bounds.ts
|
|
4756
4809
|
var SQRT2_INV = 1 / Math.SQRT2;
|
|
4757
4810
|
var contentBounds = (node) => {
|
|
@@ -4836,6 +4889,7 @@ var createRenderer = (opts) => {
|
|
|
4836
4889
|
let interactiveDirty = false;
|
|
4837
4890
|
let overlaySet = /* @__PURE__ */ new Set();
|
|
4838
4891
|
let lastDrawn = 0;
|
|
4892
|
+
let lastDrawPath = "idle";
|
|
4839
4893
|
let cacheSurface = null;
|
|
4840
4894
|
let cacheCamX = 0;
|
|
4841
4895
|
let cacheCamY = 0;
|
|
@@ -4893,6 +4947,8 @@ var createRenderer = (opts) => {
|
|
|
4893
4947
|
paintBackground(surface.ctx, { viewport, zoom: camera.z, background });
|
|
4894
4948
|
const visible = visibleNodes(camera, viewport);
|
|
4895
4949
|
const isMoving2 = isMoving(interaction);
|
|
4950
|
+
const viewMotion = interaction.mode === "panning" || interaction.mode === "zooming" || interaction.mode === "marqueeing";
|
|
4951
|
+
const isStripRender = !fullRender;
|
|
4896
4952
|
const minOnScreen = MIN_ON_SCREEN_SIZE_PX;
|
|
4897
4953
|
const nextOverlaySet = /* @__PURE__ */ new Set();
|
|
4898
4954
|
let drawn = 0;
|
|
@@ -4905,9 +4961,8 @@ var createRenderer = (opts) => {
|
|
|
4905
4961
|
theme: (token) => theme ? theme(token) : void 0
|
|
4906
4962
|
};
|
|
4907
4963
|
const editingNodeId = interaction.editingTarget?.kind === "node" ? interaction.editingTarget.id : null;
|
|
4908
|
-
const cameraIsMoving = interaction.mode === "panning" || interaction.mode === "zooming";
|
|
4909
4964
|
const movingNodeCount = excludedNodes?.size ?? 0;
|
|
4910
|
-
const roughEnabled =
|
|
4965
|
+
const roughEnabled = movingNodeCount <= ROUGH_MAX_MOVING_NODES && camera.z >= ROUGH_MIN_ZOOM && visible.length <= ROUGH_MAX_NODES;
|
|
4911
4966
|
if (!hideFrames) {
|
|
4912
4967
|
for (const node of visible) {
|
|
4913
4968
|
if (node.type !== "frame") continue;
|
|
@@ -4982,7 +5037,7 @@ var createRenderer = (opts) => {
|
|
|
4982
5037
|
if (!def) continue;
|
|
4983
5038
|
if (node.w * camera.z < minOnScreen && node.h * camera.z < minOnScreen) continue;
|
|
4984
5039
|
if (camera.z < def.lod.minZoomForPlaceholder) continue;
|
|
4985
|
-
const preferCanvas = camera.z < def.lod.minZoomForReact ||
|
|
5040
|
+
const preferCanvas = camera.z < def.lod.minZoomForReact || viewMotion && isStripRender && !overlaySet.has(node.id);
|
|
4986
5041
|
if (preferCanvas) {
|
|
4987
5042
|
if (paintCustomCanvasFallback(surface.ctx, node, def, scale, renderEnv)) {
|
|
4988
5043
|
drawn++;
|
|
@@ -5003,7 +5058,7 @@ var createRenderer = (opts) => {
|
|
|
5003
5058
|
}
|
|
5004
5059
|
}
|
|
5005
5060
|
const visEdges = visibleEdges(viewport);
|
|
5006
|
-
const edgeRoughEnabled =
|
|
5061
|
+
const edgeRoughEnabled = movingNodeCount <= ROUGH_MAX_MOVING_NODES && camera.z >= ROUGH_MIN_ZOOM && visEdges.length <= ROUGH_MAX_NODES;
|
|
5007
5062
|
for (const edge of visEdges) {
|
|
5008
5063
|
if (excludedEdges?.has(edge.id)) continue;
|
|
5009
5064
|
paintOneEdge(surface.ctx, edge, scale, edgeRoughEnabled, camera.z, isMoving2);
|
|
@@ -5083,6 +5138,44 @@ var createRenderer = (opts) => {
|
|
|
5083
5138
|
if (hw > 0) renderCacheStrip(cache5, newCamX, newCamY, camera.z, hx, 0, hw, cacheH);
|
|
5084
5139
|
if (vh > 0 && vw > 0) renderCacheStrip(cache5, newCamX, newCamY, camera.z, vx, vy, vw, vh);
|
|
5085
5140
|
};
|
|
5141
|
+
let scaledExtendScratch = null;
|
|
5142
|
+
const extendCacheScaled = (camera, layout) => {
|
|
5143
|
+
const cache5 = ensureCacheSurface();
|
|
5144
|
+
const cacheW = cache5.canvas.width;
|
|
5145
|
+
const cacheH = cache5.canvas.height;
|
|
5146
|
+
if (!scaledExtendScratch || scaledExtendScratch.width !== cacheW || scaledExtendScratch.height !== cacheH) {
|
|
5147
|
+
scaledExtendScratch = document.createElement("canvas");
|
|
5148
|
+
scaledExtendScratch.width = cacheW;
|
|
5149
|
+
scaledExtendScratch.height = cacheH;
|
|
5150
|
+
}
|
|
5151
|
+
const sctx = scaledExtendScratch.getContext("2d");
|
|
5152
|
+
sctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
5153
|
+
sctx.clearRect(0, 0, cacheW, cacheH);
|
|
5154
|
+
sctx.drawImage(cache5.canvas, 0, 0);
|
|
5155
|
+
cache5.ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
5156
|
+
cache5.ctx.clearRect(0, 0, cacheW, cacheH);
|
|
5157
|
+
cache5.ctx.drawImage(
|
|
5158
|
+
scaledExtendScratch,
|
|
5159
|
+
layout.dest.x,
|
|
5160
|
+
layout.dest.y,
|
|
5161
|
+
layout.dest.w,
|
|
5162
|
+
layout.dest.h
|
|
5163
|
+
);
|
|
5164
|
+
cacheCamX = camera.x;
|
|
5165
|
+
cacheCamY = camera.y;
|
|
5166
|
+
cacheCamZ = camera.z;
|
|
5167
|
+
const strips = [
|
|
5168
|
+
layout.strips.top,
|
|
5169
|
+
layout.strips.bottom,
|
|
5170
|
+
layout.strips.left,
|
|
5171
|
+
layout.strips.right
|
|
5172
|
+
];
|
|
5173
|
+
for (const s of strips) {
|
|
5174
|
+
if (s.w > 0 && s.h > 0) {
|
|
5175
|
+
renderCacheStrip(cache5, camera.x, camera.y, camera.z, s.x, s.y, s.w, s.h);
|
|
5176
|
+
}
|
|
5177
|
+
}
|
|
5178
|
+
};
|
|
5086
5179
|
const cacheSourceOffset = (camera) => {
|
|
5087
5180
|
const dpr = staticSurface.dpr;
|
|
5088
5181
|
return {
|
|
@@ -5104,21 +5197,72 @@ var createRenderer = (opts) => {
|
|
|
5104
5197
|
staticSurface.ctx.clearRect(0, 0, w, h);
|
|
5105
5198
|
staticSurface.ctx.drawImage(cache5.canvas, srcX, srcY, w, h, 0, 0, w, h);
|
|
5106
5199
|
};
|
|
5200
|
+
const snapshotCacheCamera = (cache5) => ({
|
|
5201
|
+
camX: cacheCamX,
|
|
5202
|
+
camY: cacheCamY,
|
|
5203
|
+
camZ: cacheCamZ,
|
|
5204
|
+
widthDevicePx: cache5.canvas.width,
|
|
5205
|
+
heightDevicePx: cache5.canvas.height,
|
|
5206
|
+
dpr: cache5.dpr,
|
|
5207
|
+
marginCssPx: SCENE_CACHE_MARGIN_PX
|
|
5208
|
+
});
|
|
5209
|
+
const snapshotView = (camera) => ({
|
|
5210
|
+
camX: camera.x,
|
|
5211
|
+
camY: camera.y,
|
|
5212
|
+
camZ: camera.z,
|
|
5213
|
+
widthCssPx: staticSurface.cssWidth,
|
|
5214
|
+
heightCssPx: staticSurface.cssHeight
|
|
5215
|
+
});
|
|
5216
|
+
const presentStaticScaled = (camera) => {
|
|
5217
|
+
const cache5 = ensureCacheSurface();
|
|
5218
|
+
const w = staticSurface.canvas.width;
|
|
5219
|
+
const h = staticSurface.canvas.height;
|
|
5220
|
+
const { srcX, srcY, srcW, srcH } = computeCacheSourceRect(
|
|
5221
|
+
snapshotCacheCamera(cache5),
|
|
5222
|
+
snapshotView(camera)
|
|
5223
|
+
);
|
|
5224
|
+
staticSurface.ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
5225
|
+
staticSurface.ctx.clearRect(0, 0, w, h);
|
|
5226
|
+
staticSurface.ctx.drawImage(cache5.canvas, srcX, srcY, srcW, srcH, 0, 0, w, h);
|
|
5227
|
+
};
|
|
5228
|
+
const SCALED_BLIT_MAX_RATIO = 4;
|
|
5229
|
+
const SCALED_EXTEND_MIN_RATIO = 0.5;
|
|
5107
5230
|
const paintStatic = () => {
|
|
5108
5231
|
const camera = store.getCamera();
|
|
5109
5232
|
if (!cacheStale && camera.z === cacheCamZ) {
|
|
5110
5233
|
if (viewportFitsInCache(camera)) {
|
|
5111
5234
|
presentStatic(camera);
|
|
5235
|
+
lastDrawPath = "present";
|
|
5112
5236
|
return;
|
|
5113
5237
|
}
|
|
5114
5238
|
if (canExtend(camera)) {
|
|
5115
5239
|
extendCache(camera);
|
|
5116
5240
|
presentStatic(camera);
|
|
5241
|
+
lastDrawPath = "extend";
|
|
5242
|
+
return;
|
|
5243
|
+
}
|
|
5244
|
+
}
|
|
5245
|
+
if (!cacheStale && camera.z !== cacheCamZ && store.getInteractionState().mode === "zooming" && cacheSurface) {
|
|
5246
|
+
const cacheCam = snapshotCacheCamera(cacheSurface);
|
|
5247
|
+
const view = snapshotView(camera);
|
|
5248
|
+
if (scaleRatioInBounds(cacheCam.camZ, view.camZ, SCALED_BLIT_MAX_RATIO) && cacheCoversViewport(cacheCam, view)) {
|
|
5249
|
+
presentStaticScaled(camera);
|
|
5250
|
+
lastDrawPath = "scaled";
|
|
5117
5251
|
return;
|
|
5118
5252
|
}
|
|
5253
|
+
if (zoomExtendRatioInBounds(cacheCam.camZ, view.camZ, SCALED_EXTEND_MIN_RATIO)) {
|
|
5254
|
+
const layout = cacheReuseLayout(cacheCam, view);
|
|
5255
|
+
if (layout.valid) {
|
|
5256
|
+
extendCacheScaled(camera, layout);
|
|
5257
|
+
presentStatic(camera);
|
|
5258
|
+
lastDrawPath = "scaled-extend";
|
|
5259
|
+
return;
|
|
5260
|
+
}
|
|
5261
|
+
}
|
|
5119
5262
|
}
|
|
5120
5263
|
renderFullCache(camera);
|
|
5121
5264
|
presentStatic(camera);
|
|
5265
|
+
lastDrawPath = "full";
|
|
5122
5266
|
};
|
|
5123
5267
|
const paintCustomCanvasFallback = (ctx, node, def, drawScale, env) => {
|
|
5124
5268
|
if (def.getSnapshot) {
|
|
@@ -5441,9 +5585,11 @@ var createRenderer = (opts) => {
|
|
|
5441
5585
|
};
|
|
5442
5586
|
const onInteractionChange = (state) => {
|
|
5443
5587
|
interactiveDirty = true;
|
|
5444
|
-
if (state.mode === "dragging" || state.mode === "resizing" || state.mode === "rotating" || state.mode === "panning" || state.mode === "
|
|
5588
|
+
if (state.mode === "dragging" || state.mode === "resizing" || state.mode === "rotating" || state.mode === "panning" || state.mode === "idle") {
|
|
5445
5589
|
staticDirty = true;
|
|
5446
5590
|
cacheStale = true;
|
|
5591
|
+
} else if (state.mode === "zooming") {
|
|
5592
|
+
staticDirty = true;
|
|
5447
5593
|
}
|
|
5448
5594
|
loop.requestFrame();
|
|
5449
5595
|
};
|
|
@@ -5507,6 +5653,7 @@ var createRenderer = (opts) => {
|
|
|
5507
5653
|
},
|
|
5508
5654
|
stats: () => loop.stats(),
|
|
5509
5655
|
lastDrawCount: () => lastDrawn,
|
|
5656
|
+
getLastDrawPath: () => lastDrawPath,
|
|
5510
5657
|
getOverlaySet: () => [...overlaySet],
|
|
5511
5658
|
getAssetCache: () => assetCache,
|
|
5512
5659
|
dispose() {
|