signalium 0.2.2 → 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,11 @@
1
1
  # signalium
2
2
 
3
+ ## 0.2.3
4
+
5
+ ### Patch Changes
6
+
7
+ - 0ba50a0: Remove linked-lists for deps and subs
8
+
3
9
  ## 0.2.2
4
10
 
5
11
  ### Patch Changes
package/dist/signals.d.ts CHANGED
@@ -32,26 +32,22 @@ declare const enum SignalState {
32
32
  Dirty = 2
33
33
  }
34
34
  interface Link {
35
- id: number;
36
- sub: WeakRef<ComputedSignal<any>>;
37
35
  dep: ComputedSignal<any>;
36
+ sub: WeakRef<ComputedSignal<any>>;
38
37
  ord: number;
39
38
  version: number;
40
- nextDep: Link | undefined;
41
- nextSub: Link | undefined;
42
- prevSub: Link | undefined;
39
+ consumedAt: number;
43
40
  nextDirty: Link | undefined;
44
41
  }
45
- export declare function endTrack(sub: ComputedSignal<any>, shouldDisconnect: boolean): void;
46
42
  export declare class ComputedSignal<T> {
47
- id: number;
43
+ _id: number;
48
44
  _type: SignalType;
49
- _subs: Link | undefined;
50
- _subsTail: Link | undefined;
51
- _deps: Link | undefined;
45
+ _deps: Map<ComputedSignal<any>, Link>;
52
46
  _dirtyDep: Link | undefined;
47
+ _subs: Set<Link>;
53
48
  _state: SignalState;
54
49
  _version: number;
50
+ _computedCount: number;
55
51
  _connectedCount: number;
56
52
  _currentValue: T | AsyncResult<T> | undefined;
57
53
  _compute: SignalCompute<T> | SignalAsyncCompute<T> | SignalSubscribe<T> | undefined;
@@ -60,7 +56,7 @@ export declare class ComputedSignal<T> {
60
56
  constructor(type: SignalType, compute: SignalCompute<T> | SignalAsyncCompute<T> | SignalSubscribe<T> | undefined, equals?: SignalEquals<T>, initValue?: T);
61
57
  get(): T | AsyncResult<T>;
62
58
  _check(shouldWatch?: boolean): number;
63
- _run(wasConnected: boolean, isConnected: boolean, shouldConnect: boolean): void;
59
+ _run(wasConnected: boolean, shouldConnect: boolean): void;
64
60
  _resetDirty(): void;
65
61
  _dirty(): void;
66
62
  _dirtyConsumers(): void;
@@ -92,7 +88,7 @@ export type AsyncResult<T> = AsyncPending<T> | AsyncReady<T>;
92
88
  declare class StateSignal<T> implements StateSignal<T> {
93
89
  private _value;
94
90
  private _equals;
95
- private _consumers;
91
+ private _subs;
96
92
  constructor(_value: T, _equals?: SignalEquals<T>);
97
93
  get(): T;
98
94
  set(value: T): void;
package/dist/signals.js CHANGED
@@ -1,148 +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
- 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
9
  export class ComputedSignal {
137
- id = id++;
10
+ _id = ID++;
138
11
  _type;
139
- _subs;
140
- _subsTail;
141
- _deps;
142
- _dirtyDep;
12
+ _deps = new Map();
13
+ _dirtyDep = undefined;
14
+ _subs = new Set();
143
15
  _state = 2 /* SignalState.Dirty */;
144
16
  _version = 0;
145
- _connectedCount;
17
+ _computedCount = 0;
18
+ _connectedCount = 0;
146
19
  _currentValue;
147
20
  _compute;
148
21
  _equals;
@@ -186,35 +59,30 @@ export class ComputedSignal {
186
59
  };
187
60
  }
188
61
  get() {
189
- let prevTracked = false;
190
- 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);
191
65
  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;
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);
211
85
  }
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
86
  }
219
87
  else {
220
88
  this._check();
@@ -222,6 +90,7 @@ export class ComputedSignal {
222
90
  return this._currentValue;
223
91
  }
224
92
  _check(shouldWatch = false) {
93
+ // COUNTS.checks++;
225
94
  let state = this._state;
226
95
  let connectedCount = this._connectedCount;
227
96
  const wasConnected = connectedCount > 0;
@@ -234,16 +103,11 @@ export class ComputedSignal {
234
103
  state = 2 /* SignalState.Dirty */;
235
104
  }
236
105
  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;
106
+ for (const [dep, link] of this._deps) {
242
107
  if (link.version !== dep._check(true)) {
243
108
  state = 2 /* SignalState.Dirty */;
244
109
  break;
245
110
  }
246
- link = link.nextDep;
247
111
  }
248
112
  }
249
113
  }
@@ -262,7 +126,7 @@ export class ComputedSignal {
262
126
  }
263
127
  }
264
128
  if (state === 2 /* SignalState.Dirty */) {
265
- this._run(wasConnected, connectedCount > 0, shouldConnect);
129
+ this._run(wasConnected, shouldConnect);
266
130
  }
267
131
  else {
268
132
  this._resetDirty();
@@ -271,20 +135,13 @@ export class ComputedSignal {
271
135
  this._dirtyDep = undefined;
272
136
  return this._version;
273
137
  }
274
- _run(wasConnected, isConnected, shouldConnect) {
138
+ _run(wasConnected, shouldConnect) {
275
139
  const { _type: type } = this;
276
140
  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
141
  try {
282
142
  // eslint-disable-next-line @typescript-eslint/no-this-alias
283
143
  CURRENT_CONSUMER = this;
284
- CURRENT_ORD = 0;
285
- CURRENT_SEEN = new WeakSet();
286
- CURRENT_DEP_TAIL = undefined;
287
- CURRENT_IS_WATCHED = isConnected;
144
+ this._computedCount++;
288
145
  switch (type) {
289
146
  case 0 /* SignalType.Computed */: {
290
147
  const prevValue = this._currentValue;
@@ -391,39 +248,30 @@ export class ComputedSignal {
391
248
  }
392
249
  }
393
250
  finally {
394
- 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
+ }
395
262
  CURRENT_CONSUMER = prevConsumer;
396
- CURRENT_SEEN = prevSeen;
397
- CURRENT_DEP_TAIL = prevDepTail;
398
- CURRENT_ORD = prevOrd;
399
- CURRENT_IS_WATCHED = prevIsWatched;
400
263
  }
401
264
  }
402
265
  _resetDirty() {
403
266
  let dirty = this._dirtyDep;
267
+ // COUNTS.dirtyResetIterations++;
404
268
  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
- }
269
+ // COUNTS.dirtyResetIterations++;
270
+ dirty.dep._subs.add(dirty);
421
271
  let nextDirty = dirty.nextDirty;
422
272
  dirty.nextDirty = undefined;
423
273
  dirty = nextDirty;
424
274
  }
425
- if (process.env.NODE_ENV !== 'production')
426
- checkForCircularLinks(this._dirtyDep);
427
275
  }
428
276
  _dirty() {
429
277
  if (this._type === 1 /* SignalType.Subscription */) {
@@ -438,52 +286,41 @@ export class ComputedSignal {
438
286
  else {
439
287
  this._dirtyConsumers();
440
288
  }
441
- this._subs = undefined;
442
289
  }
443
290
  _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;
453
- continue;
454
- }
455
- const state = consumer._state;
456
- if (state === 2 /* SignalState.Dirty */) {
457
- const nextSub = link.nextSub;
458
- link = nextSub;
291
+ for (const link of this._subs.values()) {
292
+ const sub = link.sub.deref();
293
+ if (sub === undefined)
459
294
  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;
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;
473
302
  }
474
- link.nextDirty = nextDirty;
475
- dirty.nextDirty = link;
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;
312
+ }
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();
476
320
  }
477
321
  }
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
322
  }
323
+ this._subs = new Set();
487
324
  }
488
325
  _disconnect(count = 1) {
489
326
  this._connectedCount -= count;
@@ -500,25 +337,23 @@ export class ComputedSignal {
500
337
  SUBSCRIPTIONS.delete(this);
501
338
  }
502
339
  }
503
- let link = this._deps;
504
- while (link !== undefined) {
340
+ for (const link of this._deps.values()) {
505
341
  const dep = link.dep;
506
342
  dep._disconnect();
507
- link = link.nextDep;
508
343
  }
509
344
  }
510
345
  }
511
346
  class StateSignal {
512
347
  _value;
513
348
  _equals;
514
- _consumers = [];
349
+ _subs = [];
515
350
  constructor(_value, _equals = (a, b) => a === b) {
516
351
  this._value = _value;
517
352
  this._equals = _equals;
518
353
  }
519
354
  get() {
520
355
  if (CURRENT_CONSUMER !== undefined) {
521
- this._consumers.push(CURRENT_CONSUMER._ref);
356
+ this._subs.push(CURRENT_CONSUMER._ref);
522
357
  }
523
358
  return this._value;
524
359
  }
@@ -527,16 +362,24 @@ class StateSignal {
527
362
  return;
528
363
  }
529
364
  this._value = value;
530
- const { _consumers: consumers } = this;
531
- for (const consumerRef of consumers) {
532
- const consumer = consumerRef.deref();
533
- 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) {
534
370
  continue;
535
371
  }
536
- consumer._state = 2 /* SignalState.Dirty */;
537
- 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
+ }
538
381
  }
539
- consumers.length = 0;
382
+ this._subs = [];
540
383
  }
541
384
  }
542
385
  export function state(initialValue, opts) {
@@ -552,7 +395,7 @@ export function subscription(subscribe, opts) {
552
395
  return new ComputedSignal(1 /* SignalType.Subscription */, subscribe, opts?.equals, opts?.initValue);
553
396
  }
554
397
  export function watcher(fn) {
555
- const subscribers = new Set();
398
+ const subscribers = [];
556
399
  const watcher = new ComputedSignal(3 /* SignalType.Watcher */, () => {
557
400
  fn();
558
401
  untrack(() => {
@@ -567,9 +410,9 @@ export function watcher(fn) {
567
410
  scheduleDisconnect(watcher);
568
411
  },
569
412
  subscribe(subscriber) {
570
- subscribers.add(subscriber);
413
+ subscribers.push(subscriber);
571
414
  return () => {
572
- subscribers.delete(subscriber);
415
+ subscribers.splice(subscribers.indexOf(subscriber), 1);
573
416
  };
574
417
  },
575
418
  };
@@ -579,18 +422,12 @@ export function isTracking() {
579
422
  }
580
423
  export function untrack(fn) {
581
424
  const prevConsumer = CURRENT_CONSUMER;
582
- const prevOrd = CURRENT_ORD;
583
- const prevIsWatched = CURRENT_IS_WATCHED;
584
425
  try {
585
426
  CURRENT_CONSUMER = undefined;
586
427
  // LAST_CONSUMED = undefined;
587
- CURRENT_ORD = 0;
588
- CURRENT_IS_WATCHED = false;
589
428
  return fn();
590
429
  }
591
430
  finally {
592
431
  CURRENT_CONSUMER = prevConsumer;
593
- CURRENT_ORD = prevOrd;
594
- CURRENT_IS_WATCHED = prevIsWatched;
595
432
  }
596
433
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "signalium",
3
- "version": "0.2.2",
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",