Skip to content

Commit dafa95b

Browse files
authored
Merge pull request #233 from preactjs/simplificate-nodes
Simplify Node instances
2 parents a42a2bd + 3f652a7 commit dafa95b

File tree

2 files changed

+34
-39
lines changed

2 files changed

+34
-39
lines changed

.changeset/shy-cobras-try.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@preact/signals-core": patch
3+
---
4+
5+
Simplify Node book keeping code

packages/core/src/index.ts

Lines changed: 29 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,9 @@ const DISPOSED = 1 << 3;
1010
const HAS_ERROR = 1 << 4;
1111
const TRACKING = 1 << 5;
1212

13-
// Flags for Nodes.
14-
const NODE_FREE = 1 << 0;
15-
const NODE_SUBSCRIBED = 1 << 1;
16-
1713
// A linked list node used to track dependencies (sources) and dependents (targets).
1814
// Also used to remember the source's last version number that the target saw.
1915
type Node = {
20-
// A node may have the following flags:
21-
// NODE_FREE when it's unclear whether the source is still a dependency of the target
22-
// NODE_SUBSCRIBED when the target has subscribed to listen change notifications from the source
23-
_flags: number;
24-
2516
// A source whose value the target depends on.
2617
_source: Signal;
2718
_prevSource?: Node;
@@ -35,6 +26,7 @@ type Node = {
3526
// The version number of the source that target has last seen. We use version numbers
3627
// instead of storing the source value, because source values can take arbitrary amount
3728
// of memory, and computeds could hang on to them forever because they're lazily evaluated.
29+
// Use the special value -1 to mark potentially unused but recyclable nodes.
3830
_version: number;
3931

4032
// Used to remember & roll back the source's previous `._node` value when entering &
@@ -121,7 +113,6 @@ function addDependency(signal: Signal): Node | undefined {
121113
// `signal` is a new dependency. Create a new node dependency node, move it
122114
// to the front of the current context's dependency list.
123115
node = {
124-
_flags: 0,
125116
_version: 0,
126117
_source: signal,
127118
_prevSource: undefined,
@@ -140,10 +131,10 @@ function addDependency(signal: Signal): Node | undefined {
140131
signal._subscribe(node);
141132
}
142133
return node;
143-
} else if (node._flags & NODE_FREE) {
134+
} else if (node._version === -1) {
144135
// `signal` is an existing dependency from a previous evaluation. Reuse the dependency
145136
// node and move it to the front of the evaluation context's dependency list.
146-
node._flags &= ~NODE_FREE;
137+
node._version = 0;
147138

148139
const head = evalContext._sources;
149140
if (node !== head) {
@@ -174,7 +165,10 @@ declare class Signal<T = any> {
174165
/** @internal */
175166
_value: unknown;
176167

177-
/** @internal */
168+
/** @internal
169+
* Version numbers should always be >= 0, because the special value -1 is used
170+
* by Nodes to signify potentially unused but recyclable notes.
171+
*/
178172
_version: number;
179173

180174
/** @internal */
@@ -219,10 +213,8 @@ Signal.prototype._refresh = function () {
219213
};
220214

221215
Signal.prototype._subscribe = function (node) {
222-
if (!(node._flags & NODE_SUBSCRIBED)) {
223-
node._flags |= NODE_SUBSCRIBED;
216+
if (this._targets !== node && node._prevTarget === undefined) {
224217
node._nextTarget = this._targets;
225-
226218
if (this._targets !== undefined) {
227219
this._targets._prevTarget = node;
228220
}
@@ -231,22 +223,18 @@ Signal.prototype._subscribe = function (node) {
231223
};
232224

233225
Signal.prototype._unsubscribe = function (node) {
234-
if (node._flags & NODE_SUBSCRIBED) {
235-
node._flags &= ~NODE_SUBSCRIBED;
236-
237-
const prev = node._prevTarget;
238-
const next = node._nextTarget;
239-
if (prev !== undefined) {
240-
prev._nextTarget = next;
241-
node._prevTarget = undefined;
242-
}
243-
if (next !== undefined) {
244-
next._prevTarget = prev;
245-
node._nextTarget = undefined;
246-
}
247-
if (node === this._targets) {
248-
this._targets = next;
249-
}
226+
const prev = node._prevTarget;
227+
const next = node._nextTarget;
228+
if (prev !== undefined) {
229+
prev._nextTarget = next;
230+
node._prevTarget = undefined;
231+
}
232+
if (next !== undefined) {
233+
next._prevTarget = prev;
234+
node._nextTarget = undefined;
235+
}
236+
if (node === this._targets) {
237+
this._targets = next;
250238
}
251239
};
252240

@@ -318,8 +306,11 @@ function needsToRecompute(target: Computed | Effect): boolean {
318306
// Check the dependencies for changed values. The dependency list is already
319307
// in order of use. Therefore if multiple dependencies have changed values, only
320308
// the first used dependency is re-evaluated at this point.
321-
let node = target._sources;
322-
while (node !== undefined) {
309+
for (
310+
let node = target._sources;
311+
node !== undefined;
312+
node = node._nextSource
313+
) {
323314
// If there's a new version of the dependency before or after refreshing,
324315
// or the dependency has something blocking it from refreshing at all (e.g. a
325316
// dependency cycle), then we need to recompute.
@@ -328,13 +319,12 @@ function needsToRecompute(target: Computed | Effect): boolean {
328319
!node._source._refresh() ||
329320
node._source._version !== node._version
330321
) {
331-
break;
322+
return true;
332323
}
333-
node = node._nextSource;
334324
}
335325
// If none of the dependencies have changed values since last recompute then the
336326
// there's no need to recompute.
337-
return node !== undefined;
327+
return false;
338328
}
339329

340330
function prepareSources(target: Computed | Effect) {
@@ -348,7 +338,7 @@ function prepareSources(target: Computed | Effect) {
348338
node._rollbackNode = rollbackNode;
349339
}
350340
node._source._node = node;
351-
node._flags |= NODE_FREE;
341+
node._version = -1;
352342
}
353343
}
354344

@@ -363,7 +353,7 @@ function cleanupSources(target: Computed | Effect) {
363353
let sources = undefined;
364354
while (node !== undefined) {
365355
const next = node._nextSource;
366-
if (node._flags & NODE_FREE) {
356+
if (node._version === -1) {
367357
node._source._unsubscribe(node);
368358
node._nextSource = undefined;
369359
} else {

0 commit comments

Comments
 (0)