fix(editor): discard stale layout bitmap in turbo renderer (#10427)

Fixes this bug caused by stale bitmap:

[Screen Recording 2025-02-24 at 6.10.19 PM.mov <span class="graphite__hidden">(uploaded via Graphite)</span> <img class="graphite__hidden" src="https://app.graphite.dev/api/v1/graphite/video/thumbnail/lEGcysB4lFTEbCwZ8jMv/3e24f4b7-6f95-4c7c-a79a-b8e4ffdb3b10.mov" />](https://app.graphite.dev/media/video/lEGcysB4lFTEbCwZ8jMv/3e24f4b7-6f95-4c7c-a79a-b8e4ffdb3b10.mov)
This commit is contained in:
doodlewind
2025-02-25 10:51:55 +00:00
parent 842c39c3be
commit f3911b1b5e
2 changed files with 22 additions and 6 deletions

View File

@@ -8,6 +8,7 @@ type WorkerMessagePaint = {
height: number; height: number;
dpr: number; dpr: number;
zoom: number; zoom: number;
version: number;
}; };
}; };
@@ -63,7 +64,7 @@ class LayoutPainter {
this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height); this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
} }
paint(layout: ViewportLayout) { paint(layout: ViewportLayout, version: number) {
const { canvas, ctx } = this; const { canvas, ctx } = this;
if (!canvas || !ctx) return; if (!canvas || !ctx) return;
if (layout.rect.w === 0 || layout.rect.h === 0) { if (layout.rect.w === 0 || layout.rect.h === 0) {
@@ -103,7 +104,10 @@ class LayoutPainter {
}); });
const bitmap = canvas.transferToImageBitmap(); const bitmap = canvas.transferToImageBitmap();
self.postMessage({ type: 'bitmapPainted', bitmap }, { transfer: [bitmap] }); self.postMessage(
{ type: 'bitmapPainted', bitmap, version },
{ transfer: [bitmap] }
);
} }
} }
@@ -127,9 +131,9 @@ self.onmessage = async (e: MessageEvent<WorkerMessage>) => {
switch (type) { switch (type) {
case 'paintLayout': { case 'paintLayout': {
const { layout, width, height, dpr, zoom } = data; const { layout, width, height, dpr, zoom, version } = data;
painter.setSize(width, height, dpr, zoom); painter.setSize(width, height, dpr, zoom);
painter.paint(layout); painter.paint(layout, version);
break; break;
} }
} }

View File

@@ -31,6 +31,7 @@ const zoomThreshold = 1;
export class ViewportTurboRendererExtension extends LifeCycleWatcher { export class ViewportTurboRendererExtension extends LifeCycleWatcher {
state: 'monitoring' | 'paused' = 'paused'; state: 'monitoring' | 'paused' = 'paused';
disposables = new DisposableGroup(); disposables = new DisposableGroup();
private layoutVersion = 0;
static override setup(di: Container) { static override setup(di: Container) {
di.addImpl(ViewportTurboRendererIdentifier, this, [StdIdentifier]); di.addImpl(ViewportTurboRendererIdentifier, this, [StdIdentifier]);
@@ -115,6 +116,7 @@ export class ViewportTurboRendererExtension extends LifeCycleWatcher {
} }
invalidate() { invalidate() {
this.layoutVersion++;
this.layoutCache = null; this.layoutCache = null;
this.clearTile(); this.clearTile();
this.clearCanvas(); // Should clear immediately after content updates this.clearCanvas(); // Should clear immediately after content updates
@@ -137,6 +139,8 @@ export class ViewportTurboRendererExtension extends LifeCycleWatcher {
if (!this.worker) return; if (!this.worker) return;
const dpr = window.devicePixelRatio; const dpr = window.devicePixelRatio;
const currentVersion = this.layoutVersion;
this.worker.postMessage({ this.worker.postMessage({
type: 'paintLayout', type: 'paintLayout',
data: { data: {
@@ -145,12 +149,18 @@ export class ViewportTurboRendererExtension extends LifeCycleWatcher {
height: layout.rect.h, height: layout.rect.h,
dpr, dpr,
zoom: this.viewport.zoom, zoom: this.viewport.zoom,
version: currentVersion,
}, },
}); });
this.worker.onmessage = (e: MessageEvent) => { this.worker.onmessage = (e: MessageEvent) => {
if (e.data.type === 'bitmapPainted') { if (e.data.type === 'bitmapPainted') {
this.handlePaintedBitmap(e.data.bitmap, resolve); if (e.data.version === this.layoutVersion) {
this.handlePaintedBitmap(e.data.bitmap, resolve);
} else {
e.data.bitmap.close();
resolve();
}
} }
}; };
}); });
@@ -180,7 +190,9 @@ export class ViewportTurboRendererExtension extends LifeCycleWatcher {
} }
private drawCachedBitmap(layout: ViewportLayout) { private drawCachedBitmap(layout: ViewportLayout) {
const bitmap = this.tile!.bitmap; if (!this.tile) return; // version mismatch
const bitmap = this.tile.bitmap;
const ctx = this.canvas.getContext('2d'); const ctx = this.canvas.getContext('2d');
if (!ctx) return; if (!ctx) return;