@esportsplus/reactivity 0.38.1 → 0.39.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.
- package/bench/ownership.bench.ts +212 -0
- package/build/reactive/array.d.ts +3 -2
- package/build/reactive/array.js +28 -10
- package/build/reactive/index.js +1 -1
- package/build/system.d.ts +2 -1
- package/build/system.js +93 -37
- package/build/types.d.ts +5 -0
- package/package.json +1 -1
- package/src/reactive/array.ts +42 -13
- package/src/reactive/index.ts +1 -1
- package/src/system.ts +134 -43
- package/src/types.ts +5 -0
- package/test/ownership.test.ts +569 -0
- package/test/reactive/array.test.ts +68 -0
- package/test/system.test.ts +2 -2
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import { test } from 'vitest';
|
|
2
|
+
import { computed, dispose, effect, flush, onCleanup, read, root, signal, write } from '~/system';
|
|
3
|
+
import type { Computed } from '~/system';
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
const CHURN = 1000;
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
function noop() {
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// Owner registrations still held after churn: user cleanups plus owned children (`owned` is absent
|
|
13
|
+
// before the owned list existed, so this also counts a baseline tree's closure array)
|
|
14
|
+
function retained(node: Computed<unknown>): number {
|
|
15
|
+
let n = node.cleanup === null ? 0 : typeof node.cleanup === 'function' ? 1 : node.cleanup.length;
|
|
16
|
+
|
|
17
|
+
for (let child = node.owned; child; child = child.nextOwned) {
|
|
18
|
+
n++;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return n;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
test('ownership churn', async ({ bench }) => {
|
|
26
|
+
await bench.compare(
|
|
27
|
+
bench(`${CHURN} child roots created + disposed under one owner`, () => {
|
|
28
|
+
let owner = computed(() => {
|
|
29
|
+
for (let i = 0; i < CHURN; i++) {
|
|
30
|
+
root((d) => {
|
|
31
|
+
onCleanup(noop);
|
|
32
|
+
d();
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return 0;
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
dispose(owner);
|
|
40
|
+
}),
|
|
41
|
+
bench(`${CHURN} child effects created + disposed under one owner`, () => {
|
|
42
|
+
let owner = computed(() => {
|
|
43
|
+
for (let i = 0; i < CHURN; i++) {
|
|
44
|
+
effect(() => {
|
|
45
|
+
onCleanup(noop);
|
|
46
|
+
})();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return 0;
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
dispose(owner);
|
|
53
|
+
})
|
|
54
|
+
);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
test('ownership churn retention', () => {
|
|
59
|
+
let gc = (globalThis as { gc?: () => void }).gc,
|
|
60
|
+
heap = 0;
|
|
61
|
+
|
|
62
|
+
for (let n of [1000, 10000]) {
|
|
63
|
+
gc?.();
|
|
64
|
+
|
|
65
|
+
let before = process.memoryUsage().heapUsed,
|
|
66
|
+
owner = computed(() => {
|
|
67
|
+
for (let i = 0; i < n; i++) {
|
|
68
|
+
root((d) => {
|
|
69
|
+
onCleanup(noop);
|
|
70
|
+
d();
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return 0;
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
gc?.();
|
|
78
|
+
heap = process.memoryUsage().heapUsed - before;
|
|
79
|
+
|
|
80
|
+
console.log(`[ownership] churn ${n}: retained registrations = ${retained(owner as Computed<unknown>)}, heap delta = ${gc ? `${(heap / 1024).toFixed(1)} KiB` : 'n/a (run with --expose-gc)'}`);
|
|
81
|
+
dispose(owner);
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
test('ownership effect re-run', async ({ bench }) => {
|
|
87
|
+
let s = signal(0),
|
|
88
|
+
nested = signal(0),
|
|
89
|
+
i = 0;
|
|
90
|
+
|
|
91
|
+
let stop = effect(() => {
|
|
92
|
+
read(s);
|
|
93
|
+
|
|
94
|
+
for (let j = 0; j < 10; j++) {
|
|
95
|
+
effect(() => {
|
|
96
|
+
read(nested);
|
|
97
|
+
onCleanup(noop);
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
await bench.compare(
|
|
103
|
+
bench('re-run effect owning 10 child effects', () => {
|
|
104
|
+
write(s, ++i);
|
|
105
|
+
flush();
|
|
106
|
+
}),
|
|
107
|
+
bench('re-run 10 child effects (parent stable)', () => {
|
|
108
|
+
write(nested, ++i);
|
|
109
|
+
flush();
|
|
110
|
+
})
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
stop();
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
test('ownership tree dispose', async ({ bench }) => {
|
|
118
|
+
await bench.compare(
|
|
119
|
+
bench('deep owner tree (1000 nested roots) disposed at once', () => {
|
|
120
|
+
root((d) => {
|
|
121
|
+
let parent = (depth: number): void => {
|
|
122
|
+
if (depth === 0) {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
root(() => {
|
|
127
|
+
onCleanup(noop);
|
|
128
|
+
parent(depth - 1);
|
|
129
|
+
});
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
parent(1000);
|
|
133
|
+
d();
|
|
134
|
+
});
|
|
135
|
+
}),
|
|
136
|
+
bench('deep owner tree (200 nested effects) disposed at once', () => {
|
|
137
|
+
root((d) => {
|
|
138
|
+
let parent = (depth: number): void => {
|
|
139
|
+
if (depth === 0) {
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
effect(() => {
|
|
144
|
+
onCleanup(noop);
|
|
145
|
+
parent(depth - 1);
|
|
146
|
+
});
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
parent(200);
|
|
150
|
+
d();
|
|
151
|
+
});
|
|
152
|
+
}),
|
|
153
|
+
bench('wide owner tree (1000 child effects) disposed at once', () => {
|
|
154
|
+
root((d) => {
|
|
155
|
+
for (let i = 0; i < 1000; i++) {
|
|
156
|
+
effect(() => {
|
|
157
|
+
onCleanup(noop);
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
d();
|
|
162
|
+
});
|
|
163
|
+
}),
|
|
164
|
+
bench('wide x deep owner tree (32 x 32 effects) disposed at once', () => {
|
|
165
|
+
root((d) => {
|
|
166
|
+
for (let i = 0; i < 32; i++) {
|
|
167
|
+
effect(() => {
|
|
168
|
+
for (let j = 0; j < 32; j++) {
|
|
169
|
+
effect(() => {
|
|
170
|
+
onCleanup(noop);
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
d();
|
|
177
|
+
});
|
|
178
|
+
})
|
|
179
|
+
);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
test('ownership single cleanup', async ({ bench }) => {
|
|
184
|
+
let s = signal(0),
|
|
185
|
+
i = 0;
|
|
186
|
+
|
|
187
|
+
let stop = effect(() => {
|
|
188
|
+
read(s);
|
|
189
|
+
onCleanup(noop);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
await bench.compare(
|
|
193
|
+
bench('effect re-run with 1 cleanup', () => {
|
|
194
|
+
write(s, ++i);
|
|
195
|
+
flush();
|
|
196
|
+
}),
|
|
197
|
+
bench('computed create + dispose with 1 cleanup', () => {
|
|
198
|
+
dispose(computed(() => {
|
|
199
|
+
onCleanup(noop);
|
|
200
|
+
return 0;
|
|
201
|
+
}));
|
|
202
|
+
}),
|
|
203
|
+
bench('root create + dispose with 1 cleanup', () => {
|
|
204
|
+
root((d) => {
|
|
205
|
+
onCleanup(noop);
|
|
206
|
+
d();
|
|
207
|
+
});
|
|
208
|
+
})
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
stop();
|
|
212
|
+
});
|
|
@@ -50,8 +50,9 @@ declare class ReactiveArray<T> extends Array<T> {
|
|
|
50
50
|
concat(...items: (T | ConcatArray<T>)[]): ReactiveArray<T>;
|
|
51
51
|
dispatch<K extends keyof Events<T>>(event: K, value?: Events<T>[K]): void;
|
|
52
52
|
dispose(): void;
|
|
53
|
-
on<K extends keyof Events<T>>(event: K, listener: Listener<Events<T>[K]>):
|
|
54
|
-
once<K extends keyof Events<T>>(event: K, listener: Listener<Events<T>[K]>):
|
|
53
|
+
on<K extends keyof Events<T>>(event: K, listener: Listener<Events<T>[K]>): VoidFunction;
|
|
54
|
+
once<K extends keyof Events<T>>(event: K, listener: Listener<Events<T>[K]>): VoidFunction;
|
|
55
|
+
private prune;
|
|
55
56
|
pop(): T | undefined;
|
|
56
57
|
push(...items: T[]): number;
|
|
57
58
|
reverse(): this;
|
package/build/reactive/array.js
CHANGED
|
@@ -77,7 +77,7 @@ class ReactiveArray extends Array {
|
|
|
77
77
|
let dirty = false, errors = null;
|
|
78
78
|
for (let i = 0, n = listeners.length; i < n; i++) {
|
|
79
79
|
let listener = listeners[i];
|
|
80
|
-
if (listener
|
|
80
|
+
if (listener == null) {
|
|
81
81
|
continue;
|
|
82
82
|
}
|
|
83
83
|
try {
|
|
@@ -94,9 +94,7 @@ class ReactiveArray extends Array {
|
|
|
94
94
|
}
|
|
95
95
|
}
|
|
96
96
|
if (dirty) {
|
|
97
|
-
|
|
98
|
-
listeners.pop();
|
|
99
|
-
}
|
|
97
|
+
this.prune(event, listeners);
|
|
100
98
|
}
|
|
101
99
|
if (errors !== null) {
|
|
102
100
|
let error = errors.length === 1
|
|
@@ -119,25 +117,45 @@ class ReactiveArray extends Array {
|
|
|
119
117
|
this.listeners[event] = [entry];
|
|
120
118
|
}
|
|
121
119
|
else {
|
|
122
|
-
let hole = listeners.length;
|
|
120
|
+
let hole = listeners.length, registered = false;
|
|
123
121
|
for (let i = 0, n = hole; i < n; i++) {
|
|
124
122
|
let l = listeners[i];
|
|
125
123
|
if (l === entry) {
|
|
126
|
-
|
|
124
|
+
registered = true;
|
|
125
|
+
break;
|
|
127
126
|
}
|
|
128
127
|
else if (l === null && hole === n) {
|
|
129
128
|
hole = i;
|
|
130
129
|
}
|
|
131
130
|
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
131
|
+
if (!registered) {
|
|
132
|
+
listeners[hole] = entry;
|
|
133
|
+
this.prune(event, listeners);
|
|
135
134
|
}
|
|
136
135
|
}
|
|
136
|
+
return () => {
|
|
137
|
+
let current = this.listeners[event];
|
|
138
|
+
if (current === undefined) {
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
let i = current.indexOf(entry);
|
|
142
|
+
if (i !== -1) {
|
|
143
|
+
current[i] = null;
|
|
144
|
+
this.prune(event, current);
|
|
145
|
+
}
|
|
146
|
+
};
|
|
137
147
|
}
|
|
138
148
|
once(event, listener) {
|
|
139
149
|
listener.once = true;
|
|
140
|
-
this.on(event, listener);
|
|
150
|
+
return this.on(event, listener);
|
|
151
|
+
}
|
|
152
|
+
prune(event, listeners) {
|
|
153
|
+
while (listeners.length && listeners[listeners.length - 1] === null) {
|
|
154
|
+
listeners.pop();
|
|
155
|
+
}
|
|
156
|
+
if (listeners.length === 0 && this.listeners[event] === listeners) {
|
|
157
|
+
delete this.listeners[event];
|
|
158
|
+
}
|
|
141
159
|
}
|
|
142
160
|
pop() {
|
|
143
161
|
if (this.length === 0) {
|
package/build/reactive/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { onCleanup, root } from '@esportsplus/reactivity';
|
|
2
1
|
import { isArray, isObject } from '@esportsplus/utilities';
|
|
3
2
|
import { PACKAGE_NAME } from '../constants.js';
|
|
3
|
+
import { onCleanup, root } from '../system.js';
|
|
4
4
|
import { ReactiveArray } from './array.js';
|
|
5
5
|
import { ReactiveObject } from './object.js';
|
|
6
6
|
function reactive(input) {
|
package/build/system.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ declare namespace computed {
|
|
|
7
7
|
declare const dispose: <T>(computed: Computed<T>) => void;
|
|
8
8
|
declare const effect: <T>(fn: Computed<T>['fn'], apply?: (value: T, prev: T | undefined) => void) => () => void;
|
|
9
9
|
declare const flush: () => void;
|
|
10
|
+
declare const hasOwner: () => boolean;
|
|
10
11
|
declare const isComputed: (value: unknown) => value is Computed<unknown>;
|
|
11
12
|
declare const isSignal: (value: unknown) => value is Signal<unknown>;
|
|
12
13
|
declare const onCleanup: (fn: VoidFunction) => typeof fn;
|
|
@@ -19,5 +20,5 @@ declare namespace signal {
|
|
|
19
20
|
}
|
|
20
21
|
declare const untrack: <T>(fn: () => T) => T;
|
|
21
22
|
declare const write: <T>(signal: Signal<T>, value: T) => void;
|
|
22
|
-
export { batch, computed, dispose, effect, flush, isComputed, isSignal, onCleanup, peek, read, root, signal, untrack, write };
|
|
23
|
+
export { batch, computed, dispose, effect, flush, hasOwner, isComputed, isSignal, onCleanup, peek, read, root, signal, untrack, write };
|
|
23
24
|
export type { Computed, Signal };
|
package/build/system.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { PACKAGE_NAME, SIGNAL, STABILIZER_DEFERRED, STABILIZER_IDLE, STABILIZER_RESCHEDULE, STABILIZER_RUNNING, STABILIZER_SCHEDULED, STATE_CHECK, STATE_COMPUTED, STATE_DIRTY, STATE_EFFECT, STATE_ERROR, STATE_IN_HEAP, STATE_NOTIFY_MASK, STATE_RECOMPUTING } from './constants.js';
|
|
2
2
|
import { isObject, isPromise } from '@esportsplus/utilities';
|
|
3
|
-
let
|
|
3
|
+
let asyncFactory = new WeakMap(), depth = 0, disposeHead = null, draining = false, heap = new Array(64), heap_i = 0, heap_n = 0, linkPoolHead = null, microtask = queueMicrotask, notified = false, observer = null, pendingHead = null, pulled = null, puller = null, scope = null, stabilizer = STABILIZER_IDLE, version = 0, walkPoolHead = null, writes = 0;
|
|
4
4
|
function noop() {
|
|
5
5
|
}
|
|
6
6
|
function walkPop(walk) {
|
|
@@ -23,30 +23,33 @@ function walkPush(computed, link, prev) {
|
|
|
23
23
|
return { computed, link, prev };
|
|
24
24
|
}
|
|
25
25
|
function cleanup(computed) {
|
|
26
|
-
|
|
26
|
+
let child = computed.owned, value = computed.cleanup;
|
|
27
|
+
computed.cleanup = null;
|
|
28
|
+
if (child === null && typeof value === 'function') {
|
|
29
|
+
value();
|
|
27
30
|
return;
|
|
28
31
|
}
|
|
29
|
-
let errors =
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
32
|
+
let errors = null, n = value === null ? 0 : typeof value === 'function' ? 1 : value.length;
|
|
33
|
+
for (let i = 0; i <= n; i++) {
|
|
34
|
+
while ((child = computed.owned) !== null && (i === n || child.ownerSlot <= i)) {
|
|
35
|
+
disown(child);
|
|
36
|
+
try {
|
|
37
|
+
dispose(child);
|
|
38
|
+
}
|
|
39
|
+
catch (e) {
|
|
40
|
+
(errors ??= []).push(e);
|
|
41
|
+
}
|
|
37
42
|
}
|
|
38
|
-
|
|
39
|
-
else {
|
|
40
|
-
for (let i = 0, n = value.length; i < n; i++) {
|
|
43
|
+
if (i < n) {
|
|
41
44
|
try {
|
|
42
|
-
value[i]();
|
|
45
|
+
(typeof value === 'function' ? value : value[i])();
|
|
43
46
|
}
|
|
44
47
|
catch (e) {
|
|
45
|
-
errors.push(e);
|
|
48
|
+
(errors ??= []).push(e);
|
|
46
49
|
}
|
|
47
50
|
}
|
|
48
51
|
}
|
|
49
|
-
if (errors
|
|
52
|
+
if (errors !== null) {
|
|
50
53
|
throw errors.length === 1 ? errors[0] : new AggregateError(errors, `${PACKAGE_NAME}: cleanup produced multiple errors`);
|
|
51
54
|
}
|
|
52
55
|
}
|
|
@@ -73,6 +76,20 @@ function deleteFromHeap(computed) {
|
|
|
73
76
|
computed.nextHeap = undefined;
|
|
74
77
|
computed.prevHeap = computed;
|
|
75
78
|
}
|
|
79
|
+
function disown(child) {
|
|
80
|
+
let next = child.nextOwned, owner = child.owner, prev = child.prevOwned;
|
|
81
|
+
if (owner.owned === child) {
|
|
82
|
+
owner.owned = next;
|
|
83
|
+
if (next !== null) {
|
|
84
|
+
next.prevOwned = prev;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
prev.nextOwned = next;
|
|
89
|
+
(next ?? owner.owned).prevOwned = prev;
|
|
90
|
+
}
|
|
91
|
+
child.nextOwned = child.owner = child.prevOwned = null;
|
|
92
|
+
}
|
|
76
93
|
function drainPending() {
|
|
77
94
|
let node = pendingHead;
|
|
78
95
|
pendingHead = null;
|
|
@@ -198,6 +215,21 @@ function notify(computed, newState) {
|
|
|
198
215
|
stack = walkPop(stack);
|
|
199
216
|
}
|
|
200
217
|
}
|
|
218
|
+
function own(owner, child) {
|
|
219
|
+
let head = owner.owned, value = owner.cleanup;
|
|
220
|
+
child.owner = owner;
|
|
221
|
+
child.ownerSlot = value === null ? 0 : typeof value === 'function' ? 1 : value.length;
|
|
222
|
+
if (head === null) {
|
|
223
|
+
owner.owned = child;
|
|
224
|
+
child.prevOwned = child;
|
|
225
|
+
}
|
|
226
|
+
else {
|
|
227
|
+
let tail = head.prevOwned;
|
|
228
|
+
tail.nextOwned = child;
|
|
229
|
+
child.prevOwned = tail;
|
|
230
|
+
head.prevOwned = child;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
201
233
|
function pull(node) {
|
|
202
234
|
if (!notified) {
|
|
203
235
|
notified = true;
|
|
@@ -238,7 +270,7 @@ function recompute(computed) {
|
|
|
238
270
|
if (computed.state & STATE_IN_HEAP) {
|
|
239
271
|
deleteFromHeap(computed);
|
|
240
272
|
}
|
|
241
|
-
if (computed.cleanup) {
|
|
273
|
+
if (computed.cleanup !== null || computed.owned !== null) {
|
|
242
274
|
try {
|
|
243
275
|
cleanup(computed);
|
|
244
276
|
}
|
|
@@ -486,7 +518,7 @@ function makeAsyncComputed(factory) {
|
|
|
486
518
|
return read(node);
|
|
487
519
|
});
|
|
488
520
|
wrapper.pending = pending;
|
|
489
|
-
|
|
521
|
+
asyncFactory.set(wrapper, factory);
|
|
490
522
|
wrapper.disposal = stop;
|
|
491
523
|
return wrapper;
|
|
492
524
|
}
|
|
@@ -507,12 +539,12 @@ function makeComputed(fn, eager = false) {
|
|
|
507
539
|
schedule();
|
|
508
540
|
}
|
|
509
541
|
link(self, observer);
|
|
510
|
-
|
|
542
|
+
own(observer, self);
|
|
511
543
|
}
|
|
512
544
|
else {
|
|
513
545
|
recompute(self);
|
|
514
546
|
if (scope) {
|
|
515
|
-
|
|
547
|
+
own(scope, self);
|
|
516
548
|
}
|
|
517
549
|
}
|
|
518
550
|
return self;
|
|
@@ -529,8 +561,13 @@ function makeNode(fn) {
|
|
|
529
561
|
gv: 0,
|
|
530
562
|
height: 0,
|
|
531
563
|
nextHeap: undefined,
|
|
564
|
+
nextOwned: null,
|
|
565
|
+
owned: null,
|
|
566
|
+
owner: null,
|
|
567
|
+
ownerSlot: 0,
|
|
532
568
|
pending: null,
|
|
533
569
|
prevHeap: null,
|
|
570
|
+
prevOwned: null,
|
|
534
571
|
rv: 0,
|
|
535
572
|
state: STATE_COMPUTED,
|
|
536
573
|
subs: null,
|
|
@@ -556,18 +593,19 @@ const batch = (fn) => {
|
|
|
556
593
|
const computed = (fn, equals = null) => {
|
|
557
594
|
let o = observer, self = makeComputed(fn, true), value = self.value;
|
|
558
595
|
if (isPromise(value) || (value != null && typeof value[Symbol.asyncIterator] === 'function')) {
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
596
|
+
let owner = o || scope;
|
|
597
|
+
self = root(() => makeAsyncComputed(self));
|
|
598
|
+
if (owner !== null) {
|
|
599
|
+
own(owner, self);
|
|
562
600
|
}
|
|
563
601
|
}
|
|
564
602
|
self.equals = equals;
|
|
565
603
|
return self;
|
|
566
604
|
};
|
|
567
605
|
computed.invalidate = (c) => {
|
|
568
|
-
let
|
|
569
|
-
if (
|
|
570
|
-
computed.invalidate(
|
|
606
|
+
let factory = asyncFactory.get(c);
|
|
607
|
+
if (factory) {
|
|
608
|
+
computed.invalidate(factory);
|
|
571
609
|
return;
|
|
572
610
|
}
|
|
573
611
|
writes++;
|
|
@@ -581,7 +619,7 @@ const dispose = (computed) => {
|
|
|
581
619
|
return;
|
|
582
620
|
}
|
|
583
621
|
draining = true;
|
|
584
|
-
let node = computed;
|
|
622
|
+
let errors = null, node = computed;
|
|
585
623
|
try {
|
|
586
624
|
for (;;) {
|
|
587
625
|
deleteFromHeap(node);
|
|
@@ -590,13 +628,26 @@ const dispose = (computed) => {
|
|
|
590
628
|
dep = unlink(dep);
|
|
591
629
|
}
|
|
592
630
|
node.deps = null;
|
|
593
|
-
if (node.
|
|
594
|
-
|
|
631
|
+
if (node.owner !== null) {
|
|
632
|
+
disown(node);
|
|
633
|
+
}
|
|
634
|
+
if (node.cleanup !== null || node.owned !== null) {
|
|
635
|
+
try {
|
|
636
|
+
cleanup(node);
|
|
637
|
+
}
|
|
638
|
+
catch (e) {
|
|
639
|
+
(errors ??= []).push(e);
|
|
640
|
+
}
|
|
595
641
|
}
|
|
596
642
|
if (node.disposal) {
|
|
597
643
|
let d = node.disposal;
|
|
598
644
|
node.disposal = null;
|
|
599
|
-
|
|
645
|
+
try {
|
|
646
|
+
d();
|
|
647
|
+
}
|
|
648
|
+
catch (e) {
|
|
649
|
+
(errors ??= []).push(e);
|
|
650
|
+
}
|
|
600
651
|
}
|
|
601
652
|
if (disposeHead === null) {
|
|
602
653
|
break;
|
|
@@ -608,6 +659,9 @@ const dispose = (computed) => {
|
|
|
608
659
|
finally {
|
|
609
660
|
draining = false;
|
|
610
661
|
}
|
|
662
|
+
if (errors !== null) {
|
|
663
|
+
throw errors.length === 1 ? errors[0] : new AggregateError(errors, `${PACKAGE_NAME}: dispose produced multiple errors`);
|
|
664
|
+
}
|
|
611
665
|
};
|
|
612
666
|
const effect = (fn, apply) => {
|
|
613
667
|
let prev;
|
|
@@ -635,6 +689,7 @@ const flush = () => {
|
|
|
635
689
|
stabilize();
|
|
636
690
|
}
|
|
637
691
|
};
|
|
692
|
+
const hasOwner = () => observer !== null || scope !== null;
|
|
638
693
|
const isComputed = (value) => {
|
|
639
694
|
return isObject(value) && !!(value.state & STATE_COMPUTED);
|
|
640
695
|
};
|
|
@@ -697,12 +752,16 @@ const read = (node) => {
|
|
|
697
752
|
return node.value;
|
|
698
753
|
};
|
|
699
754
|
const root = (fn) => {
|
|
700
|
-
let
|
|
755
|
+
let o = observer, s = scope, tracking = fn.length, value;
|
|
701
756
|
observer = null;
|
|
702
757
|
try {
|
|
703
758
|
if (tracking) {
|
|
704
|
-
|
|
705
|
-
|
|
759
|
+
let self = makeNode(noop), owner = o || s;
|
|
760
|
+
if (owner !== null) {
|
|
761
|
+
own(owner, self);
|
|
762
|
+
}
|
|
763
|
+
scope = self;
|
|
764
|
+
value = fn(() => dispose(self));
|
|
706
765
|
}
|
|
707
766
|
else {
|
|
708
767
|
scope = null;
|
|
@@ -713,9 +772,6 @@ const root = (fn) => {
|
|
|
713
772
|
observer = o;
|
|
714
773
|
scope = s;
|
|
715
774
|
}
|
|
716
|
-
if (c) {
|
|
717
|
-
onCleanup(c);
|
|
718
|
-
}
|
|
719
775
|
return value;
|
|
720
776
|
};
|
|
721
777
|
const signal = (value, equals = null) => {
|
|
@@ -792,4 +848,4 @@ const write = (signal, value) => {
|
|
|
792
848
|
}
|
|
793
849
|
schedule();
|
|
794
850
|
};
|
|
795
|
-
export { batch, computed, dispose, effect, flush, isComputed, isSignal, onCleanup, peek, read, root, signal, untrack, write };
|
|
851
|
+
export { batch, computed, dispose, effect, flush, hasOwner, isComputed, isSignal, onCleanup, peek, read, root, signal, untrack, write };
|
package/build/types.d.ts
CHANGED
|
@@ -11,8 +11,13 @@ interface Computed<T> {
|
|
|
11
11
|
gv: number;
|
|
12
12
|
height: number;
|
|
13
13
|
nextHeap: Computed<unknown> | undefined;
|
|
14
|
+
nextOwned: Computed<unknown> | null;
|
|
15
|
+
owned: Computed<unknown> | null;
|
|
16
|
+
owner: Computed<unknown> | null;
|
|
17
|
+
ownerSlot: number;
|
|
14
18
|
pending: Signal<boolean> | null;
|
|
15
19
|
prevHeap: Computed<unknown>;
|
|
20
|
+
prevOwned: Computed<unknown> | null;
|
|
16
21
|
rv: number;
|
|
17
22
|
state: number;
|
|
18
23
|
subs: Link | null;
|