2
* Copyright (c) 2022-2024 Huawei Device Co., Ltd.
3
* Licensed under the Apache License, Version 2.0 (the "License");
4
* you may not use this file except in compliance with the License.
5
* You may obtain a copy of the License at
7
* http://www.apache.org/licenses/LICENSE-2.0
9
* Unless required by applicable law or agreed to in writing, software
10
* distributed under the License is distributed on an "AS IS" BASIS,
11
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
* See the License for the specific language governing permissions and
13
* limitations under the License.
16
import { className, uint32 } from "@koalaui/compat"
17
import { Disposable } from "../states/Disposable"
18
import { ReadonlyTreeNode } from "./ReadonlyTreeNode"
21
* This is a node implementation for a tree, which is represented as a pair of bidirectional lists.
22
* It allows nodes to be added or removed incrementally using the state manager.
24
export class IncrementalNode implements Disposable, ReadonlyTreeNode {
25
private _disposed = false
26
private _child: IncrementalNode | undefined = undefined
27
private _prev: IncrementalNode | undefined = undefined
28
private _next: IncrementalNode | undefined = undefined
29
private _parent: IncrementalNode | undefined = undefined
30
private _incremental: IncrementalNode | undefined = undefined
33
* This callback is called when a child node is added to this parent.
35
protected onChildInserted: ((node: IncrementalNode) => void) | undefined = undefined
38
* This callback is called when a child node is removed from this parent.
40
protected onChildRemoved: ((node: IncrementalNode) => void) | undefined = undefined
43
* This kind can be used to distinguish nodes.
49
* @param kind - the kind of this instance
52
constructor(kind: uint32 = 1) {
57
* Use this method instead of standard instanceof for the sake of speed and reliability.
58
* @param kind - a kind of this or parent instance to check against
59
* @returns `true` if this node is an instance of the expected kind
62
isKind(kind: uint32): boolean {
63
return this.kind % kind == 0
67
* @returns `true` if this node should no longer be used
69
get disposed(): boolean {
74
* This method is called to remove this node from the hierarchy.
77
if (this._disposed) return
78
const prev = this._prev
79
const next = this._next
80
const parent = this._parent
82
this._child = undefined
83
if (prev !== undefined) {
84
this._prev = undefined
87
if (next !== undefined) {
88
this._next = undefined
91
if (parent !== undefined) {
92
this._parent = undefined
93
if (parent._child === this) {
96
parent.onChildRemoved?.(this)
101
* @returns a parent node if it is exist
103
get parent(): IncrementalNode | undefined {
108
* @returns text representation of the node
111
return className(this) + ": " + this.kind
115
* @returns text representation of a tree hierarchy starting from this node
117
toHierarchy(): string {
119
for (let node = this._parent; node !== undefined; node = node!._parent) str += " "
120
str += this.toString()
121
for (let node = this._child; node !== undefined; node = node!._next) str += "\n" + node!.toHierarchy()
126
* @returns the first child node contained in this node if it is exist
128
get firstChild(): IncrementalNode | undefined {
133
* @returns the next sibling of this node if it is exist
135
get nextSibling(): IncrementalNode | undefined {
140
* @returns the previous sibling of this node if it is exist
142
get previousSibling(): IncrementalNode | undefined {
147
* This method is called by the state manager
148
* when the incremental update should skip several unchanged child nodes.
149
* @param count - a number of child nodes to skip during the incremental update
152
incrementalUpdateSkip(count: uint32) {
154
const prev = this._incremental
155
let next = prev !== undefined ? prev._next : this._child
156
while (1 < count--) {
157
if (next === undefined) throw new Error("child node is expected here")
160
this._incremental = next
162
else throw new Error("unexpected count of child nodes to skip: " + count)
166
* This method is called by the state manager
167
* when the incremental update of all children of this node is completed.
170
incrementalUpdateDone(parent?: IncrementalNode) {
171
if (this._disposed) throw new Error("child node is already disposed")
172
this._incremental = undefined
173
if (parent !== undefined) {
174
const prev = parent._incremental
175
const next = prev !== undefined ? prev._next : parent._child
176
if (this._parent !== undefined) {
177
if (this._parent != parent) throw new Error("child node belongs to another parent")
178
if (this != next) throw new Error("child node is not expected here")
179
parent._incremental = this
181
parent._incremental = this
184
this._parent = parent
185
if (next !== undefined) next._prev = this
186
if (prev !== undefined) prev._next = this
187
else parent._child = this
188
parent.onChildInserted?.(this)