optimize the append queue algorithm

Loop the append queue has been proved to be very inefficient, and
some times lead to N^2 time complexity.

Especially when some abnormal data could not be appended into the
real DOM, will make a dead loop.

Previously we use a 5000ms time out to handle this, which is not
user-friendly and not explicitly.

In this patch, we transform the queue into a tree data structure,
which reflects the layout of real DOM. With the tree data structure,
we can find whether there are dangling nodes that need to be dropped.
Also, the iteration will be much more efficient.

There is still a 500ms time out to avoid a dead loop, but should not
be called in expected scenarios.
This commit is contained in:
Yanzhen Yu
2020-10-24 15:35:02 +08:00
parent 0e63852bbc
commit 6cef61882e
3 changed files with 114 additions and 30 deletions

View File

@@ -453,3 +453,68 @@ export class TreeIndex {
this.inputMap = new Map();
}
}
type ResolveTree = {
value: addedNodeMutation;
children: ResolveTree[];
parent: ResolveTree | null;
};
export function queueToResolveTrees(queue: addedNodeMutation[]): ResolveTree[] {
const queueNodeMap: Record<number, ResolveTree> = {};
const putIntoMap = (
m: addedNodeMutation,
parent: ResolveTree | null,
): ResolveTree => {
const nodeInTree: ResolveTree = {
value: m,
parent,
children: [],
};
queueNodeMap[m.node.id] = nodeInTree;
return nodeInTree;
};
const queueNodeTrees: ResolveTree[] = [];
for (const mutation of queue) {
const { nextId, parentId } = mutation;
if (nextId && nextId in queueNodeMap) {
const nextInTree = queueNodeMap[nextId];
if (nextInTree.parent) {
const idx = nextInTree.parent.children.indexOf(nextInTree);
nextInTree.parent.children.splice(
idx,
0,
putIntoMap(mutation, nextInTree.parent),
);
} else {
const idx = queueNodeTrees.indexOf(nextInTree);
queueNodeTrees.splice(idx, 0, putIntoMap(mutation, null));
}
continue;
}
if (parentId in queueNodeMap) {
const parentInTree = queueNodeMap[parentId];
parentInTree.children.push(putIntoMap(mutation, parentInTree));
continue;
}
queueNodeTrees.push(putIntoMap(mutation, null));
}
return queueNodeTrees;
}
export function iterateResolveTree(
tree: ResolveTree,
cb: (mutation: addedNodeMutation) => unknown,
) {
cb(tree.value);
/**
* The resolve tree was designed to reflect the DOM layout,
* but we need append next sibling first, so we do a reverse
* loop here.
*/
for (let i = tree.children.length - 1; i >= 0; i--) {
iterateResolveTree(tree.children[i], cb);
}
}