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 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
- nextDep: Link | undefined;
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
- id: number;
43
+ _id: number;
47
44
  _type: SignalType;
48
- _subs: Link | undefined;
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, isConnected: boolean, shouldConnect: boolean): void;
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 _consumers;
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 CURRENT_IS_WATCHED = false;
3
+ let CURRENT_CONSUMER;
6
4
  let CURRENT_IS_WAITING = false;
7
- let CURRENT_SEEN;
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
- id = id++;
10
+ _id = ID++;
102
11
  _type;
103
- _subs;
104
- _subsTail;
105
- _deps;
106
- _dirtyDep;
12
+ _deps = new Map();
13
+ _dirtyDep = undefined;
14
+ _subs = new Set();
107
15
  _state = 2 /* SignalState.Dirty */;
108
16
  _version = 0;
109
- _connectedCount;
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
- let prevTracked = false;
154
- if (CURRENT_CONSUMER !== undefined && this._type !== 3 /* SignalType.Watcher */ && !CURRENT_SEEN.has(this)) {
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
- const nextDep = CURRENT_DEP_TAIL === undefined ? CURRENT_CONSUMER._deps : CURRENT_DEP_TAIL.nextDep;
157
- let newLink = nextDep;
158
- while (newLink !== undefined) {
159
- if (newLink.dep === this) {
160
- prevTracked = true;
161
- if (CURRENT_DEP_TAIL === undefined) {
162
- CURRENT_CONSUMER._deps = newLink;
163
- }
164
- else {
165
- CURRENT_DEP_TAIL.nextDep = newLink;
166
- }
167
- newLink.ord = ord;
168
- newLink.nextDirty = undefined;
169
- if (this._subs === undefined) {
170
- this._subs = newLink;
171
- }
172
- break;
173
- }
174
- newLink = newLink.nextDep;
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
- let link = this._deps;
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, connectedCount > 0, shouldConnect);
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, isConnected, shouldConnect) {
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
- CURRENT_ORD = 0;
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
- endTrack(this, wasConnected);
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
- const dep = dirty.dep;
367
- const oldHead = dep._subs;
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
- let link = this._subs;
400
- while (link !== undefined) {
401
- const consumer = link.sub.deref();
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
- if (state === 1 /* SignalState.MaybeDirty */) {
415
- let dirty = consumer._dirtyDep;
416
- const ord = link.ord;
417
- if (dirty.ord > ord) {
418
- consumer._dirtyDep = link;
419
- link.nextDirty = dirty;
420
- }
421
- else {
422
- let nextDirty = dirty.nextDirty;
423
- while (nextDirty !== undefined && nextDirty.ord < ord) {
424
- dirty = nextDirty;
425
- nextDirty = dirty.nextDirty;
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
- link.nextDirty = nextDirty;
428
- dirty.nextDirty = link;
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
- let link = this._deps;
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
- _consumers = [];
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._consumers.push(CURRENT_CONSUMER._ref);
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 { _consumers: consumers } = this;
484
- for (const consumerRef of consumers) {
485
- const consumer = consumerRef.deref();
486
- if (consumer === undefined) {
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
- consumer._state = 2 /* SignalState.Dirty */;
490
- consumer._dirty();
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
- consumers.length = 0;
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 = new Set();
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.add(subscriber);
413
+ subscribers.push(subscriber);
524
414
  return () => {
525
- subscribers.delete(subscriber);
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,6 +1,6 @@
1
1
  {
2
2
  "name": "signalium",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "type": "module",
5
5
  "repository": "https://github.com/pzuraq/signalium",
6
6
  "description": "Chain-reactivity at critical mass",
@@ -1,5 +1,5 @@
1
1
  import { describe, expect, test } from 'vitest';
2
- import { state, computed, asyncComputed } from './utils/instrumented.js';
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(100);
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(200);
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(50);
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(60);
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(20);
214
+ await sleep(10);
215
215
 
216
216
  return 1;
217
217
  });
218
218
 
219
219
  const compB = asyncComputed(async () => {
220
- await sleep(20);
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(30);
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(30);
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(20);
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(50);
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).toHaveCounts({ get: 2, compute: 1 });
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() + 1);
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(2, {
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>): Signal<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
- ): Signal<T>;
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 id = 0;
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
- id = id++;
71
+ _id = ID++;
183
72
  _type: SignalType;
184
73
 
185
- _subs: Link | undefined;
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
- let prevTracked = false;
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
- const nextDep = CURRENT_DEP_TAIL === undefined ? CURRENT_CONSUMER._deps : CURRENT_DEP_TAIL.nextDep;
262
- let newLink: Link | undefined = nextDep;
263
-
264
- while (newLink !== undefined) {
265
- if (newLink.dep === this) {
266
- prevTracked = true;
267
-
268
- if (CURRENT_DEP_TAIL === undefined) {
269
- CURRENT_CONSUMER._deps = newLink;
270
- } else {
271
- CURRENT_DEP_TAIL.nextDep = newLink;
272
- }
273
-
274
- newLink.ord = ord;
275
- newLink.nextDirty = undefined;
276
-
277
- if (this._subs === undefined) {
278
- this._subs = newLink;
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
- let link = this._deps;
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, connectedCount > 0, shouldConnect);
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, isConnected: boolean, shouldConnect: 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
- CURRENT_ORD = 0;
376
- CURRENT_SEEN = new WeakSet();
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
- endTrack(this, wasConnected);
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
- const dep = dirty.dep;
522
- const oldHead = dep._subs;
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
- let link = this._subs;
558
-
559
- while (link !== undefined) {
560
- const consumer = link.sub.deref();
561
-
562
- if (consumer === undefined) {
563
- const nextSub = link.nextSub;
564
- poolLink(link);
565
- link = nextSub;
566
- continue;
567
- }
568
-
569
- const state = consumer._state;
570
-
571
- if (state === SignalState.Dirty) {
572
- const nextSub = link.nextSub;
573
- link = nextSub;
574
- continue;
575
- }
576
-
577
- if (state === SignalState.MaybeDirty) {
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
- link.nextDirty = nextDirty;
593
- dirty!.nextDirty = link;
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
- let link = this._deps;
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 _consumers: WeakRef<ComputedSignal<unknown>>[] = [];
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._consumers.push(CURRENT_CONSUMER._ref);
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
- const { _consumers: consumers } = this;
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 (consumer === undefined) {
534
+ if (sub === undefined) {
693
535
  continue;
694
536
  }
695
537
 
696
- consumer._state = SignalState.Dirty;
697
- consumer._dirty();
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
- consumers.length = 0;
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 = new Set<() => void>();
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.add(subscriber);
604
+ subscribers.push(subscriber);
756
605
 
757
606
  return () => {
758
- subscribers.delete(subscriber);
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
  }