signalium 0.2.2 → 0.2.4

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/dist/signals.js CHANGED
@@ -1,148 +1,22 @@
1
- import { scheduleDisconnect, scheduleWatcher } from './scheduling.js';
2
- let CURRENT_CONSUMER;
3
- let CURRENT_DEP_TAIL;
1
+ import { scheduleDirty, scheduleDisconnect, schedulePull, scheduleWatcher } from './scheduling.js';
2
+ import WeakRef from './weakref.js';
4
3
  let CURRENT_ORD = 0;
5
- let CURRENT_IS_WATCHED = false;
4
+ let CURRENT_CONSUMER;
6
5
  let CURRENT_IS_WAITING = false;
7
- let CURRENT_SEEN;
8
- let id = 0;
6
+ let ID = 0;
9
7
  const SUBSCRIPTIONS = new WeakMap();
10
8
  const ACTIVE_ASYNCS = new WeakMap();
11
9
  const WAITING = Symbol();
12
- let linkPool;
13
- const checkForCircularLinks = (link) => {
14
- if (!link)
15
- return;
16
- for (const key of ['nextDep', 'nextSub', 'prevSub', 'nextDirty']) {
17
- let currentLink = link?.[key];
18
- while (currentLink !== undefined) {
19
- if (currentLink === link) {
20
- throw new Error(`Circular link detected via ${key}. This is a bug, please report it to the Signalium maintainers.`);
21
- }
22
- currentLink = currentLink[key];
23
- }
24
- }
25
- };
26
- const typeToString = (type) => {
27
- switch (type) {
28
- case 0 /* SignalType.Computed */:
29
- return 'Computed';
30
- case 1 /* SignalType.Subscription */:
31
- return 'Subscription';
32
- case 2 /* SignalType.Async */:
33
- return 'Async';
34
- case 3 /* SignalType.Watcher */:
35
- return 'Watcher';
36
- }
37
- };
38
- const printComputed = (computed) => {
39
- const type = typeToString(computed._type);
40
- return `ComputedSignal<${type}:${computed.id}>`;
41
- };
42
- const printLink = (link) => {
43
- const sub = link.sub.deref();
44
- const subStr = sub === undefined ? 'undefined' : printComputed(sub);
45
- const depStr = printComputed(link.dep);
46
- return `Link<${link.id}> sub(${subStr}) -> dep(${depStr})`;
47
- };
48
- function linkNewDep(dep, sub, nextDep, depsTail, ord) {
49
- let newLink;
50
- if (linkPool !== undefined) {
51
- newLink = linkPool;
52
- linkPool = newLink.nextDep;
53
- newLink.nextDep = nextDep;
54
- newLink.dep = dep;
55
- newLink.sub = sub._ref;
56
- newLink.ord = ord;
57
- }
58
- else {
59
- newLink = {
60
- id: id++,
61
- dep,
62
- sub: sub._ref,
63
- ord,
64
- version: 0,
65
- nextDep,
66
- nextDirty: undefined,
67
- prevSub: undefined,
68
- nextSub: undefined,
69
- };
70
- }
71
- if (depsTail === undefined) {
72
- sub._deps = newLink;
73
- }
74
- else {
75
- depsTail.nextDep = newLink;
76
- }
77
- if (dep._subs === undefined) {
78
- dep._subs = newLink;
79
- }
80
- else {
81
- const oldTail = dep._subsTail;
82
- newLink.prevSub = oldTail;
83
- oldTail.nextSub = newLink;
84
- }
85
- dep._subsTail = newLink;
86
- return newLink;
87
- }
88
- function poolLink(link) {
89
- const dep = link.dep;
90
- const nextSub = link.nextSub;
91
- const prevSub = link.prevSub;
92
- if (nextSub !== undefined) {
93
- nextSub.prevSub = prevSub;
94
- link.nextSub = undefined;
95
- }
96
- else {
97
- dep._subsTail = prevSub;
98
- }
99
- if (prevSub !== undefined) {
100
- prevSub.nextSub = nextSub;
101
- link.prevSub = undefined;
102
- }
103
- else {
104
- dep._subs = nextSub;
105
- }
106
- // @ts-expect-error - override to pool the value
107
- link.dep = undefined;
108
- // @ts-expect-error - override to pool the value
109
- link.sub = undefined;
110
- link.nextDep = linkPool;
111
- linkPool = link;
112
- link.prevSub = undefined;
113
- }
114
- export function endTrack(sub, shouldDisconnect) {
115
- if (CURRENT_DEP_TAIL !== undefined) {
116
- if (CURRENT_DEP_TAIL.nextDep !== undefined) {
117
- clearTrack(CURRENT_DEP_TAIL.nextDep, shouldDisconnect);
118
- CURRENT_DEP_TAIL.nextDep = undefined;
119
- }
120
- }
121
- else if (sub._deps !== undefined) {
122
- clearTrack(sub._deps, shouldDisconnect);
123
- sub._deps = undefined;
124
- }
125
- }
126
- function clearTrack(link, shouldDisconnect) {
127
- do {
128
- const nextDep = link.nextDep;
129
- if (shouldDisconnect) {
130
- scheduleDisconnect(link.dep);
131
- }
132
- poolLink(link);
133
- link = nextDep;
134
- } while (link !== undefined);
135
- }
136
10
  export class ComputedSignal {
137
- id = id++;
11
+ _id = ID++;
138
12
  _type;
139
- _subs;
140
- _subsTail;
141
- _deps;
142
- _dirtyDep;
13
+ _deps = new Map();
14
+ _dirtyDep = undefined;
15
+ _subs = new Set();
143
16
  _state = 2 /* SignalState.Dirty */;
144
17
  _version = 0;
145
- _connectedCount;
18
+ _computedCount = 0;
19
+ _connectedCount = 0;
146
20
  _currentValue;
147
21
  _compute;
148
22
  _equals;
@@ -174,7 +48,7 @@ export class ComputedSignal {
174
48
  const value = this._currentValue;
175
49
  if (value.isPending) {
176
50
  const currentConsumer = CURRENT_CONSUMER;
177
- ACTIVE_ASYNCS.get(this)?.finally(() => currentConsumer._check());
51
+ ACTIVE_ASYNCS.get(this)?.finally(() => schedulePull(currentConsumer));
178
52
  CURRENT_IS_WAITING = true;
179
53
  throw WAITING;
180
54
  }
@@ -186,35 +60,30 @@ export class ComputedSignal {
186
60
  };
187
61
  }
188
62
  get() {
189
- let prevTracked = false;
190
- if (CURRENT_CONSUMER !== undefined && this._type !== 3 /* SignalType.Watcher */ && !CURRENT_SEEN.has(this)) {
63
+ if (CURRENT_CONSUMER !== undefined) {
64
+ const { _deps: deps, _computedCount: computedCount, _connectedCount: connectedCount } = CURRENT_CONSUMER;
65
+ const prevLink = deps.get(this);
191
66
  const ord = CURRENT_ORD++;
192
- const nextDep = CURRENT_DEP_TAIL === undefined ? CURRENT_CONSUMER._deps : CURRENT_DEP_TAIL.nextDep;
193
- let newLink = nextDep;
194
- while (newLink !== undefined) {
195
- if (newLink.dep === this) {
196
- prevTracked = true;
197
- if (CURRENT_DEP_TAIL === undefined) {
198
- CURRENT_CONSUMER._deps = newLink;
199
- }
200
- else {
201
- CURRENT_DEP_TAIL.nextDep = newLink;
202
- }
203
- newLink.ord = ord;
204
- newLink.nextDirty = undefined;
205
- if (this._subs === undefined) {
206
- this._subs = newLink;
207
- }
208
- break;
209
- }
210
- newLink = newLink.nextDep;
67
+ this._check(!prevLink && connectedCount > 0);
68
+ if (prevLink === undefined) {
69
+ const newLink = {
70
+ dep: this,
71
+ sub: CURRENT_CONSUMER._ref,
72
+ ord,
73
+ version: this._version,
74
+ consumedAt: CURRENT_CONSUMER._computedCount,
75
+ nextDirty: undefined,
76
+ };
77
+ deps.set(this, newLink);
78
+ this._subs.add(newLink);
79
+ }
80
+ else if (prevLink.consumedAt !== computedCount) {
81
+ prevLink.ord = ord;
82
+ prevLink.version = this._version;
83
+ prevLink.consumedAt = computedCount;
84
+ // prevLink.nextDirty = undefined;
85
+ this._subs.add(prevLink);
211
86
  }
212
- this._check(CURRENT_IS_WATCHED && !prevTracked);
213
- CURRENT_DEP_TAIL = newLink ?? linkNewDep(this, CURRENT_CONSUMER, nextDep, CURRENT_DEP_TAIL, ord);
214
- if (process.env.NODE_ENV !== 'production')
215
- checkForCircularLinks(CURRENT_DEP_TAIL);
216
- CURRENT_DEP_TAIL.version = this._version;
217
- CURRENT_SEEN.add(this);
218
87
  }
219
88
  else {
220
89
  this._check();
@@ -222,6 +91,7 @@ export class ComputedSignal {
222
91
  return this._currentValue;
223
92
  }
224
93
  _check(shouldWatch = false) {
94
+ // COUNTS.checks++;
225
95
  let state = this._state;
226
96
  let connectedCount = this._connectedCount;
227
97
  const wasConnected = connectedCount > 0;
@@ -234,16 +104,11 @@ export class ComputedSignal {
234
104
  state = 2 /* SignalState.Dirty */;
235
105
  }
236
106
  else {
237
- let link = this._deps;
238
- if (process.env.NODE_ENV !== 'production')
239
- checkForCircularLinks(link);
240
- while (link !== undefined) {
241
- const dep = link.dep;
107
+ for (const [dep, link] of this._deps) {
242
108
  if (link.version !== dep._check(true)) {
243
109
  state = 2 /* SignalState.Dirty */;
244
110
  break;
245
111
  }
246
- link = link.nextDep;
247
112
  }
248
113
  }
249
114
  }
@@ -262,7 +127,7 @@ export class ComputedSignal {
262
127
  }
263
128
  }
264
129
  if (state === 2 /* SignalState.Dirty */) {
265
- this._run(wasConnected, connectedCount > 0, shouldConnect);
130
+ this._run(wasConnected, shouldConnect);
266
131
  }
267
132
  else {
268
133
  this._resetDirty();
@@ -271,20 +136,13 @@ export class ComputedSignal {
271
136
  this._dirtyDep = undefined;
272
137
  return this._version;
273
138
  }
274
- _run(wasConnected, isConnected, shouldConnect) {
139
+ _run(wasConnected, shouldConnect) {
275
140
  const { _type: type } = this;
276
141
  const prevConsumer = CURRENT_CONSUMER;
277
- const prevOrd = CURRENT_ORD;
278
- const prevSeen = CURRENT_SEEN;
279
- const prevDepTail = CURRENT_DEP_TAIL;
280
- const prevIsWatched = CURRENT_IS_WATCHED;
281
142
  try {
282
143
  // eslint-disable-next-line @typescript-eslint/no-this-alias
283
144
  CURRENT_CONSUMER = this;
284
- CURRENT_ORD = 0;
285
- CURRENT_SEEN = new WeakSet();
286
- CURRENT_DEP_TAIL = undefined;
287
- CURRENT_IS_WATCHED = isConnected;
145
+ this._computedCount++;
288
146
  switch (type) {
289
147
  case 0 /* SignalType.Computed */: {
290
148
  const prevValue = this._currentValue;
@@ -341,7 +199,7 @@ export class ComputedSignal {
341
199
  value.isPending = false;
342
200
  value.isSuccess = true;
343
201
  this._version++;
344
- this._dirtyConsumers();
202
+ scheduleDirty(this);
345
203
  }, error => {
346
204
  if (currentVersion !== this._version || error === WAITING) {
347
205
  return;
@@ -350,7 +208,7 @@ export class ComputedSignal {
350
208
  value.isPending = false;
351
209
  value.isError = true;
352
210
  this._version++;
353
- this._dirtyConsumers();
211
+ scheduleDirty(this);
354
212
  });
355
213
  ACTIVE_ASYNCS.set(this, nextValue);
356
214
  value.isPending = true;
@@ -391,39 +249,30 @@ export class ComputedSignal {
391
249
  }
392
250
  }
393
251
  finally {
394
- endTrack(this, wasConnected);
252
+ const deps = this._deps;
253
+ for (const link of deps.values()) {
254
+ if (link.consumedAt === this._computedCount)
255
+ continue;
256
+ const dep = link.dep;
257
+ if (wasConnected) {
258
+ scheduleDisconnect(dep);
259
+ }
260
+ deps.delete(dep);
261
+ dep._subs.delete(link);
262
+ }
395
263
  CURRENT_CONSUMER = prevConsumer;
396
- CURRENT_SEEN = prevSeen;
397
- CURRENT_DEP_TAIL = prevDepTail;
398
- CURRENT_ORD = prevOrd;
399
- CURRENT_IS_WATCHED = prevIsWatched;
400
264
  }
401
265
  }
402
266
  _resetDirty() {
403
267
  let dirty = this._dirtyDep;
268
+ // COUNTS.dirtyResetIterations++;
404
269
  while (dirty !== undefined) {
405
- const dep = dirty.dep;
406
- const oldHead = dep._subs;
407
- if (oldHead === undefined) {
408
- dep._subs = dirty;
409
- dirty.nextSub = undefined;
410
- dirty.prevSub = undefined;
411
- }
412
- else {
413
- dirty.nextSub = oldHead;
414
- dirty.prevSub = undefined;
415
- oldHead.prevSub = dirty;
416
- dep._subs = dirty;
417
- }
418
- if (process.env.NODE_ENV !== 'production') {
419
- checkForCircularLinks(this._dirtyDep);
420
- }
270
+ // COUNTS.dirtyResetIterations++;
271
+ dirty.dep._subs.add(dirty);
421
272
  let nextDirty = dirty.nextDirty;
422
273
  dirty.nextDirty = undefined;
423
274
  dirty = nextDirty;
424
275
  }
425
- if (process.env.NODE_ENV !== 'production')
426
- checkForCircularLinks(this._dirtyDep);
427
276
  }
428
277
  _dirty() {
429
278
  if (this._type === 1 /* SignalType.Subscription */) {
@@ -438,52 +287,41 @@ export class ComputedSignal {
438
287
  else {
439
288
  this._dirtyConsumers();
440
289
  }
441
- this._subs = undefined;
442
290
  }
443
291
  _dirtyConsumers() {
444
- let link = this._subs;
445
- if (process.env.NODE_ENV !== 'production')
446
- checkForCircularLinks(link);
447
- while (link !== undefined) {
448
- const consumer = link.sub.deref();
449
- if (consumer === undefined) {
450
- const nextSub = link.nextSub;
451
- poolLink(link);
452
- link = nextSub;
292
+ for (const link of this._subs.values()) {
293
+ const sub = link.sub.deref();
294
+ if (sub === undefined)
453
295
  continue;
454
- }
455
- const state = consumer._state;
456
- if (state === 2 /* SignalState.Dirty */) {
457
- const nextSub = link.nextSub;
458
- link = nextSub;
459
- continue;
460
- }
461
- if (state === 1 /* SignalState.MaybeDirty */) {
462
- let dirty = consumer._dirtyDep;
463
- const ord = link.ord;
464
- if (dirty.ord > ord) {
465
- consumer._dirtyDep = link;
466
- link.nextDirty = dirty;
467
- }
468
- else {
469
- let nextDirty = dirty.nextDirty;
470
- while (nextDirty !== undefined && nextDirty.ord < ord) {
471
- dirty = nextDirty;
472
- nextDirty = dirty.nextDirty;
296
+ switch (sub._state) {
297
+ case 1 /* SignalState.MaybeDirty */: {
298
+ let dirty = sub._dirtyDep;
299
+ const ord = link.ord;
300
+ if (dirty.ord > ord) {
301
+ sub._dirtyDep = link;
302
+ link.nextDirty = dirty;
473
303
  }
474
- link.nextDirty = nextDirty;
475
- dirty.nextDirty = link;
304
+ else {
305
+ let nextDirty = dirty.nextDirty;
306
+ while (nextDirty !== undefined && nextDirty.ord < ord) {
307
+ // COUNTS.dirtyInsertIterations++;
308
+ dirty = nextDirty;
309
+ nextDirty = dirty.nextDirty;
310
+ }
311
+ link.nextDirty = nextDirty;
312
+ dirty.nextDirty = link;
313
+ }
314
+ break;
315
+ }
316
+ case 0 /* SignalState.Clean */: {
317
+ sub._state = 1 /* SignalState.MaybeDirty */;
318
+ sub._dirtyDep = link;
319
+ link.nextDirty = undefined;
320
+ sub._dirty();
476
321
  }
477
322
  }
478
- else {
479
- // consumer._dirtyQueueLength = dirtyQueueLength + 2;
480
- consumer._state = 1 /* SignalState.MaybeDirty */;
481
- consumer._dirtyDep = link;
482
- link.nextDirty = undefined;
483
- consumer._dirty();
484
- }
485
- link = link.nextSub;
486
323
  }
324
+ this._subs = new Set();
487
325
  }
488
326
  _disconnect(count = 1) {
489
327
  this._connectedCount -= count;
@@ -500,25 +338,23 @@ export class ComputedSignal {
500
338
  SUBSCRIPTIONS.delete(this);
501
339
  }
502
340
  }
503
- let link = this._deps;
504
- while (link !== undefined) {
341
+ for (const link of this._deps.values()) {
505
342
  const dep = link.dep;
506
343
  dep._disconnect();
507
- link = link.nextDep;
508
344
  }
509
345
  }
510
346
  }
511
347
  class StateSignal {
512
348
  _value;
513
349
  _equals;
514
- _consumers = [];
350
+ _subs = [];
515
351
  constructor(_value, _equals = (a, b) => a === b) {
516
352
  this._value = _value;
517
353
  this._equals = _equals;
518
354
  }
519
355
  get() {
520
356
  if (CURRENT_CONSUMER !== undefined) {
521
- this._consumers.push(CURRENT_CONSUMER._ref);
357
+ this._subs.push(CURRENT_CONSUMER._ref);
522
358
  }
523
359
  return this._value;
524
360
  }
@@ -527,16 +363,24 @@ class StateSignal {
527
363
  return;
528
364
  }
529
365
  this._value = value;
530
- const { _consumers: consumers } = this;
531
- for (const consumerRef of consumers) {
532
- const consumer = consumerRef.deref();
533
- if (consumer === undefined) {
366
+ const subs = this._subs;
367
+ const subsLength = subs.length;
368
+ for (let i = 0; i < subsLength; i++) {
369
+ const sub = subs[i].deref();
370
+ if (sub === undefined) {
534
371
  continue;
535
372
  }
536
- consumer._state = 2 /* SignalState.Dirty */;
537
- consumer._dirty();
373
+ switch (sub._state) {
374
+ case 0 /* SignalState.Clean */:
375
+ sub._state = 2 /* SignalState.Dirty */;
376
+ sub._dirty();
377
+ break;
378
+ case 1 /* SignalState.MaybeDirty */:
379
+ sub._state = 2 /* SignalState.Dirty */;
380
+ break;
381
+ }
538
382
  }
539
- consumers.length = 0;
383
+ this._subs = [];
540
384
  }
541
385
  }
542
386
  export function state(initialValue, opts) {
@@ -552,7 +396,7 @@ export function subscription(subscribe, opts) {
552
396
  return new ComputedSignal(1 /* SignalType.Subscription */, subscribe, opts?.equals, opts?.initValue);
553
397
  }
554
398
  export function watcher(fn) {
555
- const subscribers = new Set();
399
+ const subscribers = [];
556
400
  const watcher = new ComputedSignal(3 /* SignalType.Watcher */, () => {
557
401
  fn();
558
402
  untrack(() => {
@@ -567,9 +411,9 @@ export function watcher(fn) {
567
411
  scheduleDisconnect(watcher);
568
412
  },
569
413
  subscribe(subscriber) {
570
- subscribers.add(subscriber);
414
+ subscribers.push(subscriber);
571
415
  return () => {
572
- subscribers.delete(subscriber);
416
+ subscribers.splice(subscribers.indexOf(subscriber), 1);
573
417
  };
574
418
  },
575
419
  };
@@ -579,18 +423,12 @@ export function isTracking() {
579
423
  }
580
424
  export function untrack(fn) {
581
425
  const prevConsumer = CURRENT_CONSUMER;
582
- const prevOrd = CURRENT_ORD;
583
- const prevIsWatched = CURRENT_IS_WATCHED;
584
426
  try {
585
427
  CURRENT_CONSUMER = undefined;
586
428
  // LAST_CONSUMED = undefined;
587
- CURRENT_ORD = 0;
588
- CURRENT_IS_WATCHED = false;
589
429
  return fn();
590
430
  }
591
431
  finally {
592
432
  CURRENT_CONSUMER = prevConsumer;
593
- CURRENT_ORD = prevOrd;
594
- CURRENT_IS_WATCHED = prevIsWatched;
595
433
  }
596
434
  }
@@ -0,0 +1,2 @@
1
+ declare const _default: WeakRefConstructor;
2
+ export default _default;
@@ -0,0 +1,10 @@
1
+ class WeakRefPolyfill {
2
+ value;
3
+ constructor(value) {
4
+ this.value = value;
5
+ }
6
+ deref() {
7
+ return this.value;
8
+ }
9
+ }
10
+ export default typeof WeakRef === 'function' ? WeakRef : WeakRefPolyfill;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "signalium",
3
- "version": "0.2.2",
3
+ "version": "0.2.4",
4
4
  "type": "module",
5
5
  "repository": "https://github.com/pzuraq/signalium",
6
6
  "description": "Chain-reactivity at critical mass",