/** * A tree type data structure used to build scene and ... * @copyright MrKBear 2022 * @typeParam T - Child type of `TreeNode` */ class TreeNode> implements Iterable { /** * Record all child nodes when this node is the root node. * @remarks * Only maintained when the node is the root node. \ * The array is arranged in depth first traversal order. */ private _progenyNodes: Array | undefined = [ ]; /** * Whether the structure of the current tree has changed. * @remarks * Only maintained when the node is the root node. */ private _isStructUpdated = false; /** * @remarks * When a node is just created, it defaults the root node. \ * So this `rootNode` points to itself. */ private _rootNode: T | this = this; private _parentNode: T | undefined = void 0; private _childNodes: Array = []; /** * Pointer to the root node. */ public get rootNode(): T | this { return this._rootNode; } /** * Pointer to the parent node. */ public get parentNode(): T | undefined { return this._parentNode; } /** * Points to the children of the current node. */ public get childNodes(): ReadonlyArray { return this._childNodes; } /** * Is this a root node? */ public get isRootNode(): boolean { return !this._parentNode && this._rootNode === this; } /** * Depth first traverses the current `TreeNode` and its children `TreeNode`. * @remarks * This will use {@link https://en.wikipedia.org/wiki/Tree_traversal#Pre-order,_NLR Pre-order (NLR) algorithm}. \ * So... Do not call this getter frequently. * @example * ``` javascript * // ❌ Deprecated implementation !!! * for (let i = 0; i < treeNode.progenyNodes.length; i++) { * doSomeThing(treeNode.progenyNodes[i]); * } * * // ✔️ Best Practices * treeNode.progenyNodes.forEach((progenyNode)=> { * doSomeThing(progenyNode); * }); * * // ✔️ Best Practices * for(const progenyNode of treeNode.progenyNodes) { * doSomeThing(progenyNode); * } * ``` * @return Progeny nodes list. */ public get progenyNodes(): ReadonlyArray { if (this.isRootNode && !this._isStructUpdated && this._progenyNodes) { return this._progenyNodes; } else { const cacheArray: Array = []; const stack: Array = this._childNodes.slice().reverse(); while (stack.length > 0) { const currentNode: T | undefined = stack.pop(); if (currentNode) { cacheArray.push(currentNode); // Push all child nodes in reverse order for (let i = currentNode.childNodes.length - 1; i >= 0; i --) { stack.push(currentNode.childNodes[i]); } } } if (this.isRootNode) { this._progenyNodes = cacheArray; this._isStructUpdated = false; } return cacheArray; } } /** * An iterator implementation for {@link TreeNode.progenyNodes} * @see {@link TreeNode.progenyNodes} */ public *[Symbol.iterator](): Iterator { yield * this.progenyNodes; } /** * Remove `nodes` from child nodes. * @param nodes - Nodes to be deleted. * @returns Success Times. */ public removeChild(...nodes: ReadonlyArray): number { let successCount = 0; for (const node of nodes) { let removeNodesIndex = -1; for (let i = 0; i < this._childNodes.length; i++) { if (this._childNodes[i] === node) { removeNodesIndex = i; } } if (removeNodesIndex < 0) { continue; } // Perform the remove operation this._childNodes.splice(removeNodesIndex, 1); node._parentNode = void 0; node._rootNode = node; node.markStructUpdated(); for (const progeny of node.progenyNodes) { progeny._rootNode = node; } successCount ++; } if (successCount) { this._rootNode.markStructUpdated(); } return successCount; } /** * Remove this node from the parent node * @returns Success or not. */ public removeFromParent(): number { if (this.isRootNode) { return 0; } else { return this._parentNode!.removeChild(this); } } /** * Append `nodes` into child nodes. * @param node - Nodes to be added. * @returns Success Times. */ public appendChild(...nodes: ReadonlyArray): number { let successCount = 0; for (const node of nodes) { if (!node.isRootNode) { node.removeFromParent(); } for (const progeny of node.progenyNodes) { progeny._rootNode = this._rootNode; } node._rootNode = this._rootNode; node._parentNode = this; node._progenyNodes = void 0; this._childNodes.push(node); successCount ++; } if (successCount) { this._rootNode.markStructUpdated(); } return successCount; } /** * Mark that the current tree structure has been updated * @returns true */ public markStructUpdated(): boolean { if (this.isRootNode) { return this._isStructUpdated = true; } else { return this.rootNode.markStructUpdated(); } } } class B extends TreeNode { b(){}; } class A extends TreeNode { a(){}; } class C extends A { c(){}; } const a = new A(); const b = new B(); const c = new C(); a.rootNode.rootNode.appendChild(c); // a.rootNode.rootNode.appendChild(a); console.log(a); export { TreeNode };