@@ -110,18 +110,32 @@ function addDependency(signal: Signal): Node | undefined {
110
110
111
111
let node = signal . _node ;
112
112
if ( node === undefined || node . _target !== evalContext ) {
113
- // `signal` is a new dependency. Create a new node dependency node, move it
114
- // to the front of the current context's dependency list.
113
+ /**
114
+ * `signal` is a new dependency. Create a new dependency node, and set it
115
+ * as the tail of the current context's dependency list. e.g:
116
+ *
117
+ * { A <-> B }
118
+ * ↑ ↑
119
+ * tail node (new)
120
+ * ↓
121
+ * { A <-> B <-> C }
122
+ * ↑
123
+ * tail (evalContext._sources)
124
+ */
115
125
node = {
116
126
_version : 0 ,
117
127
_source : signal ,
118
- _prevSource : undefined ,
119
- _nextSource : evalContext . _sources ,
128
+ _prevSource : evalContext . _sources ,
129
+ _nextSource : undefined ,
120
130
_target : evalContext ,
121
131
_prevTarget : undefined ,
122
132
_nextTarget : undefined ,
123
133
_rollbackNode : node ,
124
134
} ;
135
+
136
+ if ( evalContext . _sources !== undefined ) {
137
+ evalContext . _sources . _nextSource = node ;
138
+ }
125
139
evalContext . _sources = node ;
126
140
signal . _node = node ;
127
141
@@ -135,18 +149,30 @@ function addDependency(signal: Signal): Node | undefined {
135
149
// `signal` is an existing dependency from a previous evaluation. Reuse it.
136
150
node . _version = 0 ;
137
151
138
- // If `node` is not already the current head of the dependency list (i.e.
139
- // there is a previous node in the list), then make `node` the new head.
140
- if ( node . _prevSource !== undefined ) {
141
- node . _prevSource . _nextSource = node . _nextSource ;
142
- if ( node . _nextSource !== undefined ) {
143
- node . _nextSource . _prevSource = node . _prevSource ;
152
+ /**
153
+ * If `node` is not already the current tail of the dependency list (i.e.
154
+ * there is a next node in the list), then make the `node` the new tail. e.g:
155
+ *
156
+ * { A <-> B <-> C <-> D }
157
+ * ↑ ↑
158
+ * node ┌─── tail (evalContext._sources)
159
+ * └─────│─────┐
160
+ * ↓ ↓
161
+ * { A <-> C <-> D <-> B }
162
+ * ↑
163
+ * tail (evalContext._sources)
164
+ */
165
+ if ( node . _nextSource !== undefined ) {
166
+ node . _nextSource . _prevSource = node . _prevSource ;
167
+
168
+ if ( node . _prevSource !== undefined ) {
169
+ node . _prevSource . _nextSource = node . _nextSource ;
144
170
}
145
- node . _prevSource = undefined ;
146
- node . _nextSource = evalContext . _sources ;
147
- // evalCotext._sources must be !== undefined (and !== node), because
148
- // `node` was originally pointing to some previous node.
149
- evalContext . _sources ! . _prevSource = node ;
171
+
172
+ node . _prevSource = evalContext . _sources ;
173
+ node . _nextSource = undefined ;
174
+
175
+ evalContext . _sources ! . _nextSource = node ;
150
176
evalContext . _sources = node ;
151
177
}
152
178
@@ -161,7 +187,8 @@ declare class Signal<T = any> {
161
187
/** @internal */
162
188
_value : unknown ;
163
189
164
- /** @internal
190
+ /**
191
+ * @internal
165
192
* Version numbers should always be >= 0, because the special value -1 is used
166
193
* by Nodes to signify potentially unused but recyclable nodes.
167
194
*/
@@ -321,12 +348,24 @@ function needsToRecompute(target: Computed | Effect): boolean {
321
348
return true ;
322
349
}
323
350
}
324
- // If none of the dependencies have changed values since last recompute then the
351
+ // If none of the dependencies have changed values since last recompute then
325
352
// there's no need to recompute.
326
353
return false ;
327
354
}
328
355
329
356
function prepareSources ( target : Computed | Effect ) {
357
+ /**
358
+ * 1. Mark all current sources as re-usable nodes (version: -1)
359
+ * 2. Set a rollback node if the current node is being used in a different context
360
+ * 3. Point 'target._sources' to the tail of the doubly-linked list, e.g:
361
+ *
362
+ * { undefined <- A <-> B <-> C -> undefined }
363
+ * ↑ ↑
364
+ * │ └──────┐
365
+ * target._sources = A; (node is head) │
366
+ * ↓ │
367
+ * target._sources = C; (node is tail) ─┘
368
+ */
330
369
for (
331
370
let node = target . _sources ;
332
371
node !== undefined ;
@@ -338,39 +377,66 @@ function prepareSources(target: Computed | Effect) {
338
377
}
339
378
node . _source . _node = node ;
340
379
node . _version = - 1 ;
380
+
381
+ if ( node . _nextSource === undefined ) {
382
+ target . _sources = node ;
383
+ break ;
384
+ }
341
385
}
342
386
}
343
387
344
388
function cleanupSources ( target : Computed | Effect ) {
345
- // At this point target._sources is a mishmash of current & former dependencies.
346
- // The current dependencies are also in a reverse order of use.
347
- // Therefore build a new, reverted list of dependencies containing only the current
348
- // dependencies in a proper order of use.
349
- // Drop former dependencies from the list and unsubscribe from their change notifications.
350
-
351
389
let node = target . _sources ;
352
- let sources = undefined ;
390
+ let head = undefined ;
391
+
392
+ /**
393
+ * At this point 'target._sources' points to the tail of the doubly-linked list.
394
+ * It contains all existing sources + new sources in order of use.
395
+ * Iterate backwards until we find the head node while dropping old dependencies.
396
+ */
353
397
while ( node !== undefined ) {
354
- const next = node . _nextSource ;
398
+ const prev = node . _prevSource ;
399
+
400
+ /**
401
+ * The node was not re-used, unsubscribe from its change notifications and remove itself
402
+ * from the doubly-linked list. e.g:
403
+ *
404
+ * { A <-> B <-> C }
405
+ * ↓
406
+ * { A <-> C }
407
+ */
355
408
if ( node . _version === - 1 ) {
356
409
node . _source . _unsubscribe ( node ) ;
357
- node . _nextSource = undefined ;
358
- } else {
359
- if ( sources !== undefined ) {
360
- sources . _prevSource = node ;
410
+
411
+ if ( prev !== undefined ) {
412
+ prev . _nextSource = node . _nextSource ;
361
413
}
362
- node . _prevSource = undefined ;
363
- node . _nextSource = sources ;
364
- sources = node ;
414
+ if ( node . _nextSource !== undefined ) {
415
+ node . _nextSource . _prevSource = prev ;
416
+ }
417
+ } else {
418
+ /**
419
+ * The new head is the last node seen which wasn't removed/unsubscribed
420
+ * from the doubly-linked list. e.g:
421
+ *
422
+ * { A <-> B <-> C }
423
+ * ↑ ↑ ↑
424
+ * │ │ └ head = node
425
+ * │ └ head = node
426
+ * └ head = node
427
+ */
428
+ head = node ;
365
429
}
366
430
367
431
node . _source . _node = node . _rollbackNode ;
368
432
if ( node . _rollbackNode !== undefined ) {
369
433
node . _rollbackNode = undefined ;
370
434
}
371
- node = next ;
435
+
436
+ node = prev ;
372
437
}
373
- target . _sources = sources ;
438
+
439
+ target . _sources = head ;
374
440
}
375
441
376
442
declare class Computed < T = any > extends Signal < T > {
@@ -417,7 +483,7 @@ Computed.prototype._refresh = function () {
417
483
this . _globalVersion = globalVersion ;
418
484
419
485
// Mark this computed signal running before checking the dependencies for value
420
- // changes, so that the RUNNIN flag can be used to notice cyclical dependencies.
486
+ // changes, so that the RUNNING flag can be used to notice cyclical dependencies.
421
487
this . _flags |= RUNNING ;
422
488
if ( this . _version > 0 && ! needsToRecompute ( this ) ) {
423
489
this . _flags &= ~ RUNNING ;
0 commit comments