@esportsplus/reactivity 0.8.0 → 0.9.0

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.
@@ -1,13 +1,17 @@
1
- import { isArray, isObject } from '@esportsplus/utilities';
1
+ import { defineProperty, isArray, isObject } from '@esportsplus/utilities';
2
2
  import { REACTIVE, STATE_CHECK, STATE_DIRTY, STATE_IN_HEAP, STATE_NONE, STATE_RECOMPUTING } from './constants';
3
3
  import { Computed, Link, Signal, } from './types';
4
4
 
5
5
 
6
- let dirtyHeap: (Computed<unknown> | undefined)[] = new Array(2000),
7
- maxDirty = 0,
8
- markedHeap = false,
9
- minDirty = 0,
10
- observer: Computed<unknown> | null = null;
6
+ let depth = 0,
7
+ heap: (Computed<unknown> | undefined)[] = new Array(2000),
8
+ index = 0,
9
+ length = 0,
10
+ notified = false,
11
+ observer: Computed<unknown> | null = null,
12
+ scheduled = false,
13
+ scheduler: ((task: VoidFunction) => void) | null = null,
14
+ version = 0;
11
15
 
12
16
 
13
17
  function cleanup<T>(node: Computed<T>): void {
@@ -15,80 +19,82 @@ function cleanup<T>(node: Computed<T>): void {
15
19
  return;
16
20
  }
17
21
 
18
- if (isArray(node.cleanup)) {
19
- for (let i = 0; i < node.cleanup.length; i++) {
20
- node.cleanup[i]();
22
+ let cleanup = node.cleanup;
23
+
24
+ if (isArray(cleanup)) {
25
+ for (let i = 0; i < cleanup.length; i++) {
26
+ cleanup[i]();
21
27
  }
22
28
  }
23
29
  else {
24
- node.cleanup();
30
+ cleanup();
25
31
  }
26
32
 
27
33
  node.cleanup = null;
28
34
  }
29
35
 
30
- function deleteFromHeap<T>(n: Computed<T>) {
31
- let state = n.state;
36
+ function deleteFromHeap<T>(computed: Computed<T>) {
37
+ let state = computed.state;
32
38
 
33
39
  if (!(state & STATE_IN_HEAP)) {
34
40
  return;
35
41
  }
36
42
 
37
- n.state = state & ~STATE_IN_HEAP;
43
+ computed.state = state & ~STATE_IN_HEAP;
38
44
 
39
- let height = n.height;
45
+ let height = computed.height;
40
46
 
41
- if (n.prevHeap === n) {
42
- dirtyHeap[height] = undefined;
47
+ if (computed.prevHeap === computed) {
48
+ heap[height] = undefined;
43
49
  }
44
50
  else {
45
- let next = n.nextHeap,
46
- dhh = dirtyHeap[height]!,
51
+ let next = computed.nextHeap,
52
+ dhh = heap[height]!,
47
53
  end = next ?? dhh;
48
54
 
49
- if (n === dhh) {
50
- dirtyHeap[height] = next;
55
+ if (computed === dhh) {
56
+ heap[height] = next;
51
57
  }
52
58
  else {
53
- n.prevHeap.nextHeap = next;
59
+ computed.prevHeap.nextHeap = next;
54
60
  }
55
61
 
56
- end.prevHeap = n.prevHeap;
62
+ end.prevHeap = computed.prevHeap;
57
63
  }
58
64
 
59
- n.nextHeap = undefined;
60
- n.prevHeap = n;
65
+ computed.nextHeap = undefined;
66
+ computed.prevHeap = computed;
61
67
  }
62
68
 
63
- function insertIntoHeap<T>(n: Computed<T>) {
64
- let state = n.state;
69
+ function insertIntoHeap<T>(computed: Computed<T>) {
70
+ let state = computed.state;
65
71
 
66
72
  if (state & STATE_IN_HEAP) {
67
73
  return;
68
74
  }
69
75
 
70
- n.state = state | STATE_IN_HEAP;
76
+ computed.state = state | STATE_IN_HEAP;
71
77
 
72
- let height = n.height,
73
- heapAtHeight = dirtyHeap[height];
78
+ let height = computed.height,
79
+ heapAtHeight = heap[height];
74
80
 
75
81
  if (heapAtHeight === undefined) {
76
- dirtyHeap[height] = n;
82
+ heap[height] = computed;
77
83
  }
78
84
  else {
79
85
  let tail = heapAtHeight.prevHeap;
80
86
 
81
- tail.nextHeap = n;
82
- n.prevHeap = tail;
83
- heapAtHeight.prevHeap = n;
87
+ tail.nextHeap = computed;
88
+ computed.prevHeap = tail;
89
+ heapAtHeight.prevHeap = computed;
84
90
  }
85
91
 
86
- if (height > maxDirty) {
87
- maxDirty = height;
92
+ if (height > length) {
93
+ length = height;
88
94
 
89
95
  // Simple auto adjust to avoid manual management within apps.
90
- if (height >= dirtyHeap.length) {
91
- dirtyHeap.length += 250;
96
+ if (height >= heap.length) {
97
+ heap.length += 250;
92
98
  }
93
99
  }
94
100
  }
@@ -107,13 +113,24 @@ function link<T>(dep: Signal<T> | Computed<T>, sub: Computed<T>) {
107
113
  nextDep = prevDep !== null ? prevDep.nextDep : sub.deps;
108
114
 
109
115
  if (nextDep !== null && nextDep.dep === dep) {
116
+ nextDep.version = version;
110
117
  sub.depsTail = nextDep;
111
118
  return;
112
119
  }
113
120
  }
114
121
 
115
- let prevSub = dep.subsTail,
116
- newLink =
122
+ let prevSub = dep.subsTail;
123
+
124
+ // https://github.com/stackblitz/alien-signals/commit/54fe1b3947fac5c0aecb73b0b0eaff000806c454
125
+ if (
126
+ prevSub !== null &&
127
+ prevSub.version === version &&
128
+ prevSub.sub === sub
129
+ ) {
130
+ return;
131
+ }
132
+
133
+ let newLink =
117
134
  sub.depsTail =
118
135
  dep.subsTail = {
119
136
  dep,
@@ -121,6 +138,7 @@ function link<T>(dep: Signal<T> | Computed<T>, sub: Computed<T>) {
121
138
  nextDep,
122
139
  prevSub,
123
140
  nextSub: null,
141
+ version
124
142
  };
125
143
 
126
144
  if (prevDep !== null) {
@@ -138,65 +156,54 @@ function link<T>(dep: Signal<T> | Computed<T>, sub: Computed<T>) {
138
156
  }
139
157
  }
140
158
 
141
- function markHeap() {
142
- if (markedHeap) {
143
- return;
144
- }
145
-
146
- markedHeap = true;
147
-
148
- for (let i = 0; i <= maxDirty; i++) {
149
- for (let el = dirtyHeap[i]; el !== undefined; el = el.nextHeap) {
150
- markNode(el);
151
- }
152
- }
153
- }
154
-
155
- function markNode<T>(el: Computed<T>, newState = STATE_DIRTY) {
156
- let state = el.state;
159
+ function notify<T>(computed: Computed<T>, newState = STATE_DIRTY) {
160
+ let state = computed.state;
157
161
 
158
162
  if ((state & (STATE_CHECK | STATE_DIRTY)) >= newState) {
159
163
  return;
160
164
  }
161
165
 
162
- el.state = state | newState;
166
+ computed.state = state | newState;
163
167
 
164
- for (let link = el.subs; link !== null; link = link.nextSub) {
165
- markNode(link.sub, STATE_CHECK);
168
+ for (let link = computed.subs; link !== null; link = link.nextSub) {
169
+ notify(link.sub, STATE_CHECK);
166
170
  }
167
171
  }
168
172
 
169
- function recompute<T>(el: Computed<T>, del: boolean) {
173
+ function recompute<T>(computed: Computed<T>, del: boolean) {
170
174
  if (del) {
171
- deleteFromHeap(el);
175
+ deleteFromHeap(computed);
172
176
  }
173
177
  else {
174
- el.nextHeap = undefined;
175
- el.prevHeap = el;
178
+ computed.nextHeap = undefined;
179
+ computed.prevHeap = computed;
176
180
  }
177
181
 
178
- cleanup(el);
182
+ cleanup(computed);
179
183
 
180
184
  let o = observer,
181
185
  ok = true,
182
186
  value;
183
187
 
184
- observer = el;
185
- el.depsTail = null;
186
- el.state = STATE_RECOMPUTING;
188
+ observer = computed;
189
+ computed.depsTail = null;
190
+ computed.state = STATE_RECOMPUTING;
191
+
192
+ depth++;
193
+ version++;
187
194
 
188
195
  try {
189
- value = el.fn(oncleanup);
196
+ value = computed.fn(onCleanup);
190
197
  }
191
198
  catch (e) {
192
199
  ok = false;
193
200
  }
194
201
 
195
202
  observer = o;
196
- el.state = STATE_NONE;
203
+ computed.state = STATE_NONE;
197
204
 
198
- let depsTail = el.depsTail as Link | null,
199
- toRemove = depsTail !== null ? depsTail.nextDep : el.deps;
205
+ let depsTail = computed.depsTail as Link | null,
206
+ toRemove = depsTail !== null ? depsTail.nextDep : computed.deps;
200
207
 
201
208
  if (toRemove !== null) {
202
209
  do {
@@ -208,15 +215,15 @@ function recompute<T>(el: Computed<T>, del: boolean) {
208
215
  depsTail.nextDep = null;
209
216
  }
210
217
  else {
211
- el.deps = null;
218
+ computed.deps = null;
212
219
  }
213
220
  }
214
221
 
215
- if (ok && value !== el.value) {
216
- el.value = value as T;
222
+ if (ok && value !== computed.value) {
223
+ computed.value = value as T;
217
224
 
218
- for (let s = el.subs; s !== null; s = s.nextSub) {
219
- let o = s.sub,
225
+ for (let c = computed.subs; c !== null; c = c.nextSub) {
226
+ let o = c.sub,
220
227
  state = o.state;
221
228
 
222
229
  if (state & STATE_CHECK) {
@@ -227,17 +234,20 @@ function recompute<T>(el: Computed<T>, del: boolean) {
227
234
  }
228
235
  }
229
236
 
230
- if (stabilize.state.value === STATE_NONE) {
231
- root(() => signal.set(stabilize.state, STATE_DIRTY));
237
+ if (!--depth) {
238
+ if (!scheduled && scheduler) {
239
+ scheduled = true;
240
+ scheduler(stabilize);
241
+ }
242
+ else {
243
+ throw new Error('@esportsplus/reactivity: stabilize.scheduler has not been set to process updates.');
244
+ }
232
245
  }
233
246
  }
234
247
 
235
248
  // https://github.com/stackblitz/alien-signals/blob/v2.0.3/src/system.ts#L100
236
249
  function unlink(link: Link): Link | null {
237
- let dep = link.dep,
238
- nextDep = link.nextDep,
239
- nextSub = link.nextSub,
240
- prevSub = link.prevSub;
250
+ let { dep, nextDep, nextSub, prevSub } = link;
241
251
 
242
252
  if (nextSub !== null) {
243
253
  nextSub.prevSub = prevSub;
@@ -260,26 +270,26 @@ function unlink(link: Link): Link | null {
260
270
  return nextDep;
261
271
  }
262
272
 
263
- function update<T>(el: Computed<T>): void {
264
- if (el.state & STATE_CHECK) {
265
- for (let d = el.deps; d; d = d.nextDep) {
266
- let dep = d.dep;
273
+ function update<T>(computed: Computed<T>): void {
274
+ if (computed.state & STATE_CHECK) {
275
+ for (let link = computed.deps; link; link = link.nextDep) {
276
+ let dep = link.dep;
267
277
 
268
278
  if ('fn' in dep) {
269
279
  update(dep);
270
280
  }
271
281
 
272
- if (el.state & STATE_DIRTY) {
282
+ if (computed.state & STATE_DIRTY) {
273
283
  break;
274
284
  }
275
285
  }
276
286
  }
277
287
 
278
- if (el.state & STATE_DIRTY) {
279
- recompute(el, true);
288
+ if (computed.state & STATE_DIRTY) {
289
+ recompute(computed, true);
280
290
  }
281
291
 
282
- el.state = STATE_NONE;
292
+ computed.state = STATE_NONE;
283
293
  }
284
294
 
285
295
 
@@ -320,33 +330,33 @@ const computed = <T>(fn: Computed<T>['fn']): Computed<T> => {
320
330
  return self;
321
331
  };
322
332
 
323
- const dispose = <T>(el: Computed<T>) => {
324
- deleteFromHeap(el);
333
+ const dispose = <T>(computed: Computed<T>) => {
334
+ deleteFromHeap(computed);
325
335
 
326
- let dep = el.deps;
336
+ let dep = computed.deps;
327
337
 
328
338
  while (dep !== null) {
329
339
  dep = unlink(dep);
330
340
  }
331
341
 
332
- el.deps = null;
342
+ computed.deps = null;
333
343
 
334
- cleanup(el);
335
- }
344
+ cleanup(computed);
345
+ };
336
346
 
337
- const isComputed = (value: unknown): value is Computed<unknown> => {
338
- return isObject(value) && REACTIVE in value && 'fn' in value;
347
+ const effect = <T>(fn: Computed<T>['fn']) => {
348
+ computed(fn);
339
349
  };
340
350
 
341
- const isReactive = (value: unknown): value is Computed<unknown> | Signal<unknown> => {
342
- return isObject(value) && REACTIVE in value;
351
+ const isComputed = (value: unknown): value is Computed<unknown> => {
352
+ return isObject(value) && REACTIVE in value && 'fn' in value;
343
353
  };
344
354
 
345
355
  const isSignal = (value: unknown): value is Signal<unknown> => {
346
356
  return isObject(value) && REACTIVE in value && 'fn' in value === false;
347
357
  };
348
358
 
349
- const oncleanup = (fn: VoidFunction): typeof fn => {
359
+ const onCleanup = (fn: VoidFunction): typeof fn => {
350
360
  if (!observer) {
351
361
  return fn;
352
362
  }
@@ -366,28 +376,37 @@ const oncleanup = (fn: VoidFunction): typeof fn => {
366
376
  return fn;
367
377
  };
368
378
 
369
- const read = <T>(el: Signal<T> | Computed<T>): T => {
379
+ const read = <T>(node: Signal<T> | Computed<T>): T => {
370
380
  if (observer) {
371
- link(el, observer);
381
+ link(node, observer);
372
382
 
373
- if ('fn' in el) {
374
- let height = el.height;
383
+ if ('fn' in node) {
384
+ let height = node.height;
375
385
 
376
386
  if (height >= observer.height) {
377
387
  observer.height = height + 1;
378
388
  }
379
389
 
380
390
  if (
381
- height >= minDirty ||
382
- el.state & (STATE_DIRTY | STATE_CHECK)
391
+ height >= index ||
392
+ node.state & (STATE_DIRTY | STATE_CHECK)
383
393
  ) {
384
- markHeap();
385
- update(el);
394
+ if (!notified) {
395
+ notified = true;
396
+
397
+ for (let i = 0; i <= length; i++) {
398
+ for (let computed = heap[i]; computed !== undefined; computed = computed.nextHeap) {
399
+ notify(computed);
400
+ }
401
+ }
402
+ }
403
+
404
+ update(node);
386
405
  }
387
406
  }
388
407
  }
389
408
 
390
- return el.value;
409
+ return node.value;
391
410
  };
392
411
 
393
412
  const root = <T>(fn: () => T) => {
@@ -411,47 +430,55 @@ const signal = <T>(value: T): Signal<T> => {
411
430
  };
412
431
  };
413
432
 
414
- signal.set = <T>(el: Signal<T>, v: T) => {
415
- if (el.value === v) {
433
+ signal.set = <T>(signal: Signal<T>, value: T) => {
434
+ if (signal.value === value) {
416
435
  return;
417
436
  }
418
437
 
419
- el.value = v;
438
+ notified = false;
439
+ signal.value = value;
420
440
 
421
- for (let link = el.subs; link !== null; link = link.nextSub) {
422
- markedHeap = false;
441
+ for (let link = signal.subs; link !== null; link = link.nextSub) {
423
442
  insertIntoHeap(link.sub);
424
443
  }
425
444
  };
426
445
 
427
446
  const stabilize = () => {
428
447
  root(() => {
429
- for (minDirty = 0; minDirty <= maxDirty; minDirty++) {
430
- let el = dirtyHeap[minDirty];
448
+ for (index = 0; index <= length; index++) {
449
+ let computed = heap[index];
431
450
 
432
- dirtyHeap[minDirty] = undefined;
451
+ heap[index] = undefined;
433
452
 
434
- while (el !== undefined) {
435
- let next = el.nextHeap;
453
+ while (computed !== undefined) {
454
+ let next = computed.nextHeap;
436
455
 
437
- recompute(el, false);
456
+ recompute(computed, false);
438
457
 
439
- el = next;
458
+ computed = next;
440
459
  }
441
460
  }
442
461
 
443
- signal.set(stabilize.state, STATE_NONE);
462
+ scheduled = false;
444
463
  });
445
464
  };
446
465
 
447
- stabilize.state = signal(STATE_NONE);
466
+ defineProperty(stabilize, 'scheduler', {
467
+ get() {
468
+ return scheduler;
469
+ },
470
+ set(s: typeof scheduler) {
471
+ scheduler = s;
472
+ },
473
+ });
448
474
 
449
475
 
450
476
  export {
451
477
  computed,
452
478
  dispose,
453
- isComputed, isReactive, isSignal,
454
- oncleanup,
479
+ effect,
480
+ isComputed, isSignal,
481
+ onCleanup,
455
482
  read, root,
456
483
  signal, stabilize
457
484
  };
package/src/types.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { REACTIVE, STATE_CHECK, STATE_DIRTY, STATE_IN_HEAP, STATE_NONE, STATE_RECOMPUTING } from './constants';
2
- import { oncleanup } from './signal';
2
+ import { onCleanup } from './system';
3
3
  import { ReactiveArray } from './reactive/array';
4
4
  import { ReactiveObject } from './reactive/object';
5
5
 
@@ -9,7 +9,7 @@ interface Computed<T> extends Signal<T> {
9
9
  cleanup: VoidFunction | VoidFunction[] | null;
10
10
  deps: Link | null;
11
11
  depsTail: Link | null;
12
- fn: (oc?: typeof oncleanup) => T;
12
+ fn: (oc?: typeof onCleanup) => T;
13
13
  height: number;
14
14
  nextHeap: Computed<unknown> | undefined;
15
15
  prevHeap: Computed<unknown>;
@@ -38,6 +38,7 @@ interface Link {
38
38
  nextDep: Link | null;
39
39
  nextSub: Link | null;
40
40
  prevSub: Link | null;
41
+ version: number;
41
42
  }
42
43
 
43
44
  type Reactive<T> = T extends Record<PropertyKey, unknown>
@@ -1,4 +0,0 @@
1
- declare const scheduler: (schedule: (task: VoidFunction) => void) => void;
2
- declare const state: import("./types.js").Signal<number>;
3
- export default scheduler;
4
- export { state };
@@ -1,17 +0,0 @@
1
- import { STATE_DIRTY, STATE_NONE } from './constants.js';
2
- import { computed, dispose, read, signal, stabilize } from './signal.js';
3
- let c = null;
4
- const scheduler = (schedule) => {
5
- if (c) {
6
- dispose(c);
7
- }
8
- c = computed(() => {
9
- if (read(state) !== STATE_DIRTY) {
10
- return;
11
- }
12
- schedule(stabilize);
13
- });
14
- };
15
- const state = signal(STATE_NONE);
16
- export default scheduler;
17
- export { state };
package/build/signal.d.ts DELETED
@@ -1,18 +0,0 @@
1
- import { Computed, Signal } from './types.js';
2
- declare const computed: <T>(fn: Computed<T>["fn"]) => Computed<T>;
3
- declare const dispose: <T>(el: Computed<T>) => void;
4
- declare const isComputed: (value: unknown) => value is Computed<unknown>;
5
- declare const isReactive: (value: unknown) => value is Computed<unknown> | Signal<unknown>;
6
- declare const isSignal: (value: unknown) => value is Signal<unknown>;
7
- declare const oncleanup: (fn: VoidFunction) => typeof fn;
8
- declare const read: <T>(el: Signal<T> | Computed<T>) => T;
9
- declare const root: <T>(fn: () => T) => T;
10
- declare const signal: {
11
- <T>(value: T): Signal<T>;
12
- set<T>(el: Signal<T>, v: T): void;
13
- };
14
- declare const stabilize: {
15
- (): void;
16
- state: Signal<number>;
17
- };
18
- export { computed, dispose, isComputed, isReactive, isSignal, oncleanup, read, root, signal, stabilize };