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:
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user