Add cloneNotNeeded Set, remove depth from dirtySubTrees (#844)

* Add cloneNotNeeded Set, remove depth from dirtySubTrees

* Fix codes

* Fix codes
This commit is contained in:
Dominic Gannaway
2021-11-17 17:01:31 +00:00
committed by acywatson
parent 031981a282
commit c3115fc213
7 changed files with 74 additions and 84 deletions

View File

@ -111,6 +111,7 @@ function textTransform(node: TextNode, state: State): void {
} }
function traverseNodes(node: BlockNode): void { function traverseNodes(node: BlockNode): void {
debugger
let child = node.getFirstChild(); let child = node.getFirstChild();
while (child !== null) { while (child !== null) {

View File

@ -144,8 +144,9 @@ export function resetEditor(
editor._pendingEditorState = pendingEditorState; editor._pendingEditorState = pendingEditorState;
editor._compositionKey = null; editor._compositionKey = null;
editor._dirtyType = NO_DIRTY_NODES; editor._dirtyType = NO_DIRTY_NODES;
editor._cloneNotNeeded.clear();
editor._dirtyNodes = new Set(); editor._dirtyNodes = new Set();
editor._dirtyBlocks = new Map(); editor._dirtySubTrees.clear();
editor._log = []; editor._log = [];
editor._updates = []; editor._updates = [];
const observer = editor._observer; const observer = editor._observer;
@ -207,8 +208,9 @@ class BaseOutlineEditor {
_textContent: string; _textContent: string;
_config: EditorConfig<{...}>; _config: EditorConfig<{...}>;
_dirtyType: 0 | 1 | 2; _dirtyType: 0 | 1 | 2;
_cloneNotNeeded: Set<NodeKey>;
_dirtyNodes: Set<NodeKey>; _dirtyNodes: Set<NodeKey>;
_dirtyBlocks: Map<NodeKey, Number>; _dirtySubTrees: Set<NodeKey>;
_observer: null | MutationObserver; _observer: null | MutationObserver;
_log: Array<string>; _log: Array<string>;
@ -254,8 +256,9 @@ class BaseOutlineEditor {
this._pendingDecorators = null; this._pendingDecorators = null;
// Used to optimize reconcilation // Used to optimize reconcilation
this._dirtyType = NO_DIRTY_NODES; this._dirtyType = NO_DIRTY_NODES;
this._cloneNotNeeded = new Set();
this._dirtyNodes = new Set(); this._dirtyNodes = new Set();
this._dirtyBlocks = new Map(); this._dirtySubTrees = new Set();
// Handling of DOM mutations // Handling of DOM mutations
this._observer = null; this._observer = null;
// Logging for updates // Logging for updates
@ -430,8 +433,9 @@ declare export class OutlineEditor {
_pendingDecorators: null | {[NodeKey]: ReactNode}; _pendingDecorators: null | {[NodeKey]: ReactNode};
_config: EditorConfig<{...}>; _config: EditorConfig<{...}>;
_dirtyType: 0 | 1 | 2; _dirtyType: 0 | 1 | 2;
_cloneNotNeeded: Set<NodeKey>;
_dirtyNodes: Set<NodeKey>; _dirtyNodes: Set<NodeKey>;
_dirtyBlocks: Map<NodeKey, number>; _dirtySubTrees: Set<NodeKey>;
_observer: null | MutationObserver; _observer: null | MutationObserver;
_log: Array<string>; _log: Array<string>;

View File

@ -24,7 +24,6 @@ import {
internallyMarkNodeAsDirty, internallyMarkNodeAsDirty,
markParentBlocksAsDirty, markParentBlocksAsDirty,
setCompositionKey, setCompositionKey,
getBlockDepth,
} from './OutlineUtils'; } from './OutlineUtils';
import invariant from 'shared/invariant'; import invariant from 'shared/invariant';
import { import {
@ -510,15 +509,12 @@ export class OutlineNode {
// Ensure we get the latest node from pending state // Ensure we get the latest node from pending state
const latestNode = this.getLatest(); const latestNode = this.getLatest();
const parent = latestNode.__parent; const parent = latestNode.__parent;
const dirtyBlocks = editor._dirtyBlocks; const dirtySubTrees = editor._dirtySubTrees;
if (parent !== null) { if (parent !== null) {
markParentBlocksAsDirty(parent, nodeMap, dirtyBlocks); markParentBlocksAsDirty(parent, nodeMap, dirtySubTrees);
} }
const dirtyNodes = editor._dirtyNodes; const cloneNotNeeded = editor._cloneNotNeeded;
if (dirtyNodes.has(key)) { if (cloneNotNeeded.has(key)) {
if (isBlockNode(this)) {
dirtyBlocks.set(key, getBlockDepth(this));
}
return latestNode; return latestNode;
} }
const constructor = latestNode.constructor; const constructor = latestNode.constructor;
@ -533,6 +529,7 @@ export class OutlineNode {
mutableNode.__format = latestNode.__format; mutableNode.__format = latestNode.__format;
mutableNode.__style = latestNode.__style; mutableNode.__style = latestNode.__style;
} }
cloneNotNeeded.add(key);
mutableNode.__key = key; mutableNode.__key = key;
internallyMarkNodeAsDirty(mutableNode); internallyMarkNodeAsDirty(mutableNode);
// Update reference in node map // Update reference in node map

View File

@ -47,7 +47,7 @@ let editorTextContent = '';
let activeEditorConfig: EditorConfig<{...}>; let activeEditorConfig: EditorConfig<{...}>;
let activeEditor: OutlineEditor; let activeEditor: OutlineEditor;
let treatAllNodesAsDirty: boolean = false; let treatAllNodesAsDirty: boolean = false;
let activeDirtyBlocks: Map<NodeKey, number>; let activeDirtySubTrees: Set<NodeKey>;
let activeDirtyNodes: Set<NodeKey>; let activeDirtyNodes: Set<NodeKey>;
let activePrevNodeMap: NodeMap; let activePrevNodeMap: NodeMap;
let activeNextNodeMap: NodeMap; let activeNextNodeMap: NodeMap;
@ -331,7 +331,7 @@ function reconcileNode(
const isDirty = const isDirty =
treatAllNodesAsDirty || treatAllNodesAsDirty ||
activeDirtyNodes.has(key) || activeDirtyNodes.has(key) ||
activeDirtyBlocks.has(key); activeDirtySubTrees.has(key);
const dom = getElementByKeyOrThrow(activeEditor, key); const dom = getElementByKeyOrThrow(activeEditor, key);
if (prevNode === nextNode && !isDirty) { if (prevNode === nextNode && !isDirty) {
@ -526,7 +526,7 @@ function reconcileRoot(
editor: OutlineEditor, editor: OutlineEditor,
selection: null | OutlineSelection, selection: null | OutlineSelection,
dirtyType: 0 | 1 | 2, dirtyType: 0 | 1 | 2,
dirtyBlocks: Map<NodeKey, number>, dirtySubTrees: Set<NodeKey>,
dirtyNodes: Set<NodeKey>, dirtyNodes: Set<NodeKey>,
): void { ): void {
subTreeTextContent = ''; subTreeTextContent = '';
@ -536,7 +536,7 @@ function reconcileRoot(
treatAllNodesAsDirty = dirtyType === FULL_RECONCILE; treatAllNodesAsDirty = dirtyType === FULL_RECONCILE;
activeEditor = editor; activeEditor = editor;
activeEditorConfig = editor._config; activeEditorConfig = editor._config;
activeDirtyBlocks = dirtyBlocks; activeDirtySubTrees = dirtySubTrees;
activeDirtyNodes = dirtyNodes; activeDirtyNodes = dirtyNodes;
activePrevNodeMap = prevEditorState._nodeMap; activePrevNodeMap = prevEditorState._nodeMap;
activeNextNodeMap = nextEditorState._nodeMap; activeNextNodeMap = nextEditorState._nodeMap;
@ -551,7 +551,7 @@ function reconcileRoot(
// $FlowFixMe // $FlowFixMe
activeEditor = undefined; activeEditor = undefined;
// $FlowFixMe // $FlowFixMe
activeDirtyBlocks = undefined; activeDirtySubTrees = undefined;
// $FlowFixMe // $FlowFixMe
activeDirtyNodes = undefined; activeDirtyNodes = undefined;
// $FlowFixMe // $FlowFixMe
@ -579,7 +579,7 @@ export function updateEditorState(
if (needsUpdate && observer !== null) { if (needsUpdate && observer !== null) {
const dirtyType = editor._dirtyType; const dirtyType = editor._dirtyType;
const dirtyBlocks = editor._dirtyBlocks; const dirtySubTrees = editor._dirtySubTrees;
const dirtyNodes = editor._dirtyNodes; const dirtyNodes = editor._dirtyNodes;
observer.disconnect(); observer.disconnect();
@ -590,7 +590,7 @@ export function updateEditorState(
editor, editor,
pendingSelection, pendingSelection,
dirtyType, dirtyType,
dirtyBlocks, dirtySubTrees,
dirtyNodes, dirtyNodes,
); );
} finally { } finally {

View File

@ -166,7 +166,7 @@ function isNodeValidForTransform(
function applyAllTransforms( function applyAllTransforms(
editorState: EditorState, editorState: EditorState,
dirtyNodes: Set<NodeKey>, dirtyNodes: Set<NodeKey>,
dirtyBlocks: Map<NodeKey, number>, dirtySubTrees: Set<NodeKey>,
editor: OutlineEditor, editor: OutlineEditor,
): void { ): void {
const transforms = editor._transforms; const transforms = editor._transforms;
@ -207,15 +207,13 @@ function applyAllTransforms(
} }
} }
if (blockTransforms.size > 0 || rootTransforms.size > 0) { if (blockTransforms.size > 0 || rootTransforms.size > 0) {
const dirtyNodesArr = Array.from(dirtyBlocks); const dirtyNodesArr = Array.from(dirtySubTrees);
const blockTransformsArr = Array.from(blockTransforms); const blockTransformsArr = Array.from(blockTransforms);
const rootTransformsArr = Array.from(rootTransforms); const rootTransformsArr = Array.from(rootTransforms);
const blockTransformsArrLength = blockTransformsArr.length; const blockTransformsArrLength = blockTransformsArr.length;
const rootTransformsArrLength = rootTransformsArr.length; const rootTransformsArrLength = rootTransformsArr.length;
// Sort the blocks by their depth, so we deal with deepest first
dirtyNodesArr.sort((a, b) => b[1] - a[1]);
for (let s = 0; s < dirtyNodesArr.length; s++) { for (let s = 0; s < dirtyNodesArr.length; s++) {
const nodeKey = dirtyNodesArr[s][0]; const nodeKey = dirtyNodesArr[s];
const node = nodeMap.get(nodeKey); const node = nodeMap.get(nodeKey);
if (isNodeValidForTransform(node, compositionKey)) { if (isNodeValidForTransform(node, compositionKey)) {
@ -383,8 +381,9 @@ export function commitPendingUpdates(editor: OutlineEditor): void {
editor._log = []; editor._log = [];
if (needsUpdate) { if (needsUpdate) {
editor._dirtyType = NO_DIRTY_NODES; editor._dirtyType = NO_DIRTY_NODES;
editor._cloneNotNeeded.clear();
editor._dirtyNodes = new Set(); editor._dirtyNodes = new Set();
editor._dirtyBlocks = new Map(); editor._dirtySubTrees.clear();
} }
garbageCollectDetachedDecorators(editor, pendingEditorState); garbageCollectDetachedDecorators(editor, pendingEditorState);
const pendingDecorators = editor._pendingDecorators; const pendingDecorators = editor._pendingDecorators;
@ -514,14 +513,14 @@ export function beginUpdate(
applySelectionTransforms(pendingEditorState, editor); applySelectionTransforms(pendingEditorState, editor);
if (editor._dirtyType !== NO_DIRTY_NODES) { if (editor._dirtyType !== NO_DIRTY_NODES) {
const dirtyNodes = editor._dirtyNodes; const dirtyNodes = editor._dirtyNodes;
const dirtyBlocks = editor._dirtyBlocks; const dirtySubTrees = editor._dirtySubTrees;
if (pendingEditorState.isEmpty()) { if (pendingEditorState.isEmpty()) {
invariant( invariant(
false, false,
'updateEditor: the pending editor state is empty. Ensure the root not never becomes empty from an update.', 'updateEditor: the pending editor state is empty. Ensure the root not never becomes empty from an update.',
); );
} }
applyAllTransforms(pendingEditorState, dirtyNodes, dirtyBlocks, editor); applyAllTransforms(pendingEditorState, dirtyNodes, dirtySubTrees, editor);
processNestedUpdates(editor, deferred); processNestedUpdates(editor, deferred);
garbageCollectDetachedNodes( garbageCollectDetachedNodes(
currentEditorState, currentEditorState,
@ -556,8 +555,9 @@ export function beginUpdate(
// Restore existing editor state to the DOM // Restore existing editor state to the DOM
editor._pendingEditorState = currentEditorState; editor._pendingEditorState = currentEditorState;
editor._dirtyType = FULL_RECONCILE; editor._dirtyType = FULL_RECONCILE;
editor._cloneNotNeeded.clear();
editor._dirtyNodes = new Set(); editor._dirtyNodes = new Set();
editor._dirtyBlocks = new Map(); editor._dirtySubTrees.clear();
editor._log.push('UpdateRecover'); editor._log.push('UpdateRecover');
commitPendingUpdates(editor); commitPendingUpdates(editor);
return; return;

View File

@ -138,6 +138,7 @@ export function generateKey(node: OutlineNode): NodeKey {
const key = generateRandomKey(); const key = generateRandomKey();
editorState._nodeMap.set(key, node); editorState._nodeMap.set(key, node);
editor._dirtyNodes.add(key); editor._dirtyNodes.add(key);
editor._cloneNotNeeded.add(key);
editor._dirtyType = HAS_DIRTY_NODES; editor._dirtyType = HAS_DIRTY_NODES;
return key; return key;
} }
@ -145,18 +146,18 @@ export function generateKey(node: OutlineNode): NodeKey {
export function markParentBlocksAsDirty( export function markParentBlocksAsDirty(
parentKey: NodeKey, parentKey: NodeKey,
nodeMap: NodeMap, nodeMap: NodeMap,
dirtyBlocks: Map<NodeKey, number>, dirtySubTress: Set<NodeKey>,
): void { ): void {
let nextParentKey = parentKey; let nextParentKey = parentKey;
while (nextParentKey !== null) { while (nextParentKey !== null) {
if (dirtyBlocks.has(nextParentKey)) { if (dirtySubTress.has(nextParentKey)) {
return; return;
} }
const node = nodeMap.get(nextParentKey); const node = nodeMap.get(nextParentKey);
if (node === undefined) { if (node === undefined) {
break; break;
} }
dirtyBlocks.set(nextParentKey, getBlockDepth(node)); dirtySubTress.add(nextParentKey);
nextParentKey = node.__parent; nextParentKey = node.__parent;
} }
} }
@ -169,17 +170,14 @@ export function internallyMarkNodeAsDirty(node: OutlineNode): void {
const editorState = getActiveEditorState(); const editorState = getActiveEditorState();
const editor = getActiveEditor(); const editor = getActiveEditor();
const nodeMap = editorState._nodeMap; const nodeMap = editorState._nodeMap;
const dirtyBlocks = editor._dirtyBlocks; const dirtySubTrees = editor._dirtySubTrees;
if (parent !== null) { if (parent !== null) {
markParentBlocksAsDirty(parent, nodeMap, dirtyBlocks); markParentBlocksAsDirty(parent, nodeMap, dirtySubTrees);
} }
const dirtyNodes = editor._dirtyNodes; const dirtyNodes = editor._dirtyNodes;
const key = latest.__key; const key = latest.__key;
editor._dirtyType = HAS_DIRTY_NODES; editor._dirtyType = HAS_DIRTY_NODES;
dirtyNodes.add(latest.__key); dirtyNodes.add(key);
if (isBlockNode(node)) {
dirtyBlocks.set(key, getBlockDepth(node));
}
} }
export function setCompositionKey(compositionKey: null | NodeKey): void { export function setCompositionKey(compositionKey: null | NodeKey): void {
@ -253,16 +251,6 @@ export function getEditorStateTextContent(editorState: EditorState): string {
return editorState.read((view) => view.getRoot().getTextContent()); return editorState.read((view) => view.getRoot().getTextContent());
} }
export function getBlockDepth(startingNode: OutlineNode): number {
let node = startingNode.getParent();
let depth = 0;
while (node !== null) {
depth++;
node = node.getParent();
}
return depth;
}
export function markAllNodesAsDirty( export function markAllNodesAsDirty(
editor: OutlineEditor, editor: OutlineEditor,
type: 'text' | 'decorator' | 'block' | 'root', type: 'text' | 'decorator' | 'block' | 'root',

View File

@ -7,41 +7,41 @@
"5": "insertNodes: cannot insert a non-block into a root node", "5": "insertNodes: cannot insert a non-block into a root node",
"6": "insertNodes: cloned parent clone is not a block", "6": "insertNodes: cloned parent clone is not a block",
"7": "insertText: first node is not a text node", "7": "insertText: first node is not a text node",
"8": "setEditorState: the editor state is empty. Ensure the editor state's root node never becomes empty.", "8": "decorate: base method not extended",
"9": "updateDOM: prevInnerDOM is null or undefined", "9": "setEditorState: the editor state is empty. Ensure the editor state's root node never becomes empty.",
"10": "updateDOM: innerDOM is null or undefined", "10": "updateDOM: prevInnerDOM is null or undefined",
"11": "setFormat: can only be used on non-immutable nodes", "11": "updateDOM: innerDOM is null or undefined",
"12": "setStyle: can only be used on non-immutable nodes", "12": "setFormat: can only be used on non-immutable nodes",
"13": "setTextContent: can only be used on non-immutable text nodes", "13": "setStyle: can only be used on non-immutable nodes",
"14": "spliceText: can only be used on non-immutable text nodes", "14": "setTextContent: can only be used on non-immutable text nodes",
"15": "spliceText: selection not found", "15": "spliceText: can only be used on non-immutable text nodes",
"16": "splitText: can only be used on non-immutable text nodes", "16": "spliceText: selection not found",
"17": "select: cannot be called on root nodes", "17": "splitText: can only be used on non-immutable text nodes",
"18": "remove: cannot be called on root nodes", "18": "Point.getNode: node not found",
"19": "replace: cannot be called on root nodes", "19": "createNodeFromParse: type \"%s\" + not found",
"20": "insertBefore: cannot be called on root nodes", "20": "select: cannot be called on root nodes",
"21": "insertAfter: cannot be called on root nodes", "21": "remove: cannot be called on root nodes",
"22": "rootNode.append: Only block nodes can be appended to the root node", "22": "replace: cannot be called on root nodes",
"23": "createNodeFromParse: type \"%s\" + not found", "23": "insertBefore: cannot be called on root nodes",
"24": "Point.getNode: node not found", "24": "insertAfter: cannot be called on root nodes",
"25": "decorate: base method not extended", "25": "rootNode.append: Only block nodes can be appended to the root node",
"26": "Cannot use method in read-only mode.", "26": "OutlineNode: Node type %s does not implement .clone().",
"27": "Unable to find an active editor state. State helpers or node methods can only be used synchronously during the callback of editor.update() or editorState.read().", "27": "Expected node %s to have a parent.",
"28": "Unable to find an active editor. This method can only be used synchronously during the callback of editor.update().", "28": "Expected node %s to have a parent block.",
"29": "updateEditor: the pending editor state is empty. Ensure the root not never becomes empty from an update.", "29": "Expected node %s to have a top parent block.",
"30": "updateEditor: selection has been lost because the previously selected nodes have been removed and selection wasn't moved to another node. Ensure selection changes after removing/replacing a selected node.", "30": "getNodesBetween: ancestor is null",
"31": "createNode: node does not exist in nodeMap", "31": "getLatest: node not found",
"32": "reconcileNode: prevNode or nextNode does not exist in nodeMap", "32": "createDOM: base method not extended",
"33": "reconcileNode: parentDOM is null", "33": "updateDOM: base method not extended",
"34": "Reconciliation: could not find DOM element for node key \"${key}\"", "34": "setFlags: can only be used on non-immutable nodes",
"35": "OutlineNode: Node type %s does not implement .clone().", "35": "Expected node with key %s to exist but it's not in the nodeMap.",
"36": "Expected node %s to have a parent.", "36": "Cannot use method in read-only mode.",
"37": "Expected node %s to have a parent block.", "37": "Unable to find an active editor state. State helpers or node methods can only be used synchronously during the callback of editor.update() or editorState.read().",
"38": "Expected node %s to have a top parent block.", "38": "Unable to find an active editor. This method can only be used synchronously during the callback of editor.update().",
"39": "getNodesBetween: ancestor is null", "39": "updateEditor: the pending editor state is empty. Ensure the root not never becomes empty from an update.",
"40": "getLatest: node not found", "40": "updateEditor: selection has been lost because the previously selected nodes have been removed and selection wasn't moved to another node. Ensure selection changes after removing/replacing a selected node.",
"41": "createDOM: base method not extended", "41": "createNode: node does not exist in nodeMap",
"42": "updateDOM: base method not extended", "42": "reconcileNode: prevNode or nextNode does not exist in nodeMap",
"43": "setFlags: can only be used on non-immutable nodes", "43": "reconcileNode: parentDOM is null",
"44": "Expected node with key %s to exist but it's not in the nodeMap." "44": "Reconciliation: could not find DOM element for node key \"${key}\""
} }