mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-25 02:13:00 +08:00
refactor(editor): simplify renderer state (#10441)
The redundant `monitoring` state has been removed. The new `ready` state is used for querying if DOM elements can be safely optimized away.
This commit is contained in:
@@ -20,10 +20,8 @@ import { choose } from 'lit/directives/choose.js';
|
||||
import { repeat } from 'lit/directives/repeat.js';
|
||||
import { styleMap } from 'lit/directives/style-map.js';
|
||||
|
||||
import {
|
||||
type PageRootBlockComponent,
|
||||
RootBlockConfigExtension,
|
||||
} from '../../index.js';
|
||||
import type { PageRootBlockComponent } from '../../page/page-root-block.js';
|
||||
import { RootBlockConfigExtension } from '../../root-config.js';
|
||||
import {
|
||||
type AFFINE_LINKED_DOC_WIDGET,
|
||||
getMenus,
|
||||
|
||||
@@ -2,7 +2,11 @@ import { type Viewport } from '@blocksuite/block-std/gfx';
|
||||
import { Pane } from 'tweakpane';
|
||||
|
||||
import { getSentenceRects, segmentSentences } from './text-utils.js';
|
||||
import type { ParagraphLayout, ViewportLayout } from './types.js';
|
||||
import type {
|
||||
ParagraphLayout,
|
||||
RenderingState,
|
||||
ViewportLayout,
|
||||
} from './types.js';
|
||||
import type { ViewportTurboRendererExtension } from './viewport-renderer.js';
|
||||
|
||||
export function syncCanvasSize(canvas: HTMLCanvasElement, host: HTMLElement) {
|
||||
@@ -98,15 +102,15 @@ export function initTweakpane(
|
||||
paneElement.style.right = '10px';
|
||||
paneElement.style.width = '250px';
|
||||
debugPane.title = 'Viewport Turbo Renderer';
|
||||
|
||||
debugPane
|
||||
.addBinding({ paused: false }, 'paused', {
|
||||
label: 'Paused',
|
||||
})
|
||||
.on('change', ({ value }) => {
|
||||
renderer.state = value ? 'paused' : 'monitoring';
|
||||
});
|
||||
debugPane.addButton({ title: 'Invalidate' }).on('click', () => {
|
||||
renderer.invalidate();
|
||||
});
|
||||
}
|
||||
|
||||
export function debugLog(message: string, state: RenderingState) {
|
||||
console.log(
|
||||
`%c[ViewportTurboRenderer]%c ${message} | state=${state}`,
|
||||
'color: #4285f4; font-weight: bold;',
|
||||
'color: inherit;'
|
||||
);
|
||||
}
|
||||
|
||||
@@ -32,3 +32,12 @@ export interface TextRect {
|
||||
rect: Rect;
|
||||
text: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the rendering state of the ViewportTurboRenderer
|
||||
* - inactive: Renderer is not active
|
||||
* - pending: Bitmap is invalid or not yet available, falling back to DOM rendering
|
||||
* - rendering: Currently rendering to a bitmap (async operation in progress)
|
||||
* - ready: Bitmap is valid and rendered, DOM elements can be safely removed
|
||||
*/
|
||||
export type RenderingState = 'inactive' | 'pending' | 'rendering' | 'ready';
|
||||
|
||||
@@ -5,16 +5,17 @@ import {
|
||||
StdIdentifier,
|
||||
} from '@blocksuite/block-std';
|
||||
import { GfxControllerIdentifier } from '@blocksuite/block-std/gfx';
|
||||
import { type Container, type ServiceIdentifier } from '@blocksuite/global/di';
|
||||
import type { Container, ServiceIdentifier } from '@blocksuite/global/di';
|
||||
import { debounce, DisposableGroup } from '@blocksuite/global/utils';
|
||||
import { type Pane } from 'tweakpane';
|
||||
|
||||
import {
|
||||
debugLog,
|
||||
getViewportLayout,
|
||||
initTweakpane,
|
||||
syncCanvasSize,
|
||||
} from './dom-utils.js';
|
||||
import { type ViewportLayout } from './types.js';
|
||||
import type { RenderingState, ViewportLayout } from './types.js';
|
||||
|
||||
export const ViewportTurboRendererIdentifier = LifeCycleWatcherIdentifier(
|
||||
'ViewportTurboRenderer'
|
||||
@@ -29,9 +30,10 @@ interface Tile {
|
||||
const zoomThreshold = 1;
|
||||
|
||||
export class ViewportTurboRendererExtension extends LifeCycleWatcher {
|
||||
state: 'monitoring' | 'paused' = 'paused';
|
||||
state: RenderingState = 'inactive';
|
||||
disposables = new DisposableGroup();
|
||||
private layoutVersion = 0;
|
||||
private readonly debug = false; // Toggle for debug logs
|
||||
|
||||
static override setup(di: Container) {
|
||||
di.addImpl(ViewportTurboRendererIdentifier, this, [StdIdentifier]);
|
||||
@@ -48,6 +50,7 @@ export class ViewportTurboRendererExtension extends LifeCycleWatcher {
|
||||
this.worker = new Worker(new URL('./painter.worker.ts', import.meta.url), {
|
||||
type: 'module',
|
||||
});
|
||||
this.debugLog('Initialized ViewportTurboRenderer');
|
||||
}
|
||||
|
||||
override mounted() {
|
||||
@@ -59,7 +62,7 @@ export class ViewportTurboRendererExtension extends LifeCycleWatcher {
|
||||
|
||||
this.viewport.elementReady.once(() => {
|
||||
syncCanvasSize(this.canvas, this.std.host);
|
||||
this.state = 'monitoring';
|
||||
this.setState('pending');
|
||||
this.disposables.add(
|
||||
this.viewport.viewportUpdated.on(() => {
|
||||
this.refresh().catch(console.error);
|
||||
@@ -76,6 +79,7 @@ export class ViewportTurboRendererExtension extends LifeCycleWatcher {
|
||||
}
|
||||
|
||||
override unmounted() {
|
||||
this.debugLog('Unmounting renderer');
|
||||
this.clearTile();
|
||||
if (this.debugPane) {
|
||||
this.debugPane.dispose();
|
||||
@@ -84,6 +88,7 @@ export class ViewportTurboRendererExtension extends LifeCycleWatcher {
|
||||
this.worker.terminate();
|
||||
this.canvas.remove();
|
||||
this.disposables.dispose();
|
||||
this.setState('inactive');
|
||||
}
|
||||
|
||||
get viewport() {
|
||||
@@ -91,20 +96,26 @@ export class ViewportTurboRendererExtension extends LifeCycleWatcher {
|
||||
}
|
||||
|
||||
async refresh() {
|
||||
if (this.state === 'paused') return;
|
||||
if (this.state === 'inactive') return;
|
||||
|
||||
this.clearCanvas();
|
||||
if (this.viewport.zoom > zoomThreshold) {
|
||||
this.debugLog('Zoom above threshold, falling back to DOM rendering');
|
||||
this.setState('pending');
|
||||
return;
|
||||
} else if (this.canUseBitmapCache()) {
|
||||
this.debugLog('Using cached bitmap');
|
||||
this.drawCachedBitmap(this.layoutCache!);
|
||||
this.setState('ready');
|
||||
} else {
|
||||
if (!this.layoutCache) {
|
||||
this.updateLayoutCache();
|
||||
}
|
||||
const layout = this.layoutCache!;
|
||||
this.setState('rendering');
|
||||
await this.paintLayout(layout);
|
||||
this.drawCachedBitmap(layout);
|
||||
// State will be updated to 'ready' in handlePaintedBitmap if successful
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,17 +132,26 @@ export class ViewportTurboRendererExtension extends LifeCycleWatcher {
|
||||
this.layoutCache = null;
|
||||
this.clearTile();
|
||||
this.clearCanvas(); // Should clear immediately after content updates
|
||||
this.setState('pending');
|
||||
this.debugLog(`Invalidated renderer (layoutVersion=${this.layoutVersion})`);
|
||||
}
|
||||
|
||||
private debugLog(message: string): void {
|
||||
if (!this.debug) return;
|
||||
debugLog(message, this.state);
|
||||
}
|
||||
|
||||
private updateLayoutCache() {
|
||||
const layout = getViewportLayout(this.std.host, this.viewport);
|
||||
this.layoutCache = layout;
|
||||
this.debugLog('Layout cache updated');
|
||||
}
|
||||
|
||||
private clearTile() {
|
||||
if (this.tile) {
|
||||
this.tile.bitmap.close();
|
||||
this.tile = null;
|
||||
this.debugLog('Tile cleared');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,6 +162,7 @@ export class ViewportTurboRendererExtension extends LifeCycleWatcher {
|
||||
const dpr = window.devicePixelRatio;
|
||||
const currentVersion = this.layoutVersion;
|
||||
|
||||
this.debugLog(`Requesting bitmap painting (version=${currentVersion})`);
|
||||
this.worker.postMessage({
|
||||
type: 'paintLayout',
|
||||
data: {
|
||||
@@ -157,9 +178,16 @@ export class ViewportTurboRendererExtension extends LifeCycleWatcher {
|
||||
this.worker.onmessage = (e: MessageEvent) => {
|
||||
if (e.data.type === 'bitmapPainted') {
|
||||
if (e.data.version === this.layoutVersion) {
|
||||
this.debugLog(
|
||||
`Bitmap painted successfully (version=${e.data.version})`
|
||||
);
|
||||
this.handlePaintedBitmap(e.data.bitmap, resolve);
|
||||
} else {
|
||||
this.debugLog(
|
||||
`Received outdated bitmap (got=${e.data.version}, current=${this.layoutVersion})`
|
||||
);
|
||||
e.data.bitmap.close();
|
||||
this.setState('pending');
|
||||
resolve();
|
||||
}
|
||||
}
|
||||
@@ -175,6 +203,7 @@ export class ViewportTurboRendererExtension extends LifeCycleWatcher {
|
||||
bitmap,
|
||||
zoom: this.viewport.zoom,
|
||||
};
|
||||
this.setState('ready');
|
||||
resolve();
|
||||
}
|
||||
|
||||
@@ -188,10 +217,12 @@ export class ViewportTurboRendererExtension extends LifeCycleWatcher {
|
||||
const ctx = this.canvas.getContext('2d');
|
||||
if (!ctx) return;
|
||||
ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
||||
this.debugLog('Canvas cleared');
|
||||
}
|
||||
|
||||
private drawCachedBitmap(layout: ViewportLayout) {
|
||||
if (!this.tile) {
|
||||
this.debugLog('No cached bitmap available, requesting refresh');
|
||||
this.debouncedRefresh();
|
||||
return; // version mismatch
|
||||
}
|
||||
@@ -213,5 +244,20 @@ export class ViewportTurboRendererExtension extends LifeCycleWatcher {
|
||||
layout.rect.w * window.devicePixelRatio * this.viewport.zoom,
|
||||
layout.rect.h * window.devicePixelRatio * this.viewport.zoom
|
||||
);
|
||||
|
||||
this.debugLog('Bitmap drawn to canvas');
|
||||
}
|
||||
|
||||
setState(newState: RenderingState): void {
|
||||
if (this.state === newState) return;
|
||||
this.debugLog(`State change: ${this.state} -> ${newState}`);
|
||||
this.state = newState;
|
||||
}
|
||||
|
||||
canOptimizeDOM(): boolean {
|
||||
const isReady = this.state === 'ready';
|
||||
const isBelowZoomThreshold = this.viewport.zoom <= zoomThreshold;
|
||||
const result = isReady && isBelowZoomThreshold;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user