Skip to content

Commit

Permalink
optimize: incremental render (#79)
Browse files Browse the repository at this point in the history
* feat: add render option

* fix: lint error
  • Loading branch information
vincentdchan authored Nov 20, 2023
1 parent db7ebb2 commit a5ddee4
Show file tree
Hide file tree
Showing 4 changed files with 127 additions and 47 deletions.
3 changes: 2 additions & 1 deletion packages/blocky-core/src/block/basic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import Delta from "quill-delta-es";
import { Subject } from "rxjs";
import { type Editor } from "@pkg/view/editor";
import { type EditorController } from "@pkg/view/controller";
import { RenderOption } from "@pkg/view/renderer";

export interface BlockDidMountEvent {
element: HTMLElement;
Expand Down Expand Up @@ -198,7 +199,7 @@ export class Block implements IDisposable {

blockContentChanged?(e: BlockContentChangedEvent): void;

render?(container: HTMLElement): void;
render?(container: HTMLElement, option: RenderOption): void;

/**
* If the block wants the renderer to render children of
Expand Down
87 changes: 47 additions & 40 deletions packages/blocky-core/src/view/editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
isString,
isNumber,
} from "lodash-es";
import { DocRenderer } from "@pkg/view/renderer";
import { DocRenderer, RenderFlag, RenderOption } from "@pkg/view/renderer";
import { EditorState, SearchContext } from "@pkg/model";
import {
type CursorStateUpdateEvent,
Expand Down Expand Up @@ -410,9 +410,18 @@ export class Editor {
}, 15);
}

render(done?: AfterFn) {
fullRender(done?: AfterFn) {
this.render(
{
flags: RenderFlag.Full,
},
done
);
}

render(option: RenderOption, done?: AfterFn) {
try {
const newDom = this.#renderer.render(this.#renderedDom);
const newDom = this.#renderer.render(option, this.#renderedDom);
if (!this.#renderedDom) {
this.#container.appendChild(newDom);
newDom.contentEditable = "true";
Expand Down Expand Up @@ -732,12 +741,17 @@ export class Editor {
this.#handleSelectionChanged(CursorStateUpdateReason.contentChanged);
const storedCursorState = this.state.cursorState;
this.state.__setCursorState(null, CursorStateUpdateReason.changeset);
this.render(() => {
this.state.__setCursorState(
storedCursorState,
CursorStateUpdateReason.changeset
);
});
this.render(
{
flags: RenderFlag.Full,
},
() => {
this.state.__setCursorState(
storedCursorState,
CursorStateUpdateReason.changeset
);
}
);
}

#handleContentChanged = () => {
Expand Down Expand Up @@ -785,23 +799,10 @@ export class Editor {
*/
#rerenderEmptyLines(changeset: FinalizedChangeset) {
for (const op of changeset.operations) {
if (op.op === "text-edit") {
const blockElement = this.state.getBlockElementById(op.id);
if (!blockElement) {
// has been deleted?
continue;
}
if (blockElement.t === TextBlock.Name) {
const textModel = blockElement.getTextModel("textContent")!;
if (textModel.length === 0) {
const block = this.state.blocks.get(op.id);
const dom = this.state.domMap.get(op.id);
if (block && dom) {
block.render?.(dom as HTMLElement);
}
}
}
if (op.op !== "text-edit") {
continue;
}
this.#renderer.renderTextBlockByOperation(changeset, op);
}
}

Expand Down Expand Up @@ -1088,22 +1089,28 @@ export class Editor {
const needsRender = options.updateView || changeset.forceUpdate;
changeset.needsRender = needsRender;
if (needsRender) {
this.render(() => {
if (!isThisUser) {
return;
}
if (!isUndefined(changeset.afterCursor)) {
this.state.__setCursorState(
changeset.afterCursor,
CursorStateUpdateReason.changeset
);
} else if (options.refreshCursor) {
this.state.__setCursorState(
changeset.beforeCursor,
CursorStateUpdateReason.changeset
);
this.render(
{
changeset,
flags: RenderFlag.Incremental,
},
() => {
if (!isThisUser) {
return;
}
if (!isUndefined(changeset.afterCursor)) {
this.state.__setCursorState(
changeset.afterCursor,
CursorStateUpdateReason.changeset
);
} else if (options.refreshCursor) {
this.state.__setCursorState(
changeset.beforeCursor,
CursorStateUpdateReason.changeset
);
}
}
});
);
}
};

Expand Down
82 changes: 77 additions & 5 deletions packages/blocky-core/src/view/renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,11 @@ import {
type DataBaseNode,
BlockDataElement,
BlockyTextModel,
FinalizedChangeset,
Operation,
} from "@pkg/data";
import type { Editor } from "@pkg/view/editor";
import { TextBlock } from "@pkg/block/textBlock";

function ensureChild<K extends keyof HTMLElementTagNameMap>(
dom: HTMLElement,
Expand Down Expand Up @@ -39,6 +42,27 @@ interface IRendererOptions {
editor: Editor;
}

export enum RenderFlag {
Full = 0x01,
Incremental = 0x02,
}

export interface RenderOption {
changeset?: FinalizedChangeset;
operation?: Operation;
flags: number;
}

function isChangesetAllTextEdit(changeset: FinalizedChangeset): boolean {
for (let i = 0, len = changeset.operations.length; i < len; i++) {
if (changeset.operations[i].op !== "text-edit") {
return false;
}
}

return true;
}

/**
* Generally, the renderer only needs to
* call the render method of the block.
Expand All @@ -64,27 +88,72 @@ export class DocRenderer {
this.blockClassName = `${clsPrefix}-editor-block`;
}

render(oldDom?: Node) {
render(option: RenderOption, oldDom?: Node) {
const { editor, clsPrefix } = this;
const { state } = editor;
const createNewDocument = () => {
const newDom = elem(
"div",
`${clsPrefix}-documents ${clsPrefix}-default-fonts`
);
this.renderDocument(state.document, newDom);
this.renderDocument(option, state.document, newDom);
return newDom;
};

if (oldDom && oldDom instanceof HTMLDivElement) {
this.renderDocument(state.document, oldDom);
const { changeset } = option;
if (
option.flags === RenderFlag.Incremental &&
changeset &&
isChangesetAllTextEdit(changeset)
) {
for (let i = 0, len = changeset.operations.length; i < len; i++) {
const op = changeset.operations[i];
this.renderTextBlockByOperation(changeset, op);
}

return oldDom;
}

this.renderDocument(option, state.document, oldDom);
return oldDom;
} else {
return createNewDocument();
}
}

protected renderDocument(document: BlockyDocument, dom: HTMLDivElement) {
renderTextBlockByOperation(
changeset: FinalizedChangeset,
operation: Operation
) {
if (operation.op !== "text-edit") {
throw new Error("op error:" + operation.op);
}
const state = this.editor.state;
const blockElement = state.getBlockElementById(operation.id);
if (!blockElement) {
// has been deleted?
return;
}
if (blockElement.t !== TextBlock.Name) {
return;
}
const block = state.blocks.get(operation.id);
const dom = state.domMap.get(operation.id);
if (block && dom) {
block.render?.(dom as HTMLElement, {
changeset,
operation,
flags: RenderFlag.Incremental,
});
}
}

protected renderDocument(
option: RenderOption,
document: BlockyDocument,
dom: HTMLDivElement
) {
dom._mgNode = document;

const { clsPrefix } = this;
Expand Down Expand Up @@ -120,6 +189,7 @@ export class DocRenderer {
}
);
this.renderBlocks(
option,
blocksContainer,
blocksContainer.firstChild,
document.body.firstChild
Expand Down Expand Up @@ -172,6 +242,7 @@ export class DocRenderer {
}

protected renderBlocks(
option: RenderOption,
blocksContainer: HTMLElement,
beginChild: ChildNode | null,
beginBlockyNode: DataBaseNode | null
Expand Down Expand Up @@ -238,7 +309,7 @@ export class DocRenderer {
if (!block) {
throw new Error(`block not found for id: ${id}`);
}
block.render?.(domPtr as HTMLElement);
block.render?.(domPtr as HTMLElement, option);

nodePtr = nodePtr.nextSibling;
prevPtr = domPtr;
Expand All @@ -249,6 +320,7 @@ export class DocRenderer {
const { childrenContainerDOM, childrenBeginDOM } = block;
if (childrenContainerDOM) {
this.renderBlocks(
option,
childrenContainerDOM,
childrenBeginDOM,
childrenBeginElement
Expand Down
2 changes: 1 addition & 1 deletion packages/blocky-react/src/editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export class BlockyEditor extends Component<Props> {
if (this.props.ignoreInitEmpty !== true) {
editor.initFirstEmptyBlock();
}
editor.render(() => {
editor.fullRender(() => {
if (autoFocus) {
controller.setCursorState(CursorState.collapse("title", 0));
}
Expand Down

1 comment on commit a5ddee4

@vercel
Copy link

@vercel vercel bot commented on a5ddee4 Nov 20, 2023

Choose a reason for hiding this comment

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

Please sign in to comment.