@@ -17,7 +17,6 @@ import {
17
17
assertFlexTreeEntityNotFreed ,
18
18
ContextSlot ,
19
19
flexTreeSlot ,
20
- isFlexTreeNode ,
21
20
isFreedSymbol ,
22
21
LazyEntity ,
23
22
TreeStatus ,
@@ -71,10 +70,15 @@ export function tryGetTreeNodeSchema(value: unknown): undefined | TreeNodeSchema
71
70
}
72
71
73
72
/** The {@link HydrationState} of a {@link TreeNodeKernel} before the kernel is hydrated */
74
- type UnhydratedState = Off ;
73
+ interface UnhydratedState {
74
+ off : Off ;
75
+ innerNode : UnhydratedFlexTreeNode ;
76
+ }
75
77
76
78
/** The {@link HydrationState} of a {@link TreeNodeKernel} after the kernel is hydrated */
77
79
interface HydratedState {
80
+ /** The flex node for this kernel (lazy - undefined if it has not yet been demanded) */
81
+ innerNode ?: FlexTreeNode ;
78
82
/** The {@link AnchorNode} that this node is associated with. */
79
83
anchorNode : AnchorNode ;
80
84
/** All {@link Off | event deregistration functions} that should be run when the kernel is disposed. */
@@ -86,14 +90,13 @@ type HydrationState = UnhydratedState | HydratedState;
86
90
87
91
/** True if and only if the given {@link HydrationState} is post-hydration */
88
92
function isHydrated ( state : HydrationState ) : state is HydratedState {
89
- return typeof state === "object" ;
93
+ return ( state as Partial < HydratedState > ) . anchorNode !== undefined ;
90
94
}
91
95
92
96
/**
93
97
* Contains state and an internal API for managing {@link TreeNode}s.
94
98
* @remarks All {@link TreeNode}s have an associated kernel object.
95
99
* The kernel has the same lifetime as the node and spans both its unhydrated and hydrated states.
96
- * When hydration occurs, the kernel is notified via the {@link TreeNodeKernel.hydrate | hydrate} method.
97
100
*/
98
101
export class TreeNodeKernel {
99
102
private disposed = false ;
@@ -110,7 +113,7 @@ export class TreeNodeKernel {
110
113
*/
111
114
public generationNumber : number = 0 ;
112
115
113
- #hydrationState: HydrationState = ( ) => { } ;
116
+ #hydrationState: HydrationState ;
114
117
115
118
/**
116
119
* Events registered before hydration.
@@ -133,7 +136,7 @@ export class TreeNodeKernel {
133
136
public constructor (
134
137
public readonly node : TreeNode ,
135
138
public readonly schema : TreeNodeSchema ,
136
- private innerNode : InnerNode ,
139
+ innerNode : InnerNode ,
137
140
private readonly initialContext : Context ,
138
141
) {
139
142
assert ( ! treeNodeToKernel . has ( node ) , 0xa1a /* only one kernel per node can be made */ ) ;
@@ -144,9 +147,9 @@ export class TreeNodeKernel {
144
147
mapTreeNodeToProxy . set ( innerNode , node ) ;
145
148
// Register for change events from the unhydrated flex node.
146
149
// These will be fired if the unhydrated node is edited, and will also be forwarded later to the hydrated node.
147
- this . #hydrationState = innerNode . events . on (
148
- "childrenChangedAfterBatch" ,
149
- ( { changedFields } ) => {
150
+ this . #hydrationState = {
151
+ innerNode ,
152
+ off : innerNode . events . on ( "childrenChangedAfterBatch" , ( { changedFields } ) => {
150
153
this . #unhydratedEvents. value . emit ( "childrenChangedAfterBatch" , {
151
154
changedFields,
152
155
} ) ;
@@ -161,15 +164,16 @@ export class TreeNodeKernel {
161
164
// This cast is safe because the parent (if it exists) of an unhydrated flex node is always another unhydrated flex node.
162
165
n = n . parentField . parent . parent as UnhydratedFlexTreeNode | undefined ;
163
166
}
164
- } ,
165
- ) ;
167
+ } ) ,
168
+ } ;
166
169
} else {
167
170
// Hydrated case
171
+ const { anchorNode } = innerNode ;
168
172
assert (
169
- ! innerNode . anchorNode . slots . has ( proxySlot ) ,
173
+ ! anchorNode . slots . has ( proxySlot ) ,
170
174
0x7f5 /* Cannot associate an flex node with multiple simple-tree nodes */ ,
171
175
) ;
172
- this . hydrate ( innerNode . anchorNode ) ;
176
+ this . #hydrationState = this . createHydratedState ( anchorNode ) ;
173
177
}
174
178
}
175
179
@@ -191,27 +195,11 @@ export class TreeNodeKernel {
191
195
* Happens at most once for any given node.
192
196
* Cleans up mappings to {@link UnhydratedFlexTreeNode} - it is assumed that they are no longer needed once the proxy has an anchor node.
193
197
*/
194
- public hydrate ( anchorNode : AnchorNode ) : void {
198
+ private hydrate ( anchorNode : AnchorNode ) : void {
195
199
assert ( ! this . disposed , 0xa2a /* cannot hydrate a disposed node */ ) ;
196
200
assert ( ! isHydrated ( this . #hydrationState) , 0xa2b /* hydration should only happen once */ ) ;
197
-
198
- // If the this node is raw and thus has a MapTreeNode, forget it:
199
- if ( this . innerNode instanceof UnhydratedFlexTreeNode ) {
200
- mapTreeNodeToProxy . delete ( this . innerNode ) ;
201
- }
202
-
203
- // However, it's fine for an anchor node to rotate through different proxies when the content at that place in the tree is replaced.
204
- anchorNode . slots . set ( proxySlot , this . node ) ;
205
- this . #hydrationState = {
206
- anchorNode,
207
- offAnchorNode : new Set ( [
208
- anchorNode . events . on ( "afterDestroy" , ( ) => this . dispose ( ) ) ,
209
- // TODO: this should be triggered on change even for unhydrated nodes.
210
- anchorNode . events . on ( "childrenChanging" , ( ) => {
211
- this . generationNumber += 1 ;
212
- } ) ,
213
- ] ) ,
214
- } ;
201
+ mapTreeNodeToProxy . delete ( this . #hydrationState. innerNode ) ;
202
+ this . #hydrationState = this . createHydratedState ( anchorNode ) ;
215
203
216
204
// If needed, register forwarding emitters for events from before hydration
217
205
if ( this . #unhydratedEvents. evaluated ) {
@@ -228,6 +216,20 @@ export class TreeNodeKernel {
228
216
}
229
217
}
230
218
219
+ private createHydratedState ( anchorNode : AnchorNode ) : HydratedState {
220
+ anchorNode . slots . set ( proxySlot , this . node ) ;
221
+ return {
222
+ anchorNode,
223
+ offAnchorNode : new Set ( [
224
+ anchorNode . events . on ( "afterDestroy" , ( ) => this . dispose ( ) ) ,
225
+ // TODO: this should be triggered on change even for unhydrated nodes.
226
+ anchorNode . events . on ( "childrenChanging" , ( ) => {
227
+ this . generationNumber += 1 ;
228
+ } ) ,
229
+ ] ) ,
230
+ } ;
231
+ }
232
+
231
233
public getStatus ( ) : TreeStatus {
232
234
if ( this . disposed ) {
233
235
return TreeStatus . Deleted ;
@@ -282,35 +284,36 @@ export class TreeNodeKernel {
282
284
* Note that for "marinated" nodes, this FlexTreeNode exists and returns it: it does not return the MapTreeNode which is the current InnerNode.
283
285
*/
284
286
public getOrCreateInnerNode ( allowFreed = false ) : InnerNode {
285
- if ( ! ( this . innerNode instanceof UnhydratedFlexTreeNode ) ) {
286
- // Cooked case
287
- return this . innerNode ;
287
+ if ( ! isHydrated ( this . #hydrationState) ) {
288
+ return this . #hydrationState. innerNode ; // Unhydrated case
288
289
}
289
290
290
- if ( ! isHydrated ( this . #hydrationState) ) {
291
- return this . innerNode ;
291
+ if ( this . #hydrationState. innerNode !== undefined ) {
292
+ return this . #hydrationState . innerNode ; // Cooked case
292
293
}
293
294
294
295
// Marinated case -> cooked
295
296
const anchorNode = this . #hydrationState. anchorNode ;
296
297
// The proxy is bound to an anchor node, but it may or may not have an actual flex node yet
297
298
const flexNode = anchorNode . slots . get ( flexTreeSlot ) ;
298
299
if ( flexNode !== undefined ) {
299
- this . innerNode = flexNode ;
300
- return flexNode ; // If it does have a flex node, return it...
301
- } // ...otherwise, the flex node must be created
302
- const context = anchorNode . anchorSet . slots . get ( ContextSlot ) ?? fail ( "missing context" ) ;
303
- const cursor = context . checkout . forest . allocateCursor ( "getFlexNode" ) ;
304
- context . checkout . forest . moveCursorToPath ( anchorNode , cursor ) ;
305
- const newFlexNode = makeTree ( context , cursor ) ;
306
- cursor . free ( ) ;
307
- this . innerNode = newFlexNode ;
308
- // Calling this is a performance improvement, however, do this only after demand to avoid momentarily having no anchors to anchorNode
309
- anchorForgetters ?. get ( this . node ) ?.( ) ;
310
- if ( ! allowFreed ) {
311
- assertFlexTreeEntityNotFreed ( newFlexNode ) ;
300
+ // If the flex node already exists, use it...
301
+ this . #hydrationState. innerNode = flexNode ;
302
+ } else {
303
+ // ...otherwise, the flex node must be created
304
+ const context = anchorNode . anchorSet . slots . get ( ContextSlot ) ?? fail ( "missing context" ) ;
305
+ const cursor = context . checkout . forest . allocateCursor ( "getFlexNode" ) ;
306
+ context . checkout . forest . moveCursorToPath ( anchorNode , cursor ) ;
307
+ this . #hydrationState. innerNode = makeTree ( context , cursor ) ;
308
+ cursor . free ( ) ;
309
+ // Calling this is a performance improvement, however, do this only after demand to avoid momentarily having no anchors to anchorNode
310
+ anchorForgetters ?. get ( this . node ) ?.( ) ;
311
+ if ( ! allowFreed ) {
312
+ assertFlexTreeEntityNotFreed ( this . #hydrationState. innerNode ) ;
313
+ }
312
314
}
313
- return newFlexNode ;
315
+
316
+ return this . #hydrationState. innerNode ;
314
317
}
315
318
316
319
/**
@@ -344,24 +347,19 @@ export class TreeNodeKernel {
344
347
/**
345
348
* Retrieves the InnerNode associated with the given target via {@link setInnerNode}, if any.
346
349
* @remarks
347
- * If `target` is a unhydrated node, returns its MapTreeNode .
350
+ * If `target` is an unhydrated node, returns its UnhydratedFlexTreeNode .
348
351
* If `target` is a cooked node (or marinated but a FlexTreeNode exists) returns the FlexTreeNode.
349
- * If the target is not a node, or a marinated node with no FlexTreeNode for its anchor, returns undefined.
352
+ * If the target is a marinated node with no FlexTreeNode for its anchor, returns undefined.
350
353
*/
351
354
public tryGetInnerNode ( ) : InnerNode | undefined {
352
- if ( isFlexTreeNode ( this . innerNode ) ) {
353
- // Cooked case
354
- return this . innerNode ;
355
- }
356
-
357
- if ( ! isHydrated ( this . #hydrationState) ) {
358
- return this . innerNode ;
355
+ if ( isHydrated ( this . #hydrationState) ) {
356
+ return (
357
+ this . #hydrationState. innerNode ??
358
+ this . #hydrationState. anchorNode . slots . get ( flexTreeSlot )
359
+ ) ;
359
360
}
360
361
361
- // Marinated case -> cooked
362
- const anchorNode = this . #hydrationState. anchorNode ;
363
- // The proxy is bound to an anchor node, but it may or may not have an actual flex node yet
364
- return anchorNode . slots . get ( flexTreeSlot ) ;
362
+ return this . #hydrationState. innerNode ;
365
363
}
366
364
}
367
365
0 commit comments