signalium 0.2.1 → 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/dist/signals.d.ts +8 -11
- package/dist/signals.js +99 -215
- package/package.json +1 -1
- package/src/__tests__/async.test.ts +141 -11
- package/src/__tests__/subscription.test.ts +3 -3
- package/src/__tests__/utils/instrumented.ts +3 -3
- package/src/signals.ts +109 -266
package/CHANGELOG.md
CHANGED
@@ -1,5 +1,17 @@
|
|
1
1
|
# signalium
|
2
2
|
|
3
|
+
## 0.2.3
|
4
|
+
|
5
|
+
### Patch Changes
|
6
|
+
|
7
|
+
- 0ba50a0: Remove linked-lists for deps and subs
|
8
|
+
|
9
|
+
## 0.2.2
|
10
|
+
|
11
|
+
### Patch Changes
|
12
|
+
|
13
|
+
- 0376187: Fix a circular ref and add logs to detect circular refs in dev
|
14
|
+
|
3
15
|
## 0.2.1
|
4
16
|
|
5
17
|
### Patch Changes
|
package/dist/signals.d.ts
CHANGED
@@ -32,25 +32,22 @@ declare const enum SignalState {
|
|
32
32
|
Dirty = 2
|
33
33
|
}
|
34
34
|
interface Link {
|
35
|
-
sub: WeakRef<ComputedSignal<any>>;
|
36
35
|
dep: ComputedSignal<any>;
|
36
|
+
sub: WeakRef<ComputedSignal<any>>;
|
37
37
|
ord: number;
|
38
38
|
version: number;
|
39
|
-
|
40
|
-
nextSub: Link | undefined;
|
41
|
-
prevSub: Link | undefined;
|
39
|
+
consumedAt: number;
|
42
40
|
nextDirty: Link | undefined;
|
43
41
|
}
|
44
|
-
export declare function endTrack(sub: ComputedSignal<any>, shouldDisconnect: boolean): void;
|
45
42
|
export declare class ComputedSignal<T> {
|
46
|
-
|
43
|
+
_id: number;
|
47
44
|
_type: SignalType;
|
48
|
-
|
49
|
-
_subsTail: Link | undefined;
|
50
|
-
_deps: Link | undefined;
|
45
|
+
_deps: Map<ComputedSignal<any>, Link>;
|
51
46
|
_dirtyDep: Link | undefined;
|
47
|
+
_subs: Set<Link>;
|
52
48
|
_state: SignalState;
|
53
49
|
_version: number;
|
50
|
+
_computedCount: number;
|
54
51
|
_connectedCount: number;
|
55
52
|
_currentValue: T | AsyncResult<T> | undefined;
|
56
53
|
_compute: SignalCompute<T> | SignalAsyncCompute<T> | SignalSubscribe<T> | undefined;
|
@@ -59,7 +56,7 @@ export declare class ComputedSignal<T> {
|
|
59
56
|
constructor(type: SignalType, compute: SignalCompute<T> | SignalAsyncCompute<T> | SignalSubscribe<T> | undefined, equals?: SignalEquals<T>, initValue?: T);
|
60
57
|
get(): T | AsyncResult<T>;
|
61
58
|
_check(shouldWatch?: boolean): number;
|
62
|
-
_run(wasConnected: boolean,
|
59
|
+
_run(wasConnected: boolean, shouldConnect: boolean): void;
|
63
60
|
_resetDirty(): void;
|
64
61
|
_dirty(): void;
|
65
62
|
_dirtyConsumers(): void;
|
@@ -91,7 +88,7 @@ export type AsyncResult<T> = AsyncPending<T> | AsyncReady<T>;
|
|
91
88
|
declare class StateSignal<T> implements StateSignal<T> {
|
92
89
|
private _value;
|
93
90
|
private _equals;
|
94
|
-
private
|
91
|
+
private _subs;
|
95
92
|
constructor(_value: T, _equals?: SignalEquals<T>);
|
96
93
|
get(): T;
|
97
94
|
set(value: T): void;
|
package/dist/signals.js
CHANGED
@@ -1,112 +1,21 @@
|
|
1
1
|
import { scheduleDisconnect, scheduleWatcher } from './scheduling.js';
|
2
|
-
let CURRENT_CONSUMER;
|
3
|
-
let CURRENT_DEP_TAIL;
|
4
2
|
let CURRENT_ORD = 0;
|
5
|
-
let
|
3
|
+
let CURRENT_CONSUMER;
|
6
4
|
let CURRENT_IS_WAITING = false;
|
7
|
-
let
|
8
|
-
let id = 0;
|
5
|
+
let ID = 0;
|
9
6
|
const SUBSCRIPTIONS = new WeakMap();
|
10
7
|
const ACTIVE_ASYNCS = new WeakMap();
|
11
8
|
const WAITING = Symbol();
|
12
|
-
let linkPool;
|
13
|
-
function linkNewDep(dep, sub, nextDep, depsTail, ord) {
|
14
|
-
let newLink;
|
15
|
-
if (linkPool !== undefined) {
|
16
|
-
newLink = linkPool;
|
17
|
-
linkPool = newLink.nextDep;
|
18
|
-
newLink.nextDep = nextDep;
|
19
|
-
newLink.dep = dep;
|
20
|
-
newLink.sub = sub._ref;
|
21
|
-
newLink.ord = ord;
|
22
|
-
}
|
23
|
-
else {
|
24
|
-
newLink = {
|
25
|
-
dep,
|
26
|
-
sub: sub._ref,
|
27
|
-
ord,
|
28
|
-
version: 0,
|
29
|
-
nextDep,
|
30
|
-
nextDirty: undefined,
|
31
|
-
prevSub: undefined,
|
32
|
-
nextSub: undefined,
|
33
|
-
};
|
34
|
-
}
|
35
|
-
if (depsTail === undefined) {
|
36
|
-
sub._deps = newLink;
|
37
|
-
}
|
38
|
-
else {
|
39
|
-
depsTail.nextDep = newLink;
|
40
|
-
}
|
41
|
-
if (dep._subs === undefined) {
|
42
|
-
dep._subs = newLink;
|
43
|
-
}
|
44
|
-
else {
|
45
|
-
const oldTail = dep._subsTail;
|
46
|
-
newLink.prevSub = oldTail;
|
47
|
-
oldTail.nextSub = newLink;
|
48
|
-
}
|
49
|
-
dep._subsTail = newLink;
|
50
|
-
return newLink;
|
51
|
-
}
|
52
|
-
function poolLink(link) {
|
53
|
-
const dep = link.dep;
|
54
|
-
const nextSub = link.nextSub;
|
55
|
-
const prevSub = link.prevSub;
|
56
|
-
if (nextSub !== undefined) {
|
57
|
-
nextSub.prevSub = prevSub;
|
58
|
-
link.nextSub = undefined;
|
59
|
-
}
|
60
|
-
else {
|
61
|
-
dep._subsTail = prevSub;
|
62
|
-
}
|
63
|
-
if (prevSub !== undefined) {
|
64
|
-
prevSub.nextSub = nextSub;
|
65
|
-
link.prevSub = undefined;
|
66
|
-
}
|
67
|
-
else {
|
68
|
-
dep._subs = nextSub;
|
69
|
-
}
|
70
|
-
// @ts-expect-error - override to pool the value
|
71
|
-
link.dep = undefined;
|
72
|
-
// @ts-expect-error - override to pool the value
|
73
|
-
link.sub = undefined;
|
74
|
-
link.nextDep = linkPool;
|
75
|
-
linkPool = link;
|
76
|
-
link.prevSub = undefined;
|
77
|
-
}
|
78
|
-
export function endTrack(sub, shouldDisconnect) {
|
79
|
-
if (CURRENT_DEP_TAIL !== undefined) {
|
80
|
-
if (CURRENT_DEP_TAIL.nextDep !== undefined) {
|
81
|
-
clearTrack(CURRENT_DEP_TAIL.nextDep, shouldDisconnect);
|
82
|
-
CURRENT_DEP_TAIL.nextDep = undefined;
|
83
|
-
}
|
84
|
-
}
|
85
|
-
else if (sub._deps !== undefined) {
|
86
|
-
clearTrack(sub._deps, shouldDisconnect);
|
87
|
-
sub._deps = undefined;
|
88
|
-
}
|
89
|
-
}
|
90
|
-
function clearTrack(link, shouldDisconnect) {
|
91
|
-
do {
|
92
|
-
const nextDep = link.nextDep;
|
93
|
-
if (shouldDisconnect) {
|
94
|
-
scheduleDisconnect(link.dep);
|
95
|
-
}
|
96
|
-
poolLink(link);
|
97
|
-
link = nextDep;
|
98
|
-
} while (link !== undefined);
|
99
|
-
}
|
100
9
|
export class ComputedSignal {
|
101
|
-
|
10
|
+
_id = ID++;
|
102
11
|
_type;
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
_dirtyDep;
|
12
|
+
_deps = new Map();
|
13
|
+
_dirtyDep = undefined;
|
14
|
+
_subs = new Set();
|
107
15
|
_state = 2 /* SignalState.Dirty */;
|
108
16
|
_version = 0;
|
109
|
-
|
17
|
+
_computedCount = 0;
|
18
|
+
_connectedCount = 0;
|
110
19
|
_currentValue;
|
111
20
|
_compute;
|
112
21
|
_equals;
|
@@ -150,33 +59,30 @@ export class ComputedSignal {
|
|
150
59
|
};
|
151
60
|
}
|
152
61
|
get() {
|
153
|
-
|
154
|
-
|
62
|
+
if (CURRENT_CONSUMER !== undefined) {
|
63
|
+
const { _deps: deps, _computedCount: computedCount, _connectedCount: connectedCount } = CURRENT_CONSUMER;
|
64
|
+
const prevLink = deps.get(this);
|
155
65
|
const ord = CURRENT_ORD++;
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
66
|
+
this._check(!prevLink && connectedCount > 0);
|
67
|
+
if (prevLink === undefined) {
|
68
|
+
const newLink = {
|
69
|
+
dep: this,
|
70
|
+
sub: CURRENT_CONSUMER._ref,
|
71
|
+
ord,
|
72
|
+
version: this._version,
|
73
|
+
consumedAt: CURRENT_CONSUMER._computedCount,
|
74
|
+
nextDirty: undefined,
|
75
|
+
};
|
76
|
+
deps.set(this, newLink);
|
77
|
+
this._subs.add(newLink);
|
78
|
+
}
|
79
|
+
else if (prevLink.consumedAt !== computedCount) {
|
80
|
+
prevLink.ord = ord;
|
81
|
+
prevLink.version = this._version;
|
82
|
+
prevLink.consumedAt = computedCount;
|
83
|
+
// prevLink.nextDirty = undefined;
|
84
|
+
this._subs.add(prevLink);
|
175
85
|
}
|
176
|
-
this._check(CURRENT_IS_WATCHED && !prevTracked);
|
177
|
-
CURRENT_DEP_TAIL = newLink ?? linkNewDep(this, CURRENT_CONSUMER, nextDep, CURRENT_DEP_TAIL, ord);
|
178
|
-
CURRENT_DEP_TAIL.version = this._version;
|
179
|
-
CURRENT_SEEN.add(this);
|
180
86
|
}
|
181
87
|
else {
|
182
88
|
this._check();
|
@@ -184,6 +90,7 @@ export class ComputedSignal {
|
|
184
90
|
return this._currentValue;
|
185
91
|
}
|
186
92
|
_check(shouldWatch = false) {
|
93
|
+
// COUNTS.checks++;
|
187
94
|
let state = this._state;
|
188
95
|
let connectedCount = this._connectedCount;
|
189
96
|
const wasConnected = connectedCount > 0;
|
@@ -196,17 +103,13 @@ export class ComputedSignal {
|
|
196
103
|
state = 2 /* SignalState.Dirty */;
|
197
104
|
}
|
198
105
|
else {
|
199
|
-
|
200
|
-
while (link !== undefined) {
|
201
|
-
const dep = link.dep;
|
106
|
+
for (const [dep, link] of this._deps) {
|
202
107
|
if (link.version !== dep._check(true)) {
|
203
108
|
state = 2 /* SignalState.Dirty */;
|
204
109
|
break;
|
205
110
|
}
|
206
|
-
link = link.nextDep;
|
207
111
|
}
|
208
112
|
}
|
209
|
-
this._resetDirty();
|
210
113
|
}
|
211
114
|
if (state === 0 /* SignalState.Clean */) {
|
212
115
|
return this._version;
|
@@ -223,7 +126,7 @@ export class ComputedSignal {
|
|
223
126
|
}
|
224
127
|
}
|
225
128
|
if (state === 2 /* SignalState.Dirty */) {
|
226
|
-
this._run(wasConnected,
|
129
|
+
this._run(wasConnected, shouldConnect);
|
227
130
|
}
|
228
131
|
else {
|
229
132
|
this._resetDirty();
|
@@ -232,20 +135,13 @@ export class ComputedSignal {
|
|
232
135
|
this._dirtyDep = undefined;
|
233
136
|
return this._version;
|
234
137
|
}
|
235
|
-
_run(wasConnected,
|
138
|
+
_run(wasConnected, shouldConnect) {
|
236
139
|
const { _type: type } = this;
|
237
140
|
const prevConsumer = CURRENT_CONSUMER;
|
238
|
-
const prevOrd = CURRENT_ORD;
|
239
|
-
const prevSeen = CURRENT_SEEN;
|
240
|
-
const prevDepTail = CURRENT_DEP_TAIL;
|
241
|
-
const prevIsWatched = CURRENT_IS_WATCHED;
|
242
141
|
try {
|
243
142
|
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
244
143
|
CURRENT_CONSUMER = this;
|
245
|
-
|
246
|
-
CURRENT_SEEN = new WeakSet();
|
247
|
-
CURRENT_DEP_TAIL = undefined;
|
248
|
-
CURRENT_IS_WATCHED = isConnected;
|
144
|
+
this._computedCount++;
|
249
145
|
switch (type) {
|
250
146
|
case 0 /* SignalType.Computed */: {
|
251
147
|
const prevValue = this._currentValue;
|
@@ -269,8 +165,8 @@ export class ComputedSignal {
|
|
269
165
|
value.isPending = false;
|
270
166
|
value.isError = true;
|
271
167
|
this._version++;
|
168
|
+
break;
|
272
169
|
}
|
273
|
-
break;
|
274
170
|
}
|
275
171
|
if (CURRENT_IS_WAITING) {
|
276
172
|
if (!value.isPending) {
|
@@ -352,29 +248,26 @@ export class ComputedSignal {
|
|
352
248
|
}
|
353
249
|
}
|
354
250
|
finally {
|
355
|
-
|
251
|
+
const deps = this._deps;
|
252
|
+
for (const link of deps.values()) {
|
253
|
+
if (link.consumedAt === this._computedCount)
|
254
|
+
continue;
|
255
|
+
const dep = link.dep;
|
256
|
+
if (wasConnected) {
|
257
|
+
scheduleDisconnect(dep);
|
258
|
+
}
|
259
|
+
deps.delete(dep);
|
260
|
+
dep._subs.delete(link);
|
261
|
+
}
|
356
262
|
CURRENT_CONSUMER = prevConsumer;
|
357
|
-
CURRENT_SEEN = prevSeen;
|
358
|
-
CURRENT_DEP_TAIL = prevDepTail;
|
359
|
-
CURRENT_ORD = prevOrd;
|
360
|
-
CURRENT_IS_WATCHED = prevIsWatched;
|
361
263
|
}
|
362
264
|
}
|
363
265
|
_resetDirty() {
|
364
266
|
let dirty = this._dirtyDep;
|
267
|
+
// COUNTS.dirtyResetIterations++;
|
365
268
|
while (dirty !== undefined) {
|
366
|
-
|
367
|
-
|
368
|
-
if (oldHead === undefined) {
|
369
|
-
dep._subs = dirty;
|
370
|
-
dirty.nextSub = undefined;
|
371
|
-
dirty.prevSub = undefined;
|
372
|
-
}
|
373
|
-
else {
|
374
|
-
dirty.nextSub = oldHead;
|
375
|
-
oldHead.prevSub = dirty;
|
376
|
-
dep._subs = dirty;
|
377
|
-
}
|
269
|
+
// COUNTS.dirtyResetIterations++;
|
270
|
+
dirty.dep._subs.add(dirty);
|
378
271
|
let nextDirty = dirty.nextDirty;
|
379
272
|
dirty.nextDirty = undefined;
|
380
273
|
dirty = nextDirty;
|
@@ -393,50 +286,41 @@ export class ComputedSignal {
|
|
393
286
|
else {
|
394
287
|
this._dirtyConsumers();
|
395
288
|
}
|
396
|
-
this._subs = undefined;
|
397
289
|
}
|
398
290
|
_dirtyConsumers() {
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
if (consumer === undefined) {
|
403
|
-
const nextSub = link.nextSub;
|
404
|
-
poolLink(link);
|
405
|
-
link = nextSub;
|
406
|
-
continue;
|
407
|
-
}
|
408
|
-
const state = consumer._state;
|
409
|
-
if (state === 2 /* SignalState.Dirty */) {
|
410
|
-
const nextSub = link.nextSub;
|
411
|
-
link = nextSub;
|
291
|
+
for (const link of this._subs.values()) {
|
292
|
+
const sub = link.sub.deref();
|
293
|
+
if (sub === undefined)
|
412
294
|
continue;
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
295
|
+
switch (sub._state) {
|
296
|
+
case 1 /* SignalState.MaybeDirty */: {
|
297
|
+
let dirty = sub._dirtyDep;
|
298
|
+
const ord = link.ord;
|
299
|
+
if (dirty.ord > ord) {
|
300
|
+
sub._dirtyDep = link;
|
301
|
+
link.nextDirty = dirty;
|
302
|
+
}
|
303
|
+
else {
|
304
|
+
let nextDirty = dirty.nextDirty;
|
305
|
+
while (nextDirty !== undefined && nextDirty.ord < ord) {
|
306
|
+
// COUNTS.dirtyInsertIterations++;
|
307
|
+
dirty = nextDirty;
|
308
|
+
nextDirty = dirty.nextDirty;
|
309
|
+
}
|
310
|
+
link.nextDirty = nextDirty;
|
311
|
+
dirty.nextDirty = link;
|
426
312
|
}
|
427
|
-
|
428
|
-
|
313
|
+
break;
|
314
|
+
}
|
315
|
+
case 0 /* SignalState.Clean */: {
|
316
|
+
sub._state = 1 /* SignalState.MaybeDirty */;
|
317
|
+
sub._dirtyDep = link;
|
318
|
+
link.nextDirty = undefined;
|
319
|
+
sub._dirty();
|
429
320
|
}
|
430
321
|
}
|
431
|
-
else {
|
432
|
-
// consumer._dirtyQueueLength = dirtyQueueLength + 2;
|
433
|
-
consumer._state = 1 /* SignalState.MaybeDirty */;
|
434
|
-
consumer._dirtyDep = link;
|
435
|
-
link.nextDirty = undefined;
|
436
|
-
consumer._dirty();
|
437
|
-
}
|
438
|
-
link = link.nextSub;
|
439
322
|
}
|
323
|
+
this._subs = new Set();
|
440
324
|
}
|
441
325
|
_disconnect(count = 1) {
|
442
326
|
this._connectedCount -= count;
|
@@ -453,25 +337,23 @@ export class ComputedSignal {
|
|
453
337
|
SUBSCRIPTIONS.delete(this);
|
454
338
|
}
|
455
339
|
}
|
456
|
-
|
457
|
-
while (link !== undefined) {
|
340
|
+
for (const link of this._deps.values()) {
|
458
341
|
const dep = link.dep;
|
459
342
|
dep._disconnect();
|
460
|
-
link = link.nextDep;
|
461
343
|
}
|
462
344
|
}
|
463
345
|
}
|
464
346
|
class StateSignal {
|
465
347
|
_value;
|
466
348
|
_equals;
|
467
|
-
|
349
|
+
_subs = [];
|
468
350
|
constructor(_value, _equals = (a, b) => a === b) {
|
469
351
|
this._value = _value;
|
470
352
|
this._equals = _equals;
|
471
353
|
}
|
472
354
|
get() {
|
473
355
|
if (CURRENT_CONSUMER !== undefined) {
|
474
|
-
this.
|
356
|
+
this._subs.push(CURRENT_CONSUMER._ref);
|
475
357
|
}
|
476
358
|
return this._value;
|
477
359
|
}
|
@@ -480,16 +362,24 @@ class StateSignal {
|
|
480
362
|
return;
|
481
363
|
}
|
482
364
|
this._value = value;
|
483
|
-
const
|
484
|
-
|
485
|
-
|
486
|
-
|
365
|
+
const subs = this._subs;
|
366
|
+
const subsLength = subs.length;
|
367
|
+
for (let i = 0; i < subsLength; i++) {
|
368
|
+
const sub = subs[i].deref();
|
369
|
+
if (sub === undefined) {
|
487
370
|
continue;
|
488
371
|
}
|
489
|
-
|
490
|
-
|
372
|
+
switch (sub._state) {
|
373
|
+
case 0 /* SignalState.Clean */:
|
374
|
+
sub._state = 2 /* SignalState.Dirty */;
|
375
|
+
sub._dirty();
|
376
|
+
break;
|
377
|
+
case 1 /* SignalState.MaybeDirty */:
|
378
|
+
sub._state = 2 /* SignalState.Dirty */;
|
379
|
+
break;
|
380
|
+
}
|
491
381
|
}
|
492
|
-
|
382
|
+
this._subs = [];
|
493
383
|
}
|
494
384
|
}
|
495
385
|
export function state(initialValue, opts) {
|
@@ -505,7 +395,7 @@ export function subscription(subscribe, opts) {
|
|
505
395
|
return new ComputedSignal(1 /* SignalType.Subscription */, subscribe, opts?.equals, opts?.initValue);
|
506
396
|
}
|
507
397
|
export function watcher(fn) {
|
508
|
-
const subscribers =
|
398
|
+
const subscribers = [];
|
509
399
|
const watcher = new ComputedSignal(3 /* SignalType.Watcher */, () => {
|
510
400
|
fn();
|
511
401
|
untrack(() => {
|
@@ -520,9 +410,9 @@ export function watcher(fn) {
|
|
520
410
|
scheduleDisconnect(watcher);
|
521
411
|
},
|
522
412
|
subscribe(subscriber) {
|
523
|
-
subscribers.
|
413
|
+
subscribers.push(subscriber);
|
524
414
|
return () => {
|
525
|
-
subscribers.
|
415
|
+
subscribers.splice(subscribers.indexOf(subscriber), 1);
|
526
416
|
};
|
527
417
|
},
|
528
418
|
};
|
@@ -532,18 +422,12 @@ export function isTracking() {
|
|
532
422
|
}
|
533
423
|
export function untrack(fn) {
|
534
424
|
const prevConsumer = CURRENT_CONSUMER;
|
535
|
-
const prevOrd = CURRENT_ORD;
|
536
|
-
const prevIsWatched = CURRENT_IS_WATCHED;
|
537
425
|
try {
|
538
426
|
CURRENT_CONSUMER = undefined;
|
539
427
|
// LAST_CONSUMED = undefined;
|
540
|
-
CURRENT_ORD = 0;
|
541
|
-
CURRENT_IS_WATCHED = false;
|
542
428
|
return fn();
|
543
429
|
}
|
544
430
|
finally {
|
545
431
|
CURRENT_CONSUMER = prevConsumer;
|
546
|
-
CURRENT_ORD = prevOrd;
|
547
|
-
CURRENT_IS_WATCHED = prevIsWatched;
|
548
432
|
}
|
549
433
|
}
|
package/package.json
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
import { describe, expect, test } from 'vitest';
|
2
|
-
import { state,
|
2
|
+
import { state, asyncComputed, computed } from './utils/instrumented.js';
|
3
3
|
import { AsyncResult } from '../signals';
|
4
4
|
|
5
5
|
const sleep = (ms = 0) => new Promise(r => setTimeout(r, ms));
|
@@ -131,7 +131,7 @@ describe('Async Signal functionality', () => {
|
|
131
131
|
const result = a.get() + b.get();
|
132
132
|
|
133
133
|
if (result === 4) {
|
134
|
-
await sleep(
|
134
|
+
await sleep(10);
|
135
135
|
}
|
136
136
|
|
137
137
|
return result;
|
@@ -163,7 +163,7 @@ describe('Async Signal functionality', () => {
|
|
163
163
|
resolve: 1,
|
164
164
|
});
|
165
165
|
|
166
|
-
await sleep(
|
166
|
+
await sleep(20);
|
167
167
|
|
168
168
|
expect(c).toHaveValueAndCounts(result(5, 'success', 'resolved'), {
|
169
169
|
compute: 3,
|
@@ -179,7 +179,7 @@ describe('Async Signal functionality', () => {
|
|
179
179
|
async () => {
|
180
180
|
const result = a.get() + b.get();
|
181
181
|
|
182
|
-
await sleep(
|
182
|
+
await sleep(10);
|
183
183
|
|
184
184
|
return result;
|
185
185
|
},
|
@@ -200,7 +200,7 @@ describe('Async Signal functionality', () => {
|
|
200
200
|
resolve: 0,
|
201
201
|
});
|
202
202
|
|
203
|
-
await sleep(
|
203
|
+
await sleep(20);
|
204
204
|
|
205
205
|
expect(c).toHaveValueAndCounts(result(3, 'success', 'resolved'), {
|
206
206
|
compute: 1,
|
@@ -211,13 +211,13 @@ describe('Async Signal functionality', () => {
|
|
211
211
|
describe('Awaiting', () => {
|
212
212
|
test('Awaiting a computed will resolve the value', async () => {
|
213
213
|
const compA = asyncComputed(async () => {
|
214
|
-
await sleep(
|
214
|
+
await sleep(10);
|
215
215
|
|
216
216
|
return 1;
|
217
217
|
});
|
218
218
|
|
219
219
|
const compB = asyncComputed(async () => {
|
220
|
-
await sleep(
|
220
|
+
await sleep(10);
|
221
221
|
|
222
222
|
return 2;
|
223
223
|
});
|
@@ -243,7 +243,7 @@ describe('Async Signal functionality', () => {
|
|
243
243
|
resolve: 0,
|
244
244
|
});
|
245
245
|
|
246
|
-
await sleep(
|
246
|
+
await sleep(10);
|
247
247
|
|
248
248
|
// Check to make sure we don't resolve early after the first task completes
|
249
249
|
expect(compC).toHaveValueAndCounts(result(undefined, 'pending', 'initial'), {
|
@@ -251,7 +251,7 @@ describe('Async Signal functionality', () => {
|
|
251
251
|
resolve: 0,
|
252
252
|
});
|
253
253
|
|
254
|
-
await sleep(
|
254
|
+
await sleep(10);
|
255
255
|
|
256
256
|
expect(compC).toHaveValueAndCounts(result(3, 'success', 'resolved'), {
|
257
257
|
compute: 3,
|
@@ -261,7 +261,7 @@ describe('Async Signal functionality', () => {
|
|
261
261
|
|
262
262
|
test('Awaiting a computed can handle errors', async () => {
|
263
263
|
const compA = asyncComputed(async () => {
|
264
|
-
await sleep(
|
264
|
+
await sleep(10);
|
265
265
|
|
266
266
|
throw 'error';
|
267
267
|
});
|
@@ -285,12 +285,142 @@ describe('Async Signal functionality', () => {
|
|
285
285
|
resolve: 0,
|
286
286
|
});
|
287
287
|
|
288
|
-
await sleep(
|
288
|
+
await sleep(10);
|
289
|
+
|
290
|
+
expect(compC).toHaveValueAndCounts(result(undefined, 'error', 'initial', 'error'), {
|
291
|
+
compute: 2,
|
292
|
+
resolve: 0,
|
293
|
+
});
|
294
|
+
});
|
295
|
+
|
296
|
+
test('Awaiting a computed does not let valid values override errors', async () => {
|
297
|
+
const compA = asyncComputed(async () => {
|
298
|
+
await sleep(10);
|
299
|
+
|
300
|
+
throw 'error';
|
301
|
+
});
|
302
|
+
|
303
|
+
const compB = asyncComputed(async () => {
|
304
|
+
await sleep(20);
|
305
|
+
|
306
|
+
return 2;
|
307
|
+
});
|
308
|
+
|
309
|
+
const compC = asyncComputed(async () => {
|
310
|
+
const aResult = compA.get();
|
311
|
+
const bResult = compB.get();
|
312
|
+
|
313
|
+
const b = bResult.await();
|
314
|
+
const a = aResult.await();
|
315
|
+
|
316
|
+
return a + b;
|
317
|
+
});
|
318
|
+
|
319
|
+
// Pull once to start the computation, trigger the computation
|
320
|
+
expect(compC).toHaveValueAndCounts(result(undefined, 'pending', 'initial'), {
|
321
|
+
compute: 1,
|
322
|
+
resolve: 0,
|
323
|
+
});
|
324
|
+
|
325
|
+
await sleep(30);
|
289
326
|
|
290
327
|
expect(compC).toHaveValueAndCounts(result(undefined, 'error', 'initial', 'error'), {
|
291
328
|
compute: 2,
|
292
329
|
resolve: 0,
|
293
330
|
});
|
294
331
|
});
|
332
|
+
|
333
|
+
test('Await can be composed and nested', async () => {
|
334
|
+
const compA = asyncComputed('compA', async () => {
|
335
|
+
await sleep(20);
|
336
|
+
return 1;
|
337
|
+
});
|
338
|
+
|
339
|
+
const compB = asyncComputed('compB', async () => {
|
340
|
+
await sleep(20);
|
341
|
+
return 2;
|
342
|
+
});
|
343
|
+
|
344
|
+
const compC = computed('compC', () => {
|
345
|
+
const resultA = compA.get();
|
346
|
+
const resultB = compB.get();
|
347
|
+
|
348
|
+
return {
|
349
|
+
awaitA: resultA.await,
|
350
|
+
awaitB: resultB.await,
|
351
|
+
};
|
352
|
+
});
|
353
|
+
|
354
|
+
const compD = asyncComputed('compD', async () => {
|
355
|
+
const { awaitA, awaitB } = compC.get();
|
356
|
+
const a = awaitA();
|
357
|
+
const b = awaitB();
|
358
|
+
|
359
|
+
return a + b;
|
360
|
+
});
|
361
|
+
|
362
|
+
// Pull once to start the computation, trigger the computation
|
363
|
+
expect(compD).toHaveValueAndCounts(result(undefined, 'pending', 'initial'), {
|
364
|
+
compute: 1,
|
365
|
+
resolve: 0,
|
366
|
+
});
|
367
|
+
|
368
|
+
await sleep(30);
|
369
|
+
|
370
|
+
expect(compD).toHaveValueAndCounts(result(3, 'success', 'resolved'), {
|
371
|
+
compute: 3,
|
372
|
+
resolve: 1,
|
373
|
+
});
|
374
|
+
});
|
375
|
+
|
376
|
+
test('Await works with intermediate state', async () => {
|
377
|
+
const compA = asyncComputed('compA', async () => {
|
378
|
+
await sleep(20);
|
379
|
+
return 1;
|
380
|
+
});
|
381
|
+
|
382
|
+
const compB = asyncComputed('compB', async () => {
|
383
|
+
await sleep(40);
|
384
|
+
return 2;
|
385
|
+
});
|
386
|
+
|
387
|
+
const compC = computed('compC', () => {
|
388
|
+
const resultA = compA.get();
|
389
|
+
const resultB = compB.get();
|
390
|
+
|
391
|
+
return {
|
392
|
+
awaitA: resultA.await,
|
393
|
+
awaitB: resultB.await,
|
394
|
+
};
|
395
|
+
});
|
396
|
+
|
397
|
+
const compD = asyncComputed('compD', async () => {
|
398
|
+
const { awaitA, awaitB } = compC.get();
|
399
|
+
const a = awaitA();
|
400
|
+
const b = awaitB();
|
401
|
+
|
402
|
+
return a + b;
|
403
|
+
});
|
404
|
+
|
405
|
+
// Pull once to start the computation, trigger the computation
|
406
|
+
expect(compD).toHaveValueAndCounts(result(undefined, 'pending', 'initial'), {
|
407
|
+
compute: 1,
|
408
|
+
resolve: 0,
|
409
|
+
});
|
410
|
+
|
411
|
+
await sleep(30);
|
412
|
+
|
413
|
+
expect(compD).toHaveValueAndCounts(result(undefined, 'pending', 'initial'), {
|
414
|
+
compute: 2,
|
415
|
+
resolve: 0,
|
416
|
+
});
|
417
|
+
|
418
|
+
await sleep(30);
|
419
|
+
|
420
|
+
expect(compD).toHaveValueAndCounts(result(3, 'success', 'resolved'), {
|
421
|
+
compute: 3,
|
422
|
+
resolve: 1,
|
423
|
+
});
|
424
|
+
});
|
295
425
|
});
|
296
426
|
});
|
@@ -227,7 +227,7 @@ describe('Subscription Signal functionality', () => {
|
|
227
227
|
await nextTick();
|
228
228
|
|
229
229
|
expect(w).toHaveCounts({ effect: 2 });
|
230
|
-
expect(c).
|
230
|
+
expect(c).toHaveValueAndCounts(123, { get: 3, compute: 1 });
|
231
231
|
expect(s).toHaveValueAndCounts(123, {
|
232
232
|
subscribe: 1,
|
233
233
|
});
|
@@ -393,7 +393,7 @@ describe('Subscription Signal functionality', () => {
|
|
393
393
|
|
394
394
|
return {
|
395
395
|
update() {
|
396
|
-
set(a.get()
|
396
|
+
set(a.get());
|
397
397
|
},
|
398
398
|
};
|
399
399
|
},
|
@@ -419,7 +419,7 @@ describe('Subscription Signal functionality', () => {
|
|
419
419
|
|
420
420
|
await nextTick();
|
421
421
|
|
422
|
-
expect(s).toHaveValueAndCounts(
|
422
|
+
expect(s).toHaveValueAndCounts(1, {
|
423
423
|
subscribe: 1,
|
424
424
|
update: 1,
|
425
425
|
});
|
@@ -1,4 +1,4 @@
|
|
1
|
-
import { expect } from 'vitest';
|
1
|
+
import { expect, beforeEach, afterEach, vi } from 'vitest';
|
2
2
|
import {
|
3
3
|
state as createState,
|
4
4
|
computed as createComputed,
|
@@ -187,12 +187,12 @@ export function computed<T>(
|
|
187
187
|
return wrapper;
|
188
188
|
}
|
189
189
|
|
190
|
-
export function asyncComputed<T>(name: string, compute: SignalAsyncCompute<T>, opts?: SignalOptions<T>):
|
190
|
+
export function asyncComputed<T>(name: string, compute: SignalAsyncCompute<T>, opts?: SignalOptions<T>): AsyncSignal<T>;
|
191
191
|
export function asyncComputed<T>(
|
192
192
|
name: string,
|
193
193
|
compute: SignalAsyncCompute<T>,
|
194
194
|
opts: SignalOptionsWithInit<T>,
|
195
|
-
):
|
195
|
+
): AsyncSignal<T>;
|
196
196
|
export function asyncComputed<T>(compute: SignalAsyncCompute<T>, opts?: SignalOptions<T>): AsyncSignal<T>;
|
197
197
|
export function asyncComputed<T>(compute: SignalAsyncCompute<T>, opts: SignalOptionsWithInit<T>): AsyncSignal<T>;
|
198
198
|
export function asyncComputed<T>(
|
package/src/signals.ts
CHANGED
@@ -1,13 +1,10 @@
|
|
1
1
|
import { scheduleDisconnect, scheduleWatcher } from './scheduling.js';
|
2
2
|
|
3
|
+
let CURRENT_ORD = 0;
|
3
4
|
let CURRENT_CONSUMER: ComputedSignal<any> | undefined;
|
4
|
-
let CURRENT_DEP_TAIL: Link | undefined;
|
5
|
-
let CURRENT_ORD: number = 0;
|
6
|
-
let CURRENT_IS_WATCHED: boolean = false;
|
7
5
|
let CURRENT_IS_WAITING: boolean = false;
|
8
|
-
let CURRENT_SEEN: WeakSet<ComputedSignal<any>> | undefined;
|
9
6
|
|
10
|
-
let
|
7
|
+
let ID = 0;
|
11
8
|
|
12
9
|
const enum SignalType {
|
13
10
|
Computed,
|
@@ -61,141 +58,30 @@ const enum SignalState {
|
|
61
58
|
const WAITING = Symbol();
|
62
59
|
|
63
60
|
interface Link {
|
64
|
-
sub: WeakRef<ComputedSignal<any>>;
|
65
61
|
dep: ComputedSignal<any>;
|
62
|
+
sub: WeakRef<ComputedSignal<any>>;
|
66
63
|
ord: number;
|
67
64
|
version: number;
|
68
|
-
|
69
|
-
nextDep: Link | undefined;
|
70
|
-
nextSub: Link | undefined;
|
71
|
-
prevSub: Link | undefined;
|
65
|
+
consumedAt: number;
|
72
66
|
|
73
67
|
nextDirty: Link | undefined;
|
74
68
|
}
|
75
69
|
|
76
|
-
let linkPool: Link | undefined;
|
77
|
-
|
78
|
-
function linkNewDep(
|
79
|
-
dep: ComputedSignal<any>,
|
80
|
-
sub: ComputedSignal<any>,
|
81
|
-
nextDep: Link | undefined,
|
82
|
-
depsTail: Link | undefined,
|
83
|
-
ord: number,
|
84
|
-
): Link {
|
85
|
-
let newLink: Link;
|
86
|
-
|
87
|
-
if (linkPool !== undefined) {
|
88
|
-
newLink = linkPool;
|
89
|
-
linkPool = newLink.nextDep;
|
90
|
-
newLink.nextDep = nextDep;
|
91
|
-
newLink.dep = dep;
|
92
|
-
newLink.sub = sub._ref;
|
93
|
-
newLink.ord = ord;
|
94
|
-
} else {
|
95
|
-
newLink = {
|
96
|
-
dep,
|
97
|
-
sub: sub._ref,
|
98
|
-
ord,
|
99
|
-
version: 0,
|
100
|
-
nextDep,
|
101
|
-
nextDirty: undefined,
|
102
|
-
prevSub: undefined,
|
103
|
-
nextSub: undefined,
|
104
|
-
};
|
105
|
-
}
|
106
|
-
|
107
|
-
if (depsTail === undefined) {
|
108
|
-
sub._deps = newLink;
|
109
|
-
} else {
|
110
|
-
depsTail.nextDep = newLink;
|
111
|
-
}
|
112
|
-
|
113
|
-
if (dep._subs === undefined) {
|
114
|
-
dep._subs = newLink;
|
115
|
-
} else {
|
116
|
-
const oldTail = dep._subsTail!;
|
117
|
-
newLink.prevSub = oldTail;
|
118
|
-
oldTail.nextSub = newLink;
|
119
|
-
}
|
120
|
-
|
121
|
-
dep._subsTail = newLink;
|
122
|
-
|
123
|
-
return newLink;
|
124
|
-
}
|
125
|
-
|
126
|
-
function poolLink(link: Link) {
|
127
|
-
const dep = link.dep;
|
128
|
-
const nextSub = link.nextSub;
|
129
|
-
const prevSub = link.prevSub;
|
130
|
-
|
131
|
-
if (nextSub !== undefined) {
|
132
|
-
nextSub.prevSub = prevSub;
|
133
|
-
link.nextSub = undefined;
|
134
|
-
} else {
|
135
|
-
dep._subsTail = prevSub;
|
136
|
-
}
|
137
|
-
|
138
|
-
if (prevSub !== undefined) {
|
139
|
-
prevSub.nextSub = nextSub;
|
140
|
-
link.prevSub = undefined;
|
141
|
-
} else {
|
142
|
-
dep._subs = nextSub;
|
143
|
-
}
|
144
|
-
|
145
|
-
// @ts-expect-error - override to pool the value
|
146
|
-
link.dep = undefined;
|
147
|
-
// @ts-expect-error - override to pool the value
|
148
|
-
link.sub = undefined;
|
149
|
-
link.nextDep = linkPool;
|
150
|
-
linkPool = link;
|
151
|
-
|
152
|
-
link.prevSub = undefined;
|
153
|
-
}
|
154
|
-
|
155
|
-
export function endTrack(sub: ComputedSignal<any>, shouldDisconnect: boolean): void {
|
156
|
-
if (CURRENT_DEP_TAIL !== undefined) {
|
157
|
-
if (CURRENT_DEP_TAIL.nextDep !== undefined) {
|
158
|
-
clearTrack(CURRENT_DEP_TAIL.nextDep, shouldDisconnect);
|
159
|
-
CURRENT_DEP_TAIL.nextDep = undefined;
|
160
|
-
}
|
161
|
-
} else if (sub._deps !== undefined) {
|
162
|
-
clearTrack(sub._deps, shouldDisconnect);
|
163
|
-
sub._deps = undefined;
|
164
|
-
}
|
165
|
-
}
|
166
|
-
|
167
|
-
function clearTrack(link: Link, shouldDisconnect: boolean): void {
|
168
|
-
do {
|
169
|
-
const nextDep = link.nextDep;
|
170
|
-
|
171
|
-
if (shouldDisconnect) {
|
172
|
-
scheduleDisconnect(link.dep);
|
173
|
-
}
|
174
|
-
|
175
|
-
poolLink(link);
|
176
|
-
|
177
|
-
link = nextDep!;
|
178
|
-
} while (link !== undefined);
|
179
|
-
}
|
180
|
-
|
181
70
|
export class ComputedSignal<T> {
|
182
|
-
|
71
|
+
_id = ID++;
|
183
72
|
_type: SignalType;
|
184
73
|
|
185
|
-
|
186
|
-
_subsTail: Link | undefined;
|
187
|
-
|
188
|
-
_deps: Link | undefined;
|
189
|
-
_dirtyDep: Link | undefined;
|
74
|
+
_deps = new Map<ComputedSignal<any>, Link>();
|
190
75
|
|
76
|
+
_dirtyDep: Link | undefined = undefined;
|
77
|
+
_subs = new Set<Link>();
|
191
78
|
_state: SignalState = SignalState.Dirty;
|
192
|
-
|
193
79
|
_version: number = 0;
|
194
|
-
|
195
|
-
_connectedCount: number;
|
196
|
-
|
80
|
+
_computedCount: number = 0;
|
81
|
+
_connectedCount: number = 0;
|
197
82
|
_currentValue: T | AsyncResult<T> | undefined;
|
198
83
|
_compute: SignalCompute<T> | SignalAsyncCompute<T> | SignalSubscribe<T> | undefined;
|
84
|
+
|
199
85
|
_equals: SignalEquals<T>;
|
200
86
|
_ref: WeakRef<ComputedSignal<T>> = new WeakRef(this);
|
201
87
|
|
@@ -253,43 +139,33 @@ export class ComputedSignal<T> {
|
|
253
139
|
}
|
254
140
|
|
255
141
|
get(): T | AsyncResult<T> {
|
256
|
-
|
142
|
+
if (CURRENT_CONSUMER !== undefined) {
|
143
|
+
const { _deps: deps, _computedCount: computedCount, _connectedCount: connectedCount } = CURRENT_CONSUMER;
|
144
|
+
const prevLink = deps.get(this);
|
257
145
|
|
258
|
-
if (CURRENT_CONSUMER !== undefined && this._type !== SignalType.Watcher && !CURRENT_SEEN!.has(this)) {
|
259
146
|
const ord = CURRENT_ORD++;
|
260
147
|
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
break;
|
282
|
-
}
|
283
|
-
|
284
|
-
newLink = newLink.nextDep;
|
148
|
+
this._check(!prevLink && connectedCount > 0);
|
149
|
+
|
150
|
+
if (prevLink === undefined) {
|
151
|
+
const newLink = {
|
152
|
+
dep: this,
|
153
|
+
sub: CURRENT_CONSUMER._ref,
|
154
|
+
ord,
|
155
|
+
version: this._version,
|
156
|
+
consumedAt: CURRENT_CONSUMER._computedCount,
|
157
|
+
nextDirty: undefined,
|
158
|
+
};
|
159
|
+
|
160
|
+
deps.set(this, newLink);
|
161
|
+
this._subs.add(newLink);
|
162
|
+
} else if (prevLink.consumedAt !== computedCount) {
|
163
|
+
prevLink.ord = ord;
|
164
|
+
prevLink.version = this._version;
|
165
|
+
prevLink.consumedAt = computedCount;
|
166
|
+
// prevLink.nextDirty = undefined;
|
167
|
+
this._subs.add(prevLink);
|
285
168
|
}
|
286
|
-
|
287
|
-
this._check(CURRENT_IS_WATCHED && !prevTracked);
|
288
|
-
|
289
|
-
CURRENT_DEP_TAIL = newLink ?? linkNewDep(this, CURRENT_CONSUMER, nextDep, CURRENT_DEP_TAIL, ord);
|
290
|
-
|
291
|
-
CURRENT_DEP_TAIL.version = this._version;
|
292
|
-
CURRENT_SEEN!.add(this);
|
293
169
|
} else {
|
294
170
|
this._check();
|
295
171
|
}
|
@@ -298,6 +174,7 @@ export class ComputedSignal<T> {
|
|
298
174
|
}
|
299
175
|
|
300
176
|
_check(shouldWatch = false): number {
|
177
|
+
// COUNTS.checks++;
|
301
178
|
let state = this._state;
|
302
179
|
let connectedCount = this._connectedCount;
|
303
180
|
|
@@ -312,21 +189,13 @@ export class ComputedSignal<T> {
|
|
312
189
|
if (this._type === SignalType.Subscription) {
|
313
190
|
state = SignalState.Dirty;
|
314
191
|
} else {
|
315
|
-
|
316
|
-
|
317
|
-
while (link !== undefined) {
|
318
|
-
const dep = link.dep;
|
319
|
-
|
192
|
+
for (const [dep, link] of this._deps) {
|
320
193
|
if (link.version !== dep._check(true)) {
|
321
194
|
state = SignalState.Dirty;
|
322
195
|
break;
|
323
196
|
}
|
324
|
-
|
325
|
-
link = link.nextDep;
|
326
197
|
}
|
327
198
|
}
|
328
|
-
|
329
|
-
this._resetDirty();
|
330
199
|
}
|
331
200
|
|
332
201
|
if (state === SignalState.Clean) {
|
@@ -349,7 +218,7 @@ export class ComputedSignal<T> {
|
|
349
218
|
}
|
350
219
|
|
351
220
|
if (state === SignalState.Dirty) {
|
352
|
-
this._run(wasConnected,
|
221
|
+
this._run(wasConnected, shouldConnect);
|
353
222
|
} else {
|
354
223
|
this._resetDirty();
|
355
224
|
}
|
@@ -360,22 +229,16 @@ export class ComputedSignal<T> {
|
|
360
229
|
return this._version;
|
361
230
|
}
|
362
231
|
|
363
|
-
_run(wasConnected: boolean,
|
232
|
+
_run(wasConnected: boolean, shouldConnect: boolean) {
|
364
233
|
const { _type: type } = this;
|
365
234
|
|
366
235
|
const prevConsumer = CURRENT_CONSUMER;
|
367
|
-
const prevOrd = CURRENT_ORD;
|
368
|
-
const prevSeen = CURRENT_SEEN;
|
369
|
-
const prevDepTail = CURRENT_DEP_TAIL;
|
370
|
-
const prevIsWatched = CURRENT_IS_WATCHED;
|
371
236
|
|
372
237
|
try {
|
373
238
|
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
374
239
|
CURRENT_CONSUMER = this;
|
375
|
-
|
376
|
-
|
377
|
-
CURRENT_DEP_TAIL = undefined;
|
378
|
-
CURRENT_IS_WATCHED = isConnected;
|
240
|
+
|
241
|
+
this._computedCount++;
|
379
242
|
|
380
243
|
switch (type) {
|
381
244
|
case SignalType.Computed: {
|
@@ -403,9 +266,8 @@ export class ComputedSignal<T> {
|
|
403
266
|
value.isPending = false;
|
404
267
|
value.isError = true;
|
405
268
|
this._version++;
|
269
|
+
break;
|
406
270
|
}
|
407
|
-
|
408
|
-
break;
|
409
271
|
}
|
410
272
|
|
411
273
|
if (CURRENT_IS_WAITING) {
|
@@ -504,32 +366,32 @@ export class ComputedSignal<T> {
|
|
504
366
|
}
|
505
367
|
}
|
506
368
|
} finally {
|
507
|
-
|
369
|
+
const deps = this._deps;
|
370
|
+
|
371
|
+
for (const link of deps.values()) {
|
372
|
+
if (link.consumedAt === this._computedCount) continue;
|
373
|
+
|
374
|
+
const dep = link.dep;
|
375
|
+
|
376
|
+
if (wasConnected) {
|
377
|
+
scheduleDisconnect(dep);
|
378
|
+
}
|
379
|
+
|
380
|
+
deps.delete(dep);
|
381
|
+
dep._subs.delete(link);
|
382
|
+
}
|
508
383
|
|
509
384
|
CURRENT_CONSUMER = prevConsumer;
|
510
|
-
CURRENT_SEEN = prevSeen;
|
511
|
-
CURRENT_DEP_TAIL = prevDepTail;
|
512
|
-
CURRENT_ORD = prevOrd;
|
513
|
-
CURRENT_IS_WATCHED = prevIsWatched;
|
514
385
|
}
|
515
386
|
}
|
516
387
|
|
517
388
|
_resetDirty() {
|
518
389
|
let dirty = this._dirtyDep;
|
390
|
+
// COUNTS.dirtyResetIterations++;
|
519
391
|
|
520
392
|
while (dirty !== undefined) {
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
if (oldHead === undefined) {
|
525
|
-
dep._subs = dirty;
|
526
|
-
dirty.nextSub = undefined;
|
527
|
-
dirty.prevSub = undefined;
|
528
|
-
} else {
|
529
|
-
dirty.nextSub = oldHead;
|
530
|
-
oldHead.prevSub = dirty;
|
531
|
-
dep._subs = dirty;
|
532
|
-
}
|
393
|
+
// COUNTS.dirtyResetIterations++;
|
394
|
+
dirty.dep._subs.add(dirty);
|
533
395
|
|
534
396
|
let nextDirty = dirty.nextDirty;
|
535
397
|
dirty.nextDirty = undefined;
|
@@ -549,59 +411,43 @@ export class ComputedSignal<T> {
|
|
549
411
|
} else {
|
550
412
|
this._dirtyConsumers();
|
551
413
|
}
|
552
|
-
|
553
|
-
this._subs = undefined;
|
554
414
|
}
|
555
415
|
|
556
416
|
_dirtyConsumers() {
|
557
|
-
|
558
|
-
|
559
|
-
|
560
|
-
|
561
|
-
|
562
|
-
|
563
|
-
|
564
|
-
|
565
|
-
|
566
|
-
|
567
|
-
|
568
|
-
|
569
|
-
|
570
|
-
|
571
|
-
|
572
|
-
|
573
|
-
|
574
|
-
|
575
|
-
|
576
|
-
|
577
|
-
|
578
|
-
let dirty = consumer._dirtyDep;
|
579
|
-
const ord = link.ord;
|
580
|
-
|
581
|
-
if (dirty!.ord > ord) {
|
582
|
-
consumer._dirtyDep = link;
|
583
|
-
link.nextDirty = dirty;
|
584
|
-
} else {
|
585
|
-
let nextDirty = dirty!.nextDirty;
|
586
|
-
|
587
|
-
while (nextDirty !== undefined && nextDirty!.ord < ord) {
|
588
|
-
dirty = nextDirty;
|
589
|
-
nextDirty = dirty.nextDirty;
|
417
|
+
for (const link of this._subs.values()) {
|
418
|
+
const sub = link.sub.deref();
|
419
|
+
|
420
|
+
if (sub === undefined) continue;
|
421
|
+
|
422
|
+
switch (sub._state) {
|
423
|
+
case SignalState.MaybeDirty: {
|
424
|
+
let dirty = sub._dirtyDep;
|
425
|
+
const ord = link.ord;
|
426
|
+
if (dirty!.ord > ord) {
|
427
|
+
sub._dirtyDep = link;
|
428
|
+
link.nextDirty = dirty;
|
429
|
+
} else {
|
430
|
+
let nextDirty = dirty!.nextDirty;
|
431
|
+
while (nextDirty !== undefined && nextDirty!.ord < ord) {
|
432
|
+
// COUNTS.dirtyInsertIterations++;
|
433
|
+
dirty = nextDirty;
|
434
|
+
nextDirty = dirty.nextDirty;
|
435
|
+
}
|
436
|
+
link.nextDirty = nextDirty;
|
437
|
+
dirty!.nextDirty = link;
|
590
438
|
}
|
591
|
-
|
592
|
-
|
593
|
-
|
439
|
+
break;
|
440
|
+
}
|
441
|
+
case SignalState.Clean: {
|
442
|
+
sub._state = SignalState.MaybeDirty;
|
443
|
+
sub._dirtyDep = link;
|
444
|
+
link.nextDirty = undefined;
|
445
|
+
sub._dirty();
|
594
446
|
}
|
595
|
-
} else {
|
596
|
-
// consumer._dirtyQueueLength = dirtyQueueLength + 2;
|
597
|
-
consumer._state = SignalState.MaybeDirty;
|
598
|
-
consumer._dirtyDep = link;
|
599
|
-
link.nextDirty = undefined;
|
600
|
-
consumer._dirty();
|
601
447
|
}
|
602
|
-
|
603
|
-
link = link.nextSub;
|
604
448
|
}
|
449
|
+
|
450
|
+
this._subs = new Set();
|
605
451
|
}
|
606
452
|
|
607
453
|
_disconnect(count = 1) {
|
@@ -622,14 +468,10 @@ export class ComputedSignal<T> {
|
|
622
468
|
}
|
623
469
|
}
|
624
470
|
|
625
|
-
|
626
|
-
|
627
|
-
while (link !== undefined) {
|
471
|
+
for (const link of this._deps.values()) {
|
628
472
|
const dep = link.dep;
|
629
473
|
|
630
474
|
dep._disconnect();
|
631
|
-
|
632
|
-
link = link.nextDep;
|
633
475
|
}
|
634
476
|
}
|
635
477
|
}
|
@@ -662,7 +504,7 @@ export interface AsyncReady<T> extends AsyncBaseResult<T> {
|
|
662
504
|
export type AsyncResult<T> = AsyncPending<T> | AsyncReady<T>;
|
663
505
|
|
664
506
|
class StateSignal<T> implements StateSignal<T> {
|
665
|
-
private
|
507
|
+
private _subs: WeakRef<ComputedSignal<unknown>>[] = [];
|
666
508
|
|
667
509
|
constructor(
|
668
510
|
private _value: T,
|
@@ -671,7 +513,7 @@ class StateSignal<T> implements StateSignal<T> {
|
|
671
513
|
|
672
514
|
get(): T {
|
673
515
|
if (CURRENT_CONSUMER !== undefined) {
|
674
|
-
this.
|
516
|
+
this._subs.push(CURRENT_CONSUMER._ref);
|
675
517
|
}
|
676
518
|
|
677
519
|
return this._value!;
|
@@ -683,21 +525,28 @@ class StateSignal<T> implements StateSignal<T> {
|
|
683
525
|
}
|
684
526
|
|
685
527
|
this._value = value;
|
528
|
+
const subs = this._subs;
|
529
|
+
const subsLength = subs.length;
|
686
530
|
|
687
|
-
|
688
|
-
|
689
|
-
for (const consumerRef of consumers) {
|
690
|
-
const consumer = consumerRef.deref();
|
531
|
+
for (let i = 0; i < subsLength; i++) {
|
532
|
+
const sub = subs[i].deref();
|
691
533
|
|
692
|
-
if (
|
534
|
+
if (sub === undefined) {
|
693
535
|
continue;
|
694
536
|
}
|
695
537
|
|
696
|
-
|
697
|
-
|
538
|
+
switch (sub._state) {
|
539
|
+
case SignalState.Clean:
|
540
|
+
sub._state = SignalState.Dirty;
|
541
|
+
sub._dirty();
|
542
|
+
break;
|
543
|
+
case SignalState.MaybeDirty:
|
544
|
+
sub._state = SignalState.Dirty;
|
545
|
+
break;
|
546
|
+
}
|
698
547
|
}
|
699
548
|
|
700
|
-
|
549
|
+
this._subs = [];
|
701
550
|
}
|
702
551
|
}
|
703
552
|
|
@@ -733,7 +582,7 @@ export interface Watcher {
|
|
733
582
|
}
|
734
583
|
|
735
584
|
export function watcher(fn: () => void): Watcher {
|
736
|
-
const subscribers
|
585
|
+
const subscribers: (() => void)[] = [];
|
737
586
|
const watcher = new ComputedSignal(SignalType.Watcher, () => {
|
738
587
|
fn();
|
739
588
|
|
@@ -752,10 +601,10 @@ export function watcher(fn: () => void): Watcher {
|
|
752
601
|
},
|
753
602
|
|
754
603
|
subscribe(subscriber: () => void) {
|
755
|
-
subscribers.
|
604
|
+
subscribers.push(subscriber);
|
756
605
|
|
757
606
|
return () => {
|
758
|
-
subscribers.
|
607
|
+
subscribers.splice(subscribers.indexOf(subscriber), 1);
|
759
608
|
};
|
760
609
|
},
|
761
610
|
};
|
@@ -767,19 +616,13 @@ export function isTracking(): boolean {
|
|
767
616
|
|
768
617
|
export function untrack<T = void>(fn: () => T): T {
|
769
618
|
const prevConsumer = CURRENT_CONSUMER;
|
770
|
-
const prevOrd = CURRENT_ORD;
|
771
|
-
const prevIsWatched = CURRENT_IS_WATCHED;
|
772
619
|
|
773
620
|
try {
|
774
621
|
CURRENT_CONSUMER = undefined;
|
775
622
|
// LAST_CONSUMED = undefined;
|
776
|
-
CURRENT_ORD = 0;
|
777
|
-
CURRENT_IS_WATCHED = false;
|
778
623
|
|
779
624
|
return fn();
|
780
625
|
} finally {
|
781
626
|
CURRENT_CONSUMER = prevConsumer;
|
782
|
-
CURRENT_ORD = prevOrd;
|
783
|
-
CURRENT_IS_WATCHED = prevIsWatched;
|
784
627
|
}
|
785
628
|
}
|