fix(renderer): per-node catch restores canvas save stack
Codex stop-hook on the prior per-node try/catch caught a leak: the catch logged but didn't roll back canvas state. drawNode pushes canvas.save() once per ancestor clipStack entry (node-renderer.ts:548) plus more for rotation / flip (574, 583) and per-shape sub-paths (701, 1094, 1102). If drawNode throws mid-loop, every save() between its entry and the throw stays on the stack — the next node's draw operates inside a leaked clip / leaked transform, and the canvas either renders nothing or renders to the wrong region. Snapshot canvas.getSaveCount() before each drawNode call; on catch, canvas.restoreToCount(saveCount) pops everything back to the baseline. Wrap the restoreToCount itself in a no-op catch since it can throw if the snapshot count is somehow above the current depth (shouldn't happen but guarded so the error reporter still runs). Net effect: per-node failures are now genuinely isolated. The canvas state at the start of each iteration is identical to where the previous iteration left it; one bad node can't smear its leaked state across the rest of the frame.
This commit is contained in:
parent
138df33309
commit
dd231d4ef2
|
|
@ -330,10 +330,28 @@ export class PenRenderer {
|
|||
// shadow omitted `spread` — the throw escaped the loop and every
|
||||
// sibling stopped rendering. The shadow path now coerces missing
|
||||
// numeric fields to 0; this catch is the structural backstop.
|
||||
//
|
||||
// Snapshot the canvas save count before each node so a throw mid-
|
||||
// way through drawNode (which pushes save() / clip() per ancestor
|
||||
// clipStack entry, plus more for rotation / flip — see node-
|
||||
// renderer.ts:548, 574, 583, 701, 1094, 1102) can roll the canvas
|
||||
// state back to a clean baseline. Without this, the leaked clip /
|
||||
// rotation state corrupts every subsequent node's draw — Codex
|
||||
// stop-hook 2026-05-10 caught the bare-catch leak.
|
||||
for (const rn of this.renderNodes) {
|
||||
const saveCount = canvas.getSaveCount();
|
||||
try {
|
||||
this.nodeRenderer.drawNode(canvas, rn);
|
||||
} catch (err) {
|
||||
try {
|
||||
canvas.restoreToCount(saveCount);
|
||||
} catch {
|
||||
// restoreToCount itself can throw if the snapshot count is
|
||||
// somehow above the current depth (shouldn't happen in
|
||||
// practice — drawNode only pushes saves, never pops below
|
||||
// its entry depth — but guard so the error reporter still
|
||||
// logs).
|
||||
}
|
||||
const id = rn.node.id ?? '<no-id>';
|
||||
console.error(`[pen-renderer] drawNode threw for node ${id}:`, err);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue