@canvas-harness/core 0.1.20 → 0.1.21
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 +149 -4
- 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 +149 -4
- 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;
|
|
@@ -4905,9 +4959,8 @@ var createRenderer = (opts) => {
|
|
|
4905
4959
|
theme: (token) => theme ? theme(token) : void 0
|
|
4906
4960
|
};
|
|
4907
4961
|
const editingNodeId = interaction.editingTarget?.kind === "node" ? interaction.editingTarget.id : null;
|
|
4908
|
-
const cameraIsMoving = interaction.mode === "panning" || interaction.mode === "zooming";
|
|
4909
4962
|
const movingNodeCount = excludedNodes?.size ?? 0;
|
|
4910
|
-
const roughEnabled =
|
|
4963
|
+
const roughEnabled = movingNodeCount <= ROUGH_MAX_MOVING_NODES && camera.z >= ROUGH_MIN_ZOOM && visible.length <= ROUGH_MAX_NODES;
|
|
4911
4964
|
if (!hideFrames) {
|
|
4912
4965
|
for (const node of visible) {
|
|
4913
4966
|
if (node.type !== "frame") continue;
|
|
@@ -5003,7 +5056,7 @@ var createRenderer = (opts) => {
|
|
|
5003
5056
|
}
|
|
5004
5057
|
}
|
|
5005
5058
|
const visEdges = visibleEdges(viewport);
|
|
5006
|
-
const edgeRoughEnabled =
|
|
5059
|
+
const edgeRoughEnabled = movingNodeCount <= ROUGH_MAX_MOVING_NODES && camera.z >= ROUGH_MIN_ZOOM && visEdges.length <= ROUGH_MAX_NODES;
|
|
5007
5060
|
for (const edge of visEdges) {
|
|
5008
5061
|
if (excludedEdges?.has(edge.id)) continue;
|
|
5009
5062
|
paintOneEdge(surface.ctx, edge, scale, edgeRoughEnabled, camera.z, isMoving2);
|
|
@@ -5083,6 +5136,44 @@ var createRenderer = (opts) => {
|
|
|
5083
5136
|
if (hw > 0) renderCacheStrip(cache5, newCamX, newCamY, camera.z, hx, 0, hw, cacheH);
|
|
5084
5137
|
if (vh > 0 && vw > 0) renderCacheStrip(cache5, newCamX, newCamY, camera.z, vx, vy, vw, vh);
|
|
5085
5138
|
};
|
|
5139
|
+
let scaledExtendScratch = null;
|
|
5140
|
+
const extendCacheScaled = (camera, layout) => {
|
|
5141
|
+
const cache5 = ensureCacheSurface();
|
|
5142
|
+
const cacheW = cache5.canvas.width;
|
|
5143
|
+
const cacheH = cache5.canvas.height;
|
|
5144
|
+
if (!scaledExtendScratch || scaledExtendScratch.width !== cacheW || scaledExtendScratch.height !== cacheH) {
|
|
5145
|
+
scaledExtendScratch = document.createElement("canvas");
|
|
5146
|
+
scaledExtendScratch.width = cacheW;
|
|
5147
|
+
scaledExtendScratch.height = cacheH;
|
|
5148
|
+
}
|
|
5149
|
+
const sctx = scaledExtendScratch.getContext("2d");
|
|
5150
|
+
sctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
5151
|
+
sctx.clearRect(0, 0, cacheW, cacheH);
|
|
5152
|
+
sctx.drawImage(cache5.canvas, 0, 0);
|
|
5153
|
+
cache5.ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
5154
|
+
cache5.ctx.clearRect(0, 0, cacheW, cacheH);
|
|
5155
|
+
cache5.ctx.drawImage(
|
|
5156
|
+
scaledExtendScratch,
|
|
5157
|
+
layout.dest.x,
|
|
5158
|
+
layout.dest.y,
|
|
5159
|
+
layout.dest.w,
|
|
5160
|
+
layout.dest.h
|
|
5161
|
+
);
|
|
5162
|
+
cacheCamX = camera.x;
|
|
5163
|
+
cacheCamY = camera.y;
|
|
5164
|
+
cacheCamZ = camera.z;
|
|
5165
|
+
const strips = [
|
|
5166
|
+
layout.strips.top,
|
|
5167
|
+
layout.strips.bottom,
|
|
5168
|
+
layout.strips.left,
|
|
5169
|
+
layout.strips.right
|
|
5170
|
+
];
|
|
5171
|
+
for (const s of strips) {
|
|
5172
|
+
if (s.w > 0 && s.h > 0) {
|
|
5173
|
+
renderCacheStrip(cache5, camera.x, camera.y, camera.z, s.x, s.y, s.w, s.h);
|
|
5174
|
+
}
|
|
5175
|
+
}
|
|
5176
|
+
};
|
|
5086
5177
|
const cacheSourceOffset = (camera) => {
|
|
5087
5178
|
const dpr = staticSurface.dpr;
|
|
5088
5179
|
return {
|
|
@@ -5104,21 +5195,72 @@ var createRenderer = (opts) => {
|
|
|
5104
5195
|
staticSurface.ctx.clearRect(0, 0, w, h);
|
|
5105
5196
|
staticSurface.ctx.drawImage(cache5.canvas, srcX, srcY, w, h, 0, 0, w, h);
|
|
5106
5197
|
};
|
|
5198
|
+
const snapshotCacheCamera = (cache5) => ({
|
|
5199
|
+
camX: cacheCamX,
|
|
5200
|
+
camY: cacheCamY,
|
|
5201
|
+
camZ: cacheCamZ,
|
|
5202
|
+
widthDevicePx: cache5.canvas.width,
|
|
5203
|
+
heightDevicePx: cache5.canvas.height,
|
|
5204
|
+
dpr: cache5.dpr,
|
|
5205
|
+
marginCssPx: SCENE_CACHE_MARGIN_PX
|
|
5206
|
+
});
|
|
5207
|
+
const snapshotView = (camera) => ({
|
|
5208
|
+
camX: camera.x,
|
|
5209
|
+
camY: camera.y,
|
|
5210
|
+
camZ: camera.z,
|
|
5211
|
+
widthCssPx: staticSurface.cssWidth,
|
|
5212
|
+
heightCssPx: staticSurface.cssHeight
|
|
5213
|
+
});
|
|
5214
|
+
const presentStaticScaled = (camera) => {
|
|
5215
|
+
const cache5 = ensureCacheSurface();
|
|
5216
|
+
const w = staticSurface.canvas.width;
|
|
5217
|
+
const h = staticSurface.canvas.height;
|
|
5218
|
+
const { srcX, srcY, srcW, srcH } = computeCacheSourceRect(
|
|
5219
|
+
snapshotCacheCamera(cache5),
|
|
5220
|
+
snapshotView(camera)
|
|
5221
|
+
);
|
|
5222
|
+
staticSurface.ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
5223
|
+
staticSurface.ctx.clearRect(0, 0, w, h);
|
|
5224
|
+
staticSurface.ctx.drawImage(cache5.canvas, srcX, srcY, srcW, srcH, 0, 0, w, h);
|
|
5225
|
+
};
|
|
5226
|
+
const SCALED_BLIT_MAX_RATIO = 4;
|
|
5227
|
+
const SCALED_EXTEND_MIN_RATIO = 0.5;
|
|
5107
5228
|
const paintStatic = () => {
|
|
5108
5229
|
const camera = store.getCamera();
|
|
5109
5230
|
if (!cacheStale && camera.z === cacheCamZ) {
|
|
5110
5231
|
if (viewportFitsInCache(camera)) {
|
|
5111
5232
|
presentStatic(camera);
|
|
5233
|
+
lastDrawPath = "present";
|
|
5112
5234
|
return;
|
|
5113
5235
|
}
|
|
5114
5236
|
if (canExtend(camera)) {
|
|
5115
5237
|
extendCache(camera);
|
|
5116
5238
|
presentStatic(camera);
|
|
5239
|
+
lastDrawPath = "extend";
|
|
5240
|
+
return;
|
|
5241
|
+
}
|
|
5242
|
+
}
|
|
5243
|
+
if (!cacheStale && camera.z !== cacheCamZ && store.getInteractionState().mode === "zooming" && cacheSurface) {
|
|
5244
|
+
const cacheCam = snapshotCacheCamera(cacheSurface);
|
|
5245
|
+
const view = snapshotView(camera);
|
|
5246
|
+
if (scaleRatioInBounds(cacheCam.camZ, view.camZ, SCALED_BLIT_MAX_RATIO) && cacheCoversViewport(cacheCam, view)) {
|
|
5247
|
+
presentStaticScaled(camera);
|
|
5248
|
+
lastDrawPath = "scaled";
|
|
5117
5249
|
return;
|
|
5118
5250
|
}
|
|
5251
|
+
if (zoomExtendRatioInBounds(cacheCam.camZ, view.camZ, SCALED_EXTEND_MIN_RATIO)) {
|
|
5252
|
+
const layout = cacheReuseLayout(cacheCam, view);
|
|
5253
|
+
if (layout.valid) {
|
|
5254
|
+
extendCacheScaled(camera, layout);
|
|
5255
|
+
presentStatic(camera);
|
|
5256
|
+
lastDrawPath = "scaled-extend";
|
|
5257
|
+
return;
|
|
5258
|
+
}
|
|
5259
|
+
}
|
|
5119
5260
|
}
|
|
5120
5261
|
renderFullCache(camera);
|
|
5121
5262
|
presentStatic(camera);
|
|
5263
|
+
lastDrawPath = "full";
|
|
5122
5264
|
};
|
|
5123
5265
|
const paintCustomCanvasFallback = (ctx, node, def, drawScale, env) => {
|
|
5124
5266
|
if (def.getSnapshot) {
|
|
@@ -5441,9 +5583,11 @@ var createRenderer = (opts) => {
|
|
|
5441
5583
|
};
|
|
5442
5584
|
const onInteractionChange = (state) => {
|
|
5443
5585
|
interactiveDirty = true;
|
|
5444
|
-
if (state.mode === "dragging" || state.mode === "resizing" || state.mode === "rotating" || state.mode === "panning" || state.mode === "
|
|
5586
|
+
if (state.mode === "dragging" || state.mode === "resizing" || state.mode === "rotating" || state.mode === "panning" || state.mode === "idle") {
|
|
5445
5587
|
staticDirty = true;
|
|
5446
5588
|
cacheStale = true;
|
|
5589
|
+
} else if (state.mode === "zooming") {
|
|
5590
|
+
staticDirty = true;
|
|
5447
5591
|
}
|
|
5448
5592
|
loop.requestFrame();
|
|
5449
5593
|
};
|
|
@@ -5507,6 +5651,7 @@ var createRenderer = (opts) => {
|
|
|
5507
5651
|
},
|
|
5508
5652
|
stats: () => loop.stats(),
|
|
5509
5653
|
lastDrawCount: () => lastDrawn,
|
|
5654
|
+
getLastDrawPath: () => lastDrawPath,
|
|
5510
5655
|
getOverlaySet: () => [...overlaySet],
|
|
5511
5656
|
getAssetCache: () => assetCache,
|
|
5512
5657
|
dispose() {
|