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:
65
src/utils.ts
65
src/utils.ts
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user