Skip to content

Commit

Permalink
perf: optimize frame preview performance (toeverything#6169)
Browse files Browse the repository at this point in the history
  • Loading branch information
donteatfriedrice authored Feb 1, 2024
1 parent 6fcb96f commit e3333a0
Show file tree
Hide file tree
Showing 4 changed files with 75 additions and 55 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import '../../../../surface-ref-block/surface-ref-portal.js';

import { DisposableGroup } from '@blocksuite/global/utils';
import { debounce, DisposableGroup } from '@blocksuite/global/utils';
import {
type EditorHost,
ShadowlessElement,
Expand All @@ -11,20 +11,21 @@ import { css, html, nothing, type PropertyValues } from 'lit';
import { customElement, property, query, state } from 'lit/decorators.js';
import { styleMap } from 'lit/directives/style-map.js';

import type {
EdgelessModel,
TopLevelBlockModel,
} from '../../../../_common/types.js';
import { buildPath } from '../../../../_common/utils/index.js';
import type { EdgelessModel } from '../../../../_common/types.js';
import type { FrameBlockModel } from '../../../../frame-block/frame-model.js';
import type { NoteBlockModel } from '../../../../note-block/note-model.js';
import type { SurfaceBlockModel } from '../../../../surface-block/surface-model.js';
import type {
ElementUpdatedData,
SurfaceBlockModel,
} from '../../../../surface-block/surface-model.js';
import type { SurfaceService } from '../../../../surface-block/surface-service.js';
import { Bound } from '../../../../surface-block/utils/bound.js';
import { deserializeXYWH } from '../../../../surface-block/utils/xywh.js';
import type { SurfaceRefPortal } from '../../../../surface-ref-block/surface-ref-portal.js';
import type { SurfaceRefRenderer } from '../../../../surface-ref-block/surface-ref-renderer.js';
import type { SurfaceRefBlockService } from '../../../../surface-ref-block/surface-ref-service.js';
import type { EdgelessPageBlockComponent } from '../../edgeless-page-block.js';
import { isTopLevelBlock } from '../../utils/query.js';

type RefElement = Exclude<EdgelessModel, NoteBlockModel>;

Expand Down Expand Up @@ -76,6 +77,9 @@ export class FramePreview extends WithDisposable(ShadowlessElement) {
@property({ attribute: false })
frame!: FrameBlockModel;

@property({ attribute: false })
page!: Page;

@property({ attribute: false })
host!: EditorHost;

Expand Down Expand Up @@ -104,10 +108,6 @@ export class FramePreview extends WithDisposable(ShadowlessElement) {
@query('.frame-preview-surface-container surface-ref-portal')
blocksPortal!: SurfaceRefPortal;

get page() {
return this.host.page;
}

get surfaceRenderer() {
return this._surfaceRefRenderer.surfaceRenderer;
}
Expand Down Expand Up @@ -136,6 +136,10 @@ export class FramePreview extends WithDisposable(ShadowlessElement) {
}
}

private get _surfaceService() {
return this.host?.std.spec.getService('affine:surface') as SurfaceService;
}

private get _surfaceRefService() {
const service = this.host.spec.getService('affine:surface-ref') as
| SurfaceRefBlockService
Expand Down Expand Up @@ -185,7 +189,7 @@ export class FramePreview extends WithDisposable(ShadowlessElement) {
}

private _refreshViewport() {
if (!this.frame) {
if (!this.frame || !this._surfaceService) {
return;
}

Expand Down Expand Up @@ -228,17 +232,36 @@ export class FramePreview extends WithDisposable(ShadowlessElement) {
};
};

private _updateOnElementChange = (element: string | { id: string }) => {
const id = typeof element === 'string' ? element : element.id;
private _overlapWithFrame = (id: string) => {
const ele = this.edgeless?.service.getElementById(id);
if (!ele || !ele.xywh) return;
if (!ele || !ele.xywh) return false;

const frameBound = Bound.deserialize(this.frame.xywh);
const eleBound = Bound.deserialize(ele.xywh);
if (!frameBound.isOverlapWithBound(eleBound)) return;
return frameBound.isOverlapWithBound(eleBound);
};

this._refreshViewport();
private _handleElementUpdated = (data: ElementUpdatedData) => {
const { id, oldValues, props } = data;
if (!props.xywh) return;
// if element is moved in frame, refresh viewport
if (this._overlapWithFrame(id)) {
this._refreshViewport();
} else if (oldValues.xywh) {
// if element is moved out of frame, refresh viewport
const oldBound = Bound.deserialize(oldValues.xywh as string);
const frameBound = Bound.deserialize(this.frame.xywh);
if (oldBound.isOverlapWithBound(frameBound)) {
this._refreshViewport();
}
}
};

private _debounceHandleElementUpdated = debounce(
this._handleElementUpdated,
1000 / 30
);

private _clearEdgelessDisposables = () => {
this._edgelessDisposables?.dispose();
this._edgelessDisposables = null;
Expand Down Expand Up @@ -267,15 +290,19 @@ export class FramePreview extends WithDisposable(ShadowlessElement) {
})
);
this._edgelessDisposables.add(
edgeless.service.surface.elementAdded.on(this._updateOnElementChange)
edgeless.service.surface.elementAdded.on(({ id }) => {
if (this._overlapWithFrame(id)) {
this._refreshViewport();
}
})
);
this._edgelessDisposables.add(
edgeless.service.surface.elementUpdated.on(this._updateOnElementChange)
edgeless.service.surface.elementUpdated.on(
this._debounceHandleElementUpdated
)
);
this._edgelessDisposables.add(
edgeless.service.surface.elementRemoved.on(() => {
this._refreshViewport();
})
edgeless.service.surface.elementRemoved.on(() => this._refreshViewport())
);
}

Expand All @@ -285,32 +312,18 @@ export class FramePreview extends WithDisposable(ShadowlessElement) {

this._pageDisposables.add(
page.slots.blockUpdated.on(event => {
const { type, flavour } = event;
const isTopLevelBlock = ['affine:image', 'affine:note'].includes(
flavour
);
if (!isTopLevelBlock) return;
const { type } = event;
// Should only check for add and delete events, the update event will be handled by the surface renderer
if (type === 'update') return;

const model =
type === 'delete' ? event.model : page.getBlockById(event.id);
if (!model || !isTopLevelBlock(model) || !model.xywh) return;

const frameBound = Bound.deserialize(this.frame.xywh);
if (type === 'delete') {
const deleteModel = event.model as TopLevelBlockModel;
const deleteBound = Bound.deserialize(deleteModel.xywh);
if (frameBound.containsPoint([deleteBound.x, deleteBound.y])) {
this._refreshViewport();
}
} else {
const topLevelModel = page.getBlockById(event.id);
const topLevelBlock = this.host.view.viewFromPath(
'block',
buildPath(topLevelModel)
);
if (!topLevelBlock) return;
const newBound = Bound.deserialize(
(topLevelModel as TopLevelBlockModel).xywh
);
if (frameBound.containsPoint([newBound.x, newBound.y])) {
this._refreshViewport();
}
const modelBound = Bound.deserialize(model.xywh);
if (frameBound.containsPoint([modelBound.x, modelBound.y])) {
this._refreshViewport();
}
})
);
Expand Down Expand Up @@ -386,9 +399,12 @@ export class FramePreview extends WithDisposable(ShadowlessElement) {
} else {
this._clearEdgelessDisposables();
}
setTimeout(() => {
this._refreshViewport();
});
}

if (_changedProperties.has('host')) {
if (_changedProperties.has('page')) {
if (this.page) {
this._setPageDisposables(this.page);
}
Expand All @@ -406,8 +422,9 @@ export class FramePreview extends WithDisposable(ShadowlessElement) {
}

override render() {
const { _surfaceModel, frame, host } = this;
const noContent = !_surfaceModel || !frame || !frame.xywh || !host;
const { _surfaceModel, frame, host, _surfaceService } = this;
const noContent =
!_surfaceModel || !frame || !frame.xywh || !host || !_surfaceService;

return html`<div class="frame-preview-container">
${noContent ? nothing : this._renderSurfaceContent(frame)}
Expand Down
12 changes: 7 additions & 5 deletions packages/blocks/src/surface-block/surface-model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@ export type SurfaceBlockProps = {
elements: Boxed<Y.Map<Y.Map<unknown>>>;
};

export interface ElementUpdatedData {
id: string;
props: Record<string, unknown>;
oldValues: Record<string, unknown>;
}

const migration = {
toV4: data => {
const { elements } = data;
Expand Down Expand Up @@ -167,11 +173,7 @@ export class SurfaceBlockModel extends BlockModel<SurfaceBlockProps> {
private _connectorToElements: Map<string, string[]> = new Map();
private _elementToConnector: Map<string, string[]> = new Map();

elementUpdated = new Slot<{
id: string;
props: Record<string, unknown>;
oldValues: Record<string, unknown>;
}>();
elementUpdated = new Slot<ElementUpdatedData>();
elementAdded = new Slot<{ id: string }>();
elementRemoved = new Slot<{
id: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,7 @@ export class FramePanelBody extends WithDisposable(ShadowlessElement) {
const selectedFrames = new Set(this._selected);
const frameCards = html`${repeat(
this._frameItems,
frameItem => frameItem.frame.id,
frameItem => [frameItem.frame.id, frameItem.cardIndex].join('-'),
frameItem => {
const { frame, frameIndex, cardIndex } = frameItem;
return html`<frame-card
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,7 @@ export class FrameCard extends WithDisposable(ShadowlessElement) {
? nothing
: html`<frame-preview
.edgeless=${this.edgeless}
.page=${this.page}
.host=${this.host}
.frame=${this.frame}
></frame-preview>`}
Expand Down

4 comments on commit e3333a0

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Size Report

Bundles

Entry Size Gzip Brotli
examples/basic 13.4 MB (+19.9 kB) 2.8 MB (+4.16 kB) 1.74 MB (+3.2 kB)

Packages

Name Size Gzip Brotli
blocks 2.1 MB (+2.22 kB) 496 kB (+391 B) 362 kB (+167 B)
editor 84 B 89 B 63 B
store 83 B 88 B 63 B
inline 84 B 88 B 63 B

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Size Report

Bundles

Entry Size Gzip Brotli
examples/basic 13.4 MB 2.8 MB 1.74 MB

Packages

Name Size Gzip Brotli
blocks 2.1 MB 496 kB 362 kB
editor 84 B 89 B 63 B
store 83 B 88 B 63 B
inline 84 B 88 B 63 B

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Size Report

Bundles

Entry Size Gzip Brotli
examples/basic 13.4 MB 2.8 MB 1.74 MB

Packages

Name Size Gzip Brotli
blocks 2.1 MB 496 kB 362 kB
editor 84 B 89 B 63 B
store 83 B 88 B 63 B
inline 84 B 88 B 63 B

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Size Report

Bundles

Entry Size Gzip Brotli
examples/basic 13.4 MB 2.8 MB 1.74 MB

Packages

Name Size Gzip Brotli
blocks 2.1 MB 496 kB 362 kB
editor 84 B 89 B 63 B
store 83 B 88 B 63 B
inline 84 B 88 B 63 B

Please sign in to comment.