feat(editor): replace flat layout cache with tree in turbo renderer (#11319)

### TL;DR

Refactored the BlockSuite turbo renderer to use a hierarchical tree structure for layouts instead of a flat list, improving rendering accuracy and performance.

### What changed?

- Redesigned the layout system to use a tree structure (`ViewportLayoutTree`) that better represents the document hierarchy
- Added `blockId` to all layout objects for better tracking and debugging
- Updated the layout query mechanism to work with models directly instead of components
- Enhanced error handling with more descriptive warnings and error messages
- Improved the painting process to traverse the layout tree recursively
- Fixed viewport coordinate calculations for more accurate rendering
- Updated the worker communication to support the new tree-based layout structure

### Why make this change?

The previous flat layout structure didn't properly represent the hierarchical nature of documents, leading to rendering issues with nested blocks. This tree-based approach:

1. Better represents the actual document structure
2. Improves rendering accuracy for nested elements
3. Makes debugging easier with more consistent block identification
4. Provides a more robust foundation for future rendering optimizations
5. Reduces the likelihood of rendering artifacts when scrolling or zooming
This commit is contained in:
doodlewind
2025-04-10 08:49:23 +00:00
parent b8e93ed714
commit f85b35227b
10 changed files with 244 additions and 158 deletions

View File

@@ -6,8 +6,9 @@ import {
segmentSentences,
} from '@blocksuite/affine-gfx-turbo-renderer';
import type { Container } from '@blocksuite/global/di';
import type { GfxBlockComponent } from '@blocksuite/std';
import { clientToModelCoord } from '@blocksuite/std/gfx';
import type { EditorHost, GfxBlockComponent } from '@blocksuite/std';
import { clientToModelCoord, type ViewportRecord } from '@blocksuite/std/gfx';
import type { BlockModel } from '@blocksuite/store';
import type { ListLayout } from './list-painter.worker';
@@ -21,24 +22,27 @@ export class ListLayoutHandlerExtension extends BlockLayoutHandlerExtension<List
);
}
queryLayout(component: GfxBlockComponent): ListLayout | null {
// Select all list items within this list block
override queryLayout(
model: BlockModel,
host: EditorHost,
viewportRecord: ViewportRecord
): ListLayout | null {
const component = host.std.view.getBlock(model.id) as GfxBlockComponent;
if (!component) return null;
// Find the list items within this specific list component
const listItemSelector =
'.affine-list-block-container .affine-list-rich-text-wrapper [data-v-text="true"]';
const listItemNodes = component.querySelectorAll(listItemSelector);
if (listItemNodes.length === 0) return null;
const viewportRecord = component.gfx.viewport.deserializeRecord(
component.dataset.viewportState
);
if (!viewportRecord) return null;
const { zoom, viewScale } = viewportRecord;
const list: ListLayout = {
type: 'affine:list',
items: [],
blockId: model.id,
rect: { x: 0, y: 0, w: 0, h: 0 },
};
listItemNodes.forEach(listItemNode => {

View File

@@ -77,10 +77,15 @@ class ListLayoutPainter implements BlockLayoutPainter {
return;
}
if (!isListLayout(layout)) return;
if (!isListLayout(layout)) {
console.warn(
'Expected list layout but received different format:',
layout
);
return;
}
const renderedPositions = new Set<string>();
layout.items.forEach(item => {
const fontSize = item.fontSize;
const baselineY = getBaseline(fontSize);