Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

optimize: incremental render #79

Merged
merged 2 commits into from
Nov 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading