@esportsplus/reactivity 0.12.2 → 0.12.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.
@@ -11,4 +11,5 @@ declare const STATE_CHECK: number;
11
11
  declare const STATE_DIRTY: number;
12
12
  declare const STATE_RECOMPUTING: number;
13
13
  declare const STATE_IN_HEAP: number;
14
- export { COMPUTED, REACTIVE_ARRAY, REACTIVE_OBJECT, SIGNAL, STABILIZER_IDLE, STABILIZER_RESCHEDULE, STABILIZER_RUNNING, STABILIZER_SCHEDULED, STATE_CHECK, STATE_DIRTY, STATE_IN_HEAP, STATE_NONE, STATE_RECOMPUTING };
14
+ declare const STATE_NOTIFY_MASK: number;
15
+ export { COMPUTED, REACTIVE_ARRAY, REACTIVE_OBJECT, SIGNAL, STABILIZER_IDLE, STATE_NOTIFY_MASK, STABILIZER_RESCHEDULE, STABILIZER_RUNNING, STABILIZER_SCHEDULED, STATE_CHECK, STATE_DIRTY, STATE_IN_HEAP, STATE_NONE, STATE_RECOMPUTING };
@@ -11,4 +11,5 @@ const STATE_CHECK = 1 << 0;
11
11
  const STATE_DIRTY = 1 << 1;
12
12
  const STATE_RECOMPUTING = 1 << 2;
13
13
  const STATE_IN_HEAP = 1 << 3;
14
- export { COMPUTED, REACTIVE_ARRAY, REACTIVE_OBJECT, SIGNAL, STABILIZER_IDLE, STABILIZER_RESCHEDULE, STABILIZER_RUNNING, STABILIZER_SCHEDULED, STATE_CHECK, STATE_DIRTY, STATE_IN_HEAP, STATE_NONE, STATE_RECOMPUTING };
14
+ const STATE_NOTIFY_MASK = (STATE_CHECK | STATE_DIRTY);
15
+ export { COMPUTED, REACTIVE_ARRAY, REACTIVE_OBJECT, SIGNAL, STABILIZER_IDLE, STATE_NOTIFY_MASK, STABILIZER_RESCHEDULE, STABILIZER_RUNNING, STABILIZER_SCHEDULED, STATE_CHECK, STATE_DIRTY, STATE_IN_HEAP, STATE_NONE, STATE_RECOMPUTING };
@@ -57,7 +57,6 @@ declare class ReactiveArray<T> {
57
57
  splice(start: number, deleteCount?: number, ...input: T[]): Item<T>[];
58
58
  unshift(...input: T[]): number;
59
59
  }
60
- declare const isReactiveArray: (value: any) => value is ReactiveArray<any>;
61
- export default function array<T>(input: T[]): API<T>;
62
- export { isReactiveArray };
60
+ declare const _default: <T>(input: T[]) => API<T>;
61
+ export default _default;
63
62
  export type { API as ReactiveArray };
@@ -63,7 +63,7 @@ class ReactiveArray {
63
63
  }
64
64
  }
65
65
  map(fn, i, n) {
66
- let { data, proxy } = this, values = [];
66
+ let { data, proxy } = this;
67
67
  if (i === undefined) {
68
68
  i = 0;
69
69
  }
@@ -71,9 +71,10 @@ class ReactiveArray {
71
71
  n = data.length;
72
72
  }
73
73
  n = Math.min(n, data.length);
74
+ let values = new Array(n - i);
74
75
  for (; i < n; i++) {
75
76
  let item = data[i];
76
- values.push(fn.call(proxy, (isComputed(item) ? item.value : item), i));
77
+ values[i] = fn.call(proxy, (isComputed(item) ? item.value : item), i);
77
78
  }
78
79
  return values;
79
80
  }
@@ -86,14 +87,18 @@ class ReactiveArray {
86
87
  if (listeners === undefined) {
87
88
  this.listeners[event] = [listener];
88
89
  }
89
- else if (listeners.indexOf(listener) === -1) {
90
- let i = listeners.indexOf(null);
91
- if (i === -1) {
92
- listeners.push(listener);
93
- }
94
- else {
95
- listeners[i] = listener;
90
+ else {
91
+ let hole = listeners.length;
92
+ for (let i = 0, n = hole; i < n; i++) {
93
+ let l = listeners[i];
94
+ if (l === listener) {
95
+ return;
96
+ }
97
+ else if (l === null && hole === n) {
98
+ hole = i;
99
+ }
96
100
  }
101
+ listeners[hole] = listener;
97
102
  }
98
103
  }
99
104
  }
@@ -153,25 +158,22 @@ class ReactiveArray {
153
158
  }
154
159
  }
155
160
  function factory(input) {
156
- let items = [];
157
- for (let i = 0, n = input.length; i < n; i++) {
161
+ let n = input.length, output = new Array(n);
162
+ for (let i = 0; i < n; i++) {
158
163
  let value = input[i];
159
164
  if (isFunction(value)) {
160
- items[i] = computed(value);
165
+ output[i] = computed(value);
161
166
  }
162
167
  else if (isObject(value)) {
163
- items[i] = object(value);
168
+ output[i] = object(value);
164
169
  }
165
170
  else {
166
- items[i] = value;
171
+ output[i] = value;
167
172
  }
168
173
  }
169
- return items;
174
+ return output;
170
175
  }
171
- const isReactiveArray = (value) => {
172
- return isObject(value) && REACTIVE_ARRAY in value;
173
- };
174
- export default function array(input) {
176
+ export default (input) => {
175
177
  let proxy = new Proxy({}, {
176
178
  get(_, key) {
177
179
  if (isNumber(key)) {
@@ -192,13 +194,12 @@ export default function array(input) {
192
194
  return true;
193
195
  }
194
196
  else if (key === 'length') {
195
- return array.length = value;
197
+ array.length = value;
198
+ return true;
196
199
  }
197
200
  return false;
198
201
  }
199
202
  }), wrapped = factory(input);
200
203
  let array = new ReactiveArray(wrapped, proxy);
201
204
  return proxy;
202
- }
203
- ;
204
- export { isReactiveArray };
205
+ };
@@ -6,10 +6,12 @@ type API<T extends Record<PropertyKey, unknown>> = Prettify<{
6
6
  }> & ReactiveObject<T>;
7
7
  declare class ReactiveObject<T extends Record<PropertyKey, unknown>> {
8
8
  [REACTIVE_OBJECT]: boolean;
9
+ private disposers;
9
10
  constructor(data: T);
10
11
  dispose(): void;
11
12
  }
12
13
  declare const isReactiveObject: (value: any) => value is ReactiveObject<any>;
13
- export default function object<T extends Record<PropertyKey, unknown>>(input: T): ReactiveObject<T>;
14
+ declare const _default: <T extends Record<PropertyKey, unknown>>(input: T) => API<T>;
15
+ export default _default;
14
16
  export { isReactiveObject };
15
17
  export type { API as ReactiveObject };
@@ -1,71 +1,71 @@
1
1
  import { defineProperty, isArray, isFunction, isObject, isPromise } from '@esportsplus/utilities';
2
- import { computed, dispose, effect, isComputed, read, root, set, signal } from '../system.js';
2
+ import { computed, dispose, effect, read, root, set, signal } from '../system.js';
3
3
  import { REACTIVE_OBJECT } from '../constants.js';
4
- import array, { isReactiveArray } from './array.js';
4
+ import array from './array.js';
5
5
  class ReactiveObject {
6
6
  [REACTIVE_OBJECT] = true;
7
+ disposers = null;
7
8
  constructor(data) {
8
- for (let key in data) {
9
- let value = data[key];
9
+ let keys = Object.keys(data);
10
+ for (let i = 0, n = keys.length; i < n; i++) {
11
+ let key = keys[i], value = data[key];
10
12
  if (isArray(value)) {
11
- let a = array(value);
13
+ let node = array(value);
14
+ (this.disposers ??= []).push(() => node.dispose());
12
15
  defineProperty(this, key, {
13
16
  enumerable: true,
14
- get() {
15
- return a;
16
- }
17
+ value: node
17
18
  });
18
19
  }
19
20
  else if (isFunction(value)) {
20
- let c;
21
+ let node;
21
22
  defineProperty(this, key, {
22
23
  enumerable: true,
23
- get() {
24
- if (c === undefined) {
24
+ get: () => {
25
+ if (node === undefined) {
25
26
  root(() => {
26
- c = computed(value);
27
- if (isPromise(c.value)) {
28
- let factory = c, version = 0;
29
- c = signal(undefined);
30
- effect(() => {
27
+ node = computed(value);
28
+ if (isPromise(node.value)) {
29
+ let factory = node, version = 0;
30
+ node = signal(undefined);
31
+ (this.disposers ??= []).push(effect(() => {
31
32
  let id = ++version;
32
- read(factory).then((value) => {
33
+ read(factory).then((v) => {
33
34
  if (id !== version) {
34
35
  return;
35
36
  }
36
- set(c, value);
37
+ set(node, v);
37
38
  });
38
- });
39
+ }));
40
+ }
41
+ else {
42
+ (this.disposers ??= []).push(() => dispose(node));
39
43
  }
40
44
  });
41
45
  }
42
- return read(c);
46
+ return read(node);
43
47
  }
44
48
  });
45
49
  }
46
50
  else {
47
- let s = signal(value);
51
+ let node = signal(value);
48
52
  defineProperty(this, key, {
49
53
  enumerable: true,
50
54
  get() {
51
- return read(s);
55
+ return read(node);
52
56
  },
53
57
  set(v) {
54
- set(s, v);
58
+ set(node, v);
55
59
  }
56
60
  });
57
61
  }
58
62
  }
59
63
  }
60
64
  dispose() {
61
- let value;
62
- for (let key in this) {
63
- value = this[key];
64
- if (isReactiveArray(value) || isReactiveObject(value)) {
65
- value.dispose();
66
- }
67
- else if (isComputed(value)) {
68
- dispose(value);
65
+ let disposers = this.disposers;
66
+ if (disposers) {
67
+ for (let i = 0, n = disposers.length; i < n; i++) {
68
+ disposers[i]();
69
69
  }
70
70
  }
71
71
  }
@@ -73,8 +73,7 @@ class ReactiveObject {
73
73
  const isReactiveObject = (value) => {
74
74
  return isObject(value) && REACTIVE_OBJECT in value;
75
75
  };
76
- export default function object(input) {
76
+ export default (input) => {
77
77
  return new ReactiveObject(input);
78
- }
79
- ;
78
+ };
80
79
  export { isReactiveObject };
package/build/system.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { isArray, isObject } from '@esportsplus/utilities';
2
- import { COMPUTED, SIGNAL, STABILIZER_IDLE, STABILIZER_RESCHEDULE, STABILIZER_RUNNING, STABILIZER_SCHEDULED, STATE_CHECK, STATE_DIRTY, STATE_IN_HEAP, STATE_NONE, STATE_RECOMPUTING } from './constants.js';
3
- let depth = 0, heap = new Array(2048), index = 0, length = 0, microtask = queueMicrotask, notified = false, observer = null, stabilizer = STABILIZER_IDLE, version = 0;
2
+ import { COMPUTED, SIGNAL, STABILIZER_IDLE, STABILIZER_RESCHEDULE, STABILIZER_RUNNING, STABILIZER_SCHEDULED, STATE_CHECK, STATE_DIRTY, STATE_IN_HEAP, STATE_NONE, STATE_NOTIFY_MASK, STATE_RECOMPUTING } from './constants.js';
3
+ let depth = 0, heap = new Array(64), heap_i = 0, heap_n = 0, microtask = queueMicrotask, notified = false, observer = null, stabilizer = STABILIZER_IDLE, version = 0;
4
4
  function cleanup(computed) {
5
5
  if (!computed.cleanup) {
6
6
  return;
@@ -55,10 +55,10 @@ function insertIntoHeap(computed) {
55
55
  computed.prevHeap = tail;
56
56
  heapAtHeight.prevHeap = computed;
57
57
  }
58
- if (height > length) {
59
- length = height;
58
+ if (height > heap_n) {
59
+ heap_n = height;
60
60
  if (height >= heap.length) {
61
- heap.length = Math.round(heap.length * 1.5);
61
+ heap.length = Math.max(height + 1, Math.ceil(heap.length * 2));
62
62
  }
63
63
  }
64
64
  }
@@ -106,7 +106,7 @@ function link(dep, sub) {
106
106
  }
107
107
  function notify(computed, newState = STATE_DIRTY) {
108
108
  let state = computed.state;
109
- if ((state & (STATE_CHECK | STATE_DIRTY)) >= newState) {
109
+ if ((state & STATE_NOTIFY_MASK) >= newState) {
110
110
  return;
111
111
  }
112
112
  computed.state = state | newState;
@@ -122,7 +122,9 @@ function recompute(computed, del) {
122
122
  computed.nextHeap = undefined;
123
123
  computed.prevHeap = computed;
124
124
  }
125
- cleanup(computed);
125
+ if (computed.cleanup) {
126
+ cleanup(computed);
127
+ }
126
128
  let o = observer, ok = true, value;
127
129
  observer = computed;
128
130
  computed.depsTail = null;
@@ -175,15 +177,18 @@ function stabilize() {
175
177
  let o = observer;
176
178
  observer = null;
177
179
  stabilizer = STABILIZER_RUNNING;
178
- for (index = 0; index <= length; index++) {
179
- let computed = heap[index];
180
- heap[index] = undefined;
180
+ for (heap_i = 0; heap_i <= heap_n; heap_i++) {
181
+ let computed = heap[heap_i];
182
+ heap[heap_i] = undefined;
181
183
  while (computed !== undefined) {
182
184
  let next = computed.nextHeap;
183
185
  recompute(computed, false);
184
186
  computed = next;
185
187
  }
186
188
  }
189
+ while (heap_n > 0 && heap[heap_n] === undefined) {
190
+ heap_n--;
191
+ }
187
192
  observer = o;
188
193
  if (stabilizer === STABILIZER_RESCHEDULE) {
189
194
  microtask(stabilize);
@@ -193,7 +198,7 @@ function stabilize() {
193
198
  }
194
199
  }
195
200
  function unlink(link) {
196
- let { dep, nextDep, nextSub, prevSub } = link;
201
+ let dep = link.dep, nextDep = link.nextDep, nextSub = link.nextSub, prevSub = link.prevSub;
197
202
  if (nextSub !== null) {
198
203
  nextSub.prevSub = prevSub;
199
204
  }
@@ -267,7 +272,9 @@ const dispose = (computed) => {
267
272
  dep = unlink(dep);
268
273
  }
269
274
  computed.deps = null;
270
- cleanup(computed);
275
+ if (computed.cleanup) {
276
+ cleanup(computed);
277
+ }
271
278
  };
272
279
  const effect = (fn) => {
273
280
  let c = computed(fn);
@@ -285,14 +292,15 @@ const onCleanup = (fn) => {
285
292
  if (!observer) {
286
293
  return fn;
287
294
  }
288
- if (!observer.cleanup) {
295
+ let cleanup = observer.cleanup;
296
+ if (!cleanup) {
289
297
  observer.cleanup = fn;
290
298
  }
291
- else if (isArray(observer.cleanup)) {
292
- observer.cleanup.push(fn);
299
+ else if (isArray(cleanup)) {
300
+ cleanup.push(fn);
293
301
  }
294
302
  else {
295
- observer.cleanup = [observer.cleanup, fn];
303
+ observer.cleanup = [cleanup, fn];
296
304
  }
297
305
  return fn;
298
306
  };
@@ -304,11 +312,10 @@ const read = (node) => {
304
312
  if (height >= observer.height) {
305
313
  observer.height = height + 1;
306
314
  }
307
- if (height >= index ||
308
- node.state & (STATE_DIRTY | STATE_CHECK)) {
315
+ if (height >= heap_i || node.state & STATE_NOTIFY_MASK) {
309
316
  if (!notified) {
310
317
  notified = true;
311
- for (let i = 0; i <= length; i++) {
318
+ for (let i = 0; i <= heap_n; i++) {
312
319
  for (let computed = heap[i]; computed !== undefined; computed = computed.nextHeap) {
313
320
  notify(computed);
314
321
  }
package/package.json CHANGED
@@ -12,7 +12,7 @@
12
12
  "private": false,
13
13
  "type": "module",
14
14
  "types": "build/index.d.ts",
15
- "version": "0.12.2",
15
+ "version": "0.12.4",
16
16
  "scripts": {
17
17
  "build": "tsc && tsc-alias",
18
18
  "-": "-"
package/src/constants.ts CHANGED
@@ -26,6 +26,8 @@ const STATE_RECOMPUTING = 1 << 2;
26
26
 
27
27
  const STATE_IN_HEAP = 1 << 3;
28
28
 
29
+ const STATE_NOTIFY_MASK = (STATE_CHECK | STATE_DIRTY);
30
+
29
31
 
30
32
  export {
31
33
  COMPUTED,
@@ -33,6 +35,7 @@ export {
33
35
  REACTIVE_OBJECT,
34
36
  SIGNAL,
35
37
  STABILIZER_IDLE,
38
+ STATE_NOTIFY_MASK,
36
39
  STABILIZER_RESCHEDULE,
37
40
  STABILIZER_RUNNING,
38
41
  STABILIZER_SCHEDULED,
@@ -53,7 +53,9 @@ class ReactiveArray<T> {
53
53
  disposables: number = 0;
54
54
 
55
55
  private data: Item<T>[];
56
+
56
57
  private listeners: Record<string, (Listener<any> | null)[]> | null = null;
58
+
57
59
  private proxy: API<T>;
58
60
 
59
61
 
@@ -134,8 +136,7 @@ class ReactiveArray<T> {
134
136
  i?: number,
135
137
  n?: number
136
138
  ) {
137
- let { data, proxy } = this,
138
- values: R[] = [];
139
+ let { data, proxy } = this;
139
140
 
140
141
  if (i === undefined) {
141
142
  i = 0;
@@ -147,15 +148,15 @@ class ReactiveArray<T> {
147
148
 
148
149
  n = Math.min(n, data.length);
149
150
 
151
+ let values: R[] = new Array(n - i);
152
+
150
153
  for (; i < n; i++) {
151
154
  let item = data[i];
152
155
 
153
- values.push(
154
- fn.call(
155
- proxy,
156
- (isComputed(item) ? item.value : item) as Value<T>,
157
- i
158
- )
156
+ values[i] = fn.call(
157
+ proxy,
158
+ (isComputed(item) ? item.value : item) as Value<T>,
159
+ i
159
160
  );
160
161
  }
161
162
 
@@ -172,15 +173,21 @@ class ReactiveArray<T> {
172
173
  if (listeners === undefined) {
173
174
  this.listeners[event] = [listener];
174
175
  }
175
- else if (listeners.indexOf(listener) === -1) {
176
- let i = listeners.indexOf(null);
176
+ else {
177
+ let hole = listeners.length;
177
178
 
178
- if (i === -1) {
179
- listeners.push(listener);
180
- }
181
- else {
182
- listeners[i] = listener;
179
+ for (let i = 0, n = hole; i < n; i++) {
180
+ let l = listeners[i];
181
+
182
+ if (l === listener) {
183
+ return;
184
+ }
185
+ else if (l === null && hole === n) {
186
+ hole = i;
187
+ }
183
188
  }
189
+
190
+ listeners[hole] = listener;
184
191
  }
185
192
  }
186
193
  }
@@ -269,32 +276,28 @@ class ReactiveArray<T> {
269
276
 
270
277
 
271
278
  function factory<T>(input: T[]) {
272
- let items: Item<T>[] = [];
279
+ let n = input.length,
280
+ output: Item<T>[] = new Array(n);
273
281
 
274
- for (let i = 0, n = input.length; i < n; i++) {
282
+ for (let i = 0; i < n; i++) {
275
283
  let value = input[i];
276
284
 
277
285
  if (isFunction(value)) {
278
- items[i] = computed(value as Computed<T>['fn']);
286
+ output[i] = computed(value as Computed<T>['fn']);
279
287
  }
280
288
  else if (isObject(value)) {
281
- items[i] = object(value) as Item<T>;
289
+ output[i] = object(value) as Item<T>;
282
290
  }
283
291
  else {
284
- items[i] = value;
292
+ output[i] = value;
285
293
  }
286
294
  }
287
295
 
288
- return items;
296
+ return output;
289
297
  }
290
298
 
291
299
 
292
- const isReactiveArray = (value: any): value is ReactiveArray<any> => {
293
- return isObject(value) && REACTIVE_ARRAY in value;
294
- };
295
-
296
-
297
- export default function array<T>(input: T[]) {
300
+ export default <T>(input: T[]) => {
298
301
  let proxy = new Proxy({}, {
299
302
  get(_: any, key: any) {
300
303
  if (isNumber(key)) {
@@ -318,7 +321,8 @@ export default function array<T>(input: T[]) {
318
321
  return true;
319
322
  }
320
323
  else if (key === 'length') {
321
- return array.length = value;
324
+ array.length = value;
325
+ return true;
322
326
  }
323
327
 
324
328
  return false;
@@ -330,5 +334,4 @@ export default function array<T>(input: T[]) {
330
334
 
331
335
  return proxy;
332
336
  };
333
- export { isReactiveArray };
334
337
  export type { API as ReactiveArray };
@@ -1,8 +1,8 @@
1
1
  import { defineProperty, isArray, isFunction, isObject, isPromise, Prettify } from '@esportsplus/utilities';
2
- import { computed, dispose, effect, isComputed, read, root, set, signal } from '~/system';
2
+ import { computed, dispose, effect, read, root, set, signal } from '~/system';
3
3
  import { Computed, Infer, Signal } from '~/types';
4
4
  import { REACTIVE_OBJECT } from '~/constants';
5
- import array, { isReactiveArray } from './array';
5
+ import array from './array';
6
6
 
7
7
 
8
8
  type API<T extends Record<PropertyKey, unknown>> = Prettify<{ [K in keyof T]: Infer<T[K]> }> & ReactiveObject<T>;
@@ -12,65 +12,76 @@ class ReactiveObject<T extends Record<PropertyKey, unknown>> {
12
12
  [REACTIVE_OBJECT] = true;
13
13
 
14
14
 
15
+ private disposers: VoidFunction[] | null = null;
16
+
17
+
15
18
  constructor(data: T) {
16
- for (let key in data) {
17
- let value = data[key];
19
+ let keys = Object.keys(data);
20
+
21
+ for (let i = 0, n = keys.length; i < n; i++) {
22
+ let key = keys[i],
23
+ value = data[key];
18
24
 
19
25
  if (isArray(value)) {
20
- let a = array(value);
26
+ let node = array(value);
27
+
28
+ (this.disposers ??= []).push( () => node.dispose() );
21
29
 
22
30
  defineProperty(this, key, {
23
31
  enumerable: true,
24
- get() {
25
- return a;
26
- }
32
+ value: node
27
33
  });
28
34
  }
29
35
  else if (isFunction(value)) {
30
- let c: Computed<T[typeof key]> | Signal<T[typeof key] | undefined> | undefined;
36
+ let node: Computed<T[typeof key]> | Signal<T[typeof key] | undefined> | undefined;
31
37
 
32
38
  defineProperty(this, key, {
33
39
  enumerable: true,
34
- get() {
35
- if (c === undefined) {
40
+ get: () => {
41
+ if (node === undefined) {
36
42
  root(() => {
37
- c = computed(value as Computed<T[typeof key]>['fn']);
43
+ node = computed(value as Computed<T[typeof key]>['fn']);
38
44
 
39
- if (isPromise(c.value)) {
40
- let factory = c,
45
+ if (isPromise(node.value)) {
46
+ let factory = node,
41
47
  version = 0;
42
48
 
43
- c = signal(undefined);
49
+ node = signal<T[typeof key] | undefined>(undefined);
44
50
 
45
- effect(() => {
46
- let id = ++version;
51
+ (this.disposers ??= []).push(
52
+ effect(() => {
53
+ let id = ++version;
47
54
 
48
- (read(factory) as Promise<T[typeof key]>).then((value) => {
49
- if (id !== version) {
50
- return;
51
- }
55
+ (read(factory) as Promise<T[typeof key]>).then((v) => {
56
+ if (id !== version) {
57
+ return;
58
+ }
52
59
 
53
- set(c as Signal<typeof value>, value);
54
- });
55
- });
60
+ set(node as Signal<typeof v>, v);
61
+ });
62
+ })
63
+ )
64
+ }
65
+ else {
66
+ (this.disposers ??= []).push(() => dispose(node as Computed<T[typeof key]>));
56
67
  }
57
68
  });
58
69
  }
59
70
 
60
- return read(c!);
71
+ return read(node!);
61
72
  }
62
73
  });
63
74
  }
64
75
  else {
65
- let s = signal(value);
76
+ let node = signal(value);
66
77
 
67
78
  defineProperty(this, key, {
68
79
  enumerable: true,
69
80
  get() {
70
- return read(s);
81
+ return read(node);
71
82
  },
72
83
  set(v: typeof value) {
73
- set(s, v);
84
+ set(node, v);
74
85
  }
75
86
  });
76
87
  }
@@ -79,16 +90,11 @@ class ReactiveObject<T extends Record<PropertyKey, unknown>> {
79
90
 
80
91
 
81
92
  dispose() {
82
- let value;
93
+ let disposers = this.disposers;
83
94
 
84
- for (let key in this) {
85
- value = this[key];
86
-
87
- if (isReactiveArray(value) || isReactiveObject(value)) {
88
- value.dispose();
89
- }
90
- else if (isComputed(value)) {
91
- dispose(value);
95
+ if (disposers) {
96
+ for (let i = 0, n = disposers.length; i < n; i++) {
97
+ disposers[i]();
92
98
  }
93
99
  }
94
100
  }
@@ -100,8 +106,8 @@ const isReactiveObject = (value: any): value is ReactiveObject<any> => {
100
106
  };
101
107
 
102
108
 
103
- export default function object<T extends Record<PropertyKey, unknown>>(input: T) {
104
- return new ReactiveObject<T>(input);
109
+ export default <T extends Record<PropertyKey, unknown>>(input: T) => {
110
+ return new ReactiveObject<T>(input) as API<T>;
105
111
  };
106
112
  export { isReactiveObject };
107
113
  export type { API as ReactiveObject };
package/src/system.ts CHANGED
@@ -2,15 +2,15 @@ import { isArray, isObject } from '@esportsplus/utilities';
2
2
  import {
3
3
  COMPUTED, SIGNAL,
4
4
  STABILIZER_IDLE, STABILIZER_RESCHEDULE, STABILIZER_RUNNING, STABILIZER_SCHEDULED,
5
- STATE_CHECK, STATE_DIRTY, STATE_IN_HEAP, STATE_NONE, STATE_RECOMPUTING
5
+ STATE_CHECK, STATE_DIRTY, STATE_IN_HEAP, STATE_NONE, STATE_NOTIFY_MASK, STATE_RECOMPUTING
6
6
  } from './constants';
7
7
  import { Computed, Link, Signal } from './types';
8
8
 
9
9
 
10
10
  let depth = 0,
11
- heap: (Computed<unknown> | undefined)[] = new Array(2048),
12
- index = 0,
13
- length = 0,
11
+ heap: (Computed<unknown> | undefined)[] = new Array(64),
12
+ heap_i = 0,
13
+ heap_n = 0,
14
14
  microtask = queueMicrotask,
15
15
  notified = false,
16
16
  observer: Computed<unknown> | null = null,
@@ -93,12 +93,12 @@ function insertIntoHeap<T>(computed: Computed<T>) {
93
93
  heapAtHeight.prevHeap = computed;
94
94
  }
95
95
 
96
- if (height > length) {
97
- length = height;
96
+ if (height > heap_n) {
97
+ heap_n = height;
98
98
 
99
99
  // Simple auto adjust to avoid manual management within apps.
100
100
  if (height >= heap.length) {
101
- heap.length = Math.round(heap.length * 1.5);
101
+ heap.length = Math.max(height + 1, Math.ceil(heap.length * 2));
102
102
  }
103
103
  }
104
104
  }
@@ -163,7 +163,7 @@ function link<T>(dep: Signal<T> | Computed<T>, sub: Computed<T>) {
163
163
  function notify<T>(computed: Computed<T>, newState = STATE_DIRTY) {
164
164
  let state = computed.state;
165
165
 
166
- if ((state & (STATE_CHECK | STATE_DIRTY)) >= newState) {
166
+ if ((state & STATE_NOTIFY_MASK) >= newState) {
167
167
  return;
168
168
  }
169
169
 
@@ -183,7 +183,9 @@ function recompute<T>(computed: Computed<T>, del: boolean) {
183
183
  computed.prevHeap = computed;
184
184
  }
185
185
 
186
- cleanup(computed);
186
+ if (computed.cleanup) {
187
+ cleanup(computed);
188
+ }
187
189
 
188
190
  let o = observer,
189
191
  ok = true,
@@ -258,10 +260,10 @@ function stabilize() {
258
260
  observer = null;
259
261
  stabilizer = STABILIZER_RUNNING;
260
262
 
261
- for (index = 0; index <= length; index++) {
262
- let computed = heap[index];
263
+ for (heap_i = 0; heap_i <= heap_n; heap_i++) {
264
+ let computed = heap[heap_i];
263
265
 
264
- heap[index] = undefined;
266
+ heap[heap_i] = undefined;
265
267
 
266
268
  while (computed !== undefined) {
267
269
  let next = computed.nextHeap;
@@ -272,6 +274,10 @@ function stabilize() {
272
274
  }
273
275
  }
274
276
 
277
+ while (heap_n > 0 && heap[heap_n] === undefined) {
278
+ heap_n--;
279
+ }
280
+
275
281
  observer = o;
276
282
 
277
283
  if (stabilizer === STABILIZER_RESCHEDULE) {
@@ -284,7 +290,10 @@ function stabilize() {
284
290
 
285
291
  // https://github.com/stackblitz/alien-signals/blob/v2.0.3/src/system.ts#L100
286
292
  function unlink(link: Link): Link | null {
287
- let { dep, nextDep, nextSub, prevSub } = link;
293
+ let dep = link.dep,
294
+ nextDep = link.nextDep,
295
+ nextSub = link.nextSub,
296
+ prevSub = link.prevSub;
288
297
 
289
298
  if (nextSub !== null) {
290
299
  nextSub.prevSub = prevSub;
@@ -377,7 +386,9 @@ const dispose = <T>(computed: Computed<T>) => {
377
386
 
378
387
  computed.deps = null;
379
388
 
380
- cleanup(computed);
389
+ if (computed.cleanup) {
390
+ cleanup(computed);
391
+ }
381
392
  };
382
393
 
383
394
  const effect = <T>(fn: Computed<T>['fn']) => {
@@ -401,14 +412,16 @@ const onCleanup = (fn: VoidFunction): typeof fn => {
401
412
  return fn;
402
413
  }
403
414
 
404
- if (!observer.cleanup) {
415
+ let cleanup = observer.cleanup;
416
+
417
+ if (!cleanup) {
405
418
  observer.cleanup = fn;
406
419
  }
407
- else if (isArray(observer.cleanup)) {
408
- observer.cleanup.push(fn);
420
+ else if (isArray(cleanup)) {
421
+ cleanup.push(fn);
409
422
  }
410
423
  else {
411
- observer.cleanup = [observer.cleanup, fn];
424
+ observer.cleanup = [cleanup, fn];
412
425
  }
413
426
 
414
427
  return fn;
@@ -425,14 +438,11 @@ const read = <T>(node: Signal<T> | Computed<T>): T => {
425
438
  observer.height = height + 1;
426
439
  }
427
440
 
428
- if (
429
- height >= index ||
430
- node.state & (STATE_DIRTY | STATE_CHECK)
431
- ) {
441
+ if (height >= heap_i || node.state & STATE_NOTIFY_MASK) {
432
442
  if (!notified) {
433
443
  notified = true;
434
444
 
435
- for (let i = 0; i <= length; i++) {
445
+ for (let i = 0; i <= heap_n; i++) {
436
446
  for (let computed = heap[i]; computed !== undefined; computed = computed.nextHeap) {
437
447
  notify(computed);
438
448
  }