@zeix/cause-effect 0.12.4 → 0.13.1
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/.prettierrc +7 -0
- package/README.md +140 -94
- package/index.d.ts +5 -4
- package/index.js +1 -1
- package/index.ts +6 -6
- package/lib/computed.d.ts +16 -7
- package/lib/computed.ts +100 -44
- package/lib/effect.d.ts +17 -4
- package/lib/effect.ts +49 -17
- package/lib/scheduler.d.ts +9 -6
- package/lib/scheduler.ts +16 -8
- package/lib/signal.d.ts +17 -33
- package/lib/signal.ts +49 -77
- package/lib/state.d.ts +3 -3
- package/lib/state.ts +29 -22
- package/lib/util.d.ts +8 -5
- package/lib/util.ts +18 -11
- package/package.json +2 -2
- package/test/batch.test.ts +43 -42
- package/test/benchmark.test.ts +315 -319
- package/test/computed.test.ts +341 -242
- package/test/effect.test.ts +136 -119
- package/test/state.test.ts +126 -118
package/test/benchmark.test.ts
CHANGED
|
@@ -1,39 +1,36 @@
|
|
|
1
1
|
import { describe, test, expect, mock } from 'bun:test'
|
|
2
|
-
import { state, computed, effect, batch } from '../'
|
|
3
|
-
import { makeGraph, runGraph, Counter } from "./util/dependency-graph"
|
|
2
|
+
import { state, computed, effect, batch } from '../'
|
|
3
|
+
import { makeGraph, runGraph, Counter } from "./util/dependency-graph"
|
|
4
4
|
|
|
5
5
|
/* === Utility Functions === */
|
|
6
6
|
|
|
7
7
|
const busy = () => {
|
|
8
|
-
let a = 0
|
|
8
|
+
let a = 0
|
|
9
9
|
for (let i = 0; i < 1_00; i++) {
|
|
10
|
-
a
|
|
10
|
+
a++
|
|
11
11
|
}
|
|
12
|
-
}
|
|
12
|
+
}
|
|
13
13
|
|
|
14
14
|
const framework = {
|
|
15
15
|
name: "Cause & Effect",
|
|
16
16
|
signal: <T extends {}>(initialValue: T) => {
|
|
17
|
-
const s = state<T>(initialValue)
|
|
17
|
+
const s = state<T>(initialValue)
|
|
18
18
|
return {
|
|
19
19
|
write: (v: T) => s.set(v),
|
|
20
20
|
read: () => s.get(),
|
|
21
|
-
}
|
|
21
|
+
}
|
|
22
22
|
},
|
|
23
23
|
computed: <T extends {}>(fn: () => T) => {
|
|
24
|
-
const c = computed(fn)
|
|
24
|
+
const c = computed(fn)
|
|
25
25
|
return {
|
|
26
26
|
read: () => c.get(),
|
|
27
|
-
}
|
|
27
|
+
}
|
|
28
28
|
},
|
|
29
|
-
effect: (fn: () => void) => effect(
|
|
30
|
-
ok: fn,
|
|
31
|
-
err: () => {}
|
|
32
|
-
}),
|
|
29
|
+
effect: (fn: () => void) => effect(fn),
|
|
33
30
|
withBatch: (fn: () => void) => batch(fn),
|
|
34
31
|
withBuild: (fn: () => void) => fn(),
|
|
35
|
-
}
|
|
36
|
-
const testPullCounts = true
|
|
32
|
+
}
|
|
33
|
+
const testPullCounts = true
|
|
37
34
|
|
|
38
35
|
function makeConfig() {
|
|
39
36
|
return {
|
|
@@ -44,7 +41,7 @@ function makeConfig() {
|
|
|
44
41
|
readFraction: 1,
|
|
45
42
|
expected: {},
|
|
46
43
|
iterations: 1,
|
|
47
|
-
}
|
|
44
|
+
}
|
|
48
45
|
}
|
|
49
46
|
|
|
50
47
|
/* === Test functions === */
|
|
@@ -53,450 +50,449 @@ function makeConfig() {
|
|
|
53
50
|
* wrapper works and can run performance tests.
|
|
54
51
|
*/
|
|
55
52
|
describe('Basic test', function () {
|
|
56
|
-
const name = framework.name
|
|
57
|
-
|
|
53
|
+
const name = framework.name
|
|
58
54
|
test(`${name} | simple dependency executes`, () => {
|
|
59
55
|
framework.withBuild(() => {
|
|
60
|
-
const s = framework.signal(2)
|
|
61
|
-
const c = framework.computed(() => s.read() * 2)
|
|
56
|
+
const s = framework.signal(2)
|
|
57
|
+
const c = framework.computed(() => s.read() * 2)
|
|
62
58
|
|
|
63
|
-
expect(c.read()).toEqual(4)
|
|
64
|
-
})
|
|
65
|
-
})
|
|
59
|
+
expect(c.read()).toEqual(4)
|
|
60
|
+
})
|
|
61
|
+
})
|
|
66
62
|
|
|
67
63
|
test(`${name} | simple write`, () => {
|
|
68
64
|
framework.withBuild(() => {
|
|
69
|
-
const s = framework.signal(2)
|
|
70
|
-
const c = framework.computed(() => s.read() * 2)
|
|
71
|
-
expect(s.read()).toEqual(2)
|
|
72
|
-
expect(c.read()).toEqual(4)
|
|
65
|
+
const s = framework.signal(2)
|
|
66
|
+
const c = framework.computed(() => s.read() * 2)
|
|
67
|
+
expect(s.read()).toEqual(2)
|
|
68
|
+
expect(c.read()).toEqual(4)
|
|
73
69
|
|
|
74
|
-
s.write(3)
|
|
75
|
-
expect(s.read()).toEqual(3)
|
|
76
|
-
expect(c.read()).toEqual(6)
|
|
77
|
-
})
|
|
78
|
-
})
|
|
70
|
+
s.write(3)
|
|
71
|
+
expect(s.read()).toEqual(3)
|
|
72
|
+
expect(c.read()).toEqual(6)
|
|
73
|
+
})
|
|
74
|
+
})
|
|
79
75
|
|
|
80
76
|
test(`${name} | static graph`, () => {
|
|
81
|
-
const config = makeConfig()
|
|
82
|
-
const counter = new Counter()
|
|
77
|
+
const config = makeConfig()
|
|
78
|
+
const counter = new Counter()
|
|
83
79
|
// @ts-expect-error
|
|
84
|
-
const graph = makeGraph(framework, config, counter)
|
|
80
|
+
const graph = makeGraph(framework, config, counter)
|
|
85
81
|
// @ts-expect-error
|
|
86
|
-
const sum = runGraph(graph, 2, 1, framework)
|
|
87
|
-
expect(sum).toEqual(16)
|
|
88
|
-
|
|
89
|
-
expect(counter.count).toEqual(11)
|
|
82
|
+
const sum = runGraph(graph, 2, 1, framework)
|
|
83
|
+
expect(sum).toEqual(16)
|
|
84
|
+
if (testPullCounts) {
|
|
85
|
+
expect(counter.count).toEqual(11)
|
|
90
86
|
} else {
|
|
91
|
-
expect(counter.count).toBeGreaterThanOrEqual(11)
|
|
92
|
-
}
|
|
93
|
-
})
|
|
87
|
+
expect(counter.count).toBeGreaterThanOrEqual(11)
|
|
88
|
+
}
|
|
89
|
+
})
|
|
94
90
|
|
|
95
91
|
test(`${name} | static graph, read 2/3 of leaves`, () => {
|
|
96
92
|
framework.withBuild(() => {
|
|
97
|
-
const config = makeConfig()
|
|
98
|
-
config.readFraction = 2 / 3
|
|
99
|
-
config.iterations = 10
|
|
100
|
-
const counter = new Counter()
|
|
93
|
+
const config = makeConfig()
|
|
94
|
+
config.readFraction = 2 / 3
|
|
95
|
+
config.iterations = 10
|
|
96
|
+
const counter = new Counter()
|
|
101
97
|
// @ts-expect-error
|
|
102
|
-
const graph = makeGraph(framework, config, counter)
|
|
98
|
+
const graph = makeGraph(framework, config, counter)
|
|
103
99
|
// @ts-expect-error
|
|
104
|
-
const sum = runGraph(graph, 10, 2 / 3, framework)
|
|
100
|
+
const sum = runGraph(graph, 10, 2 / 3, framework)
|
|
105
101
|
|
|
106
|
-
expect(sum).toEqual(71)
|
|
107
|
-
|
|
108
|
-
expect(counter.count).toEqual(41)
|
|
102
|
+
expect(sum).toEqual(71)
|
|
103
|
+
if (testPullCounts) {
|
|
104
|
+
expect(counter.count).toEqual(41)
|
|
109
105
|
} else {
|
|
110
|
-
expect(counter.count).toBeGreaterThanOrEqual(41)
|
|
111
|
-
}
|
|
112
|
-
})
|
|
113
|
-
})
|
|
106
|
+
expect(counter.count).toBeGreaterThanOrEqual(41)
|
|
107
|
+
}
|
|
108
|
+
})
|
|
109
|
+
})
|
|
114
110
|
|
|
115
111
|
test(`${name} | dynamic graph`, () => {
|
|
116
112
|
framework.withBuild(() => {
|
|
117
|
-
const config = makeConfig()
|
|
118
|
-
config.staticFraction = 0.5
|
|
119
|
-
config.width = 4
|
|
120
|
-
config.totalLayers = 2
|
|
121
|
-
const counter = new Counter()
|
|
113
|
+
const config = makeConfig()
|
|
114
|
+
config.staticFraction = 0.5
|
|
115
|
+
config.width = 4
|
|
116
|
+
config.totalLayers = 2
|
|
117
|
+
const counter = new Counter()
|
|
122
118
|
// @ts-expect-error
|
|
123
|
-
const graph = makeGraph(framework, config, counter)
|
|
119
|
+
const graph = makeGraph(framework, config, counter)
|
|
124
120
|
// @ts-expect-error
|
|
125
|
-
const sum = runGraph(graph, 10, 1, framework)
|
|
121
|
+
const sum = runGraph(graph, 10, 1, framework)
|
|
126
122
|
|
|
127
|
-
expect(sum).toEqual(72)
|
|
128
|
-
|
|
129
|
-
expect(counter.count).toEqual(22)
|
|
123
|
+
expect(sum).toEqual(72)
|
|
124
|
+
if (testPullCounts) {
|
|
125
|
+
expect(counter.count).toEqual(22)
|
|
130
126
|
} else {
|
|
131
|
-
expect(counter.count).toBeGreaterThanOrEqual(22)
|
|
132
|
-
}
|
|
133
|
-
})
|
|
134
|
-
})
|
|
127
|
+
expect(counter.count).toBeGreaterThanOrEqual(22)
|
|
128
|
+
}
|
|
129
|
+
})
|
|
130
|
+
})
|
|
135
131
|
|
|
136
132
|
test(`${name} | withBuild`, () => {
|
|
137
133
|
const r = framework.withBuild(() => {
|
|
138
|
-
const s = framework.signal(2)
|
|
139
|
-
const c = framework.computed(() => s.read() * 2)
|
|
134
|
+
const s = framework.signal(2)
|
|
135
|
+
const c = framework.computed(() => s.read() * 2)
|
|
140
136
|
|
|
141
|
-
expect(c.read()).toEqual(4)
|
|
142
|
-
return c.read()
|
|
143
|
-
})
|
|
137
|
+
expect(c.read()).toEqual(4)
|
|
138
|
+
return c.read()
|
|
139
|
+
})
|
|
144
140
|
|
|
145
141
|
// @ts-expect-error
|
|
146
|
-
expect(r).toEqual(4)
|
|
147
|
-
})
|
|
142
|
+
expect(r).toEqual(4)
|
|
143
|
+
})
|
|
148
144
|
|
|
149
145
|
test(`${name} | effect`, () => {
|
|
150
|
-
const spy = (_v) => {}
|
|
151
|
-
const spyMock = mock(spy)
|
|
146
|
+
const spy = (_v) => {}
|
|
147
|
+
const spyMock = mock(spy)
|
|
152
148
|
|
|
153
|
-
const s = framework.signal(2)
|
|
154
|
-
let c: any
|
|
149
|
+
const s = framework.signal(2)
|
|
150
|
+
let c: any
|
|
155
151
|
|
|
156
152
|
framework.withBuild(() => {
|
|
157
|
-
c = framework.computed(() => s.read() * 2)
|
|
153
|
+
c = framework.computed(() => s.read() * 2)
|
|
158
154
|
|
|
159
155
|
framework.effect(() => {
|
|
160
|
-
spyMock(c.read())
|
|
161
|
-
})
|
|
162
|
-
})
|
|
163
|
-
expect(spyMock.mock.calls.length).toBe(1)
|
|
156
|
+
spyMock(c.read())
|
|
157
|
+
})
|
|
158
|
+
})
|
|
159
|
+
expect(spyMock.mock.calls.length).toBe(1)
|
|
164
160
|
|
|
165
161
|
framework.withBatch(() => {
|
|
166
|
-
s.write(3)
|
|
167
|
-
})
|
|
168
|
-
expect(s.read()).toEqual(3)
|
|
169
|
-
expect(c.read()).toEqual(6)
|
|
170
|
-
expect(spyMock.mock.calls.length).toBe(2)
|
|
171
|
-
})
|
|
172
|
-
})
|
|
162
|
+
s.write(3)
|
|
163
|
+
})
|
|
164
|
+
expect(s.read()).toEqual(3)
|
|
165
|
+
expect(c.read()).toEqual(6)
|
|
166
|
+
expect(spyMock.mock.calls.length).toBe(2)
|
|
167
|
+
})
|
|
168
|
+
})
|
|
173
169
|
|
|
174
170
|
describe('Kairo tests', function () {
|
|
175
|
-
const name = framework.name
|
|
171
|
+
const name = framework.name
|
|
176
172
|
|
|
177
173
|
test(`${name} | avoidable propagation`, () => {
|
|
178
|
-
const head = framework.signal(0)
|
|
179
|
-
const computed1 = framework.computed(() => head.read())
|
|
180
|
-
const computed2 = framework.computed(() => (computed1.read(), 0))
|
|
174
|
+
const head = framework.signal(0)
|
|
175
|
+
const computed1 = framework.computed(() => head.read())
|
|
176
|
+
const computed2 = framework.computed(() => (computed1.read(), 0))
|
|
181
177
|
const computed3 = framework.computed(() => (busy(), computed2.read()! + 1)); // heavy computation
|
|
182
|
-
const computed4 = framework.computed(() => computed3.read()! + 2)
|
|
183
|
-
const computed5 = framework.computed(() => computed4.read()! + 3)
|
|
178
|
+
const computed4 = framework.computed(() => computed3.read()! + 2)
|
|
179
|
+
const computed5 = framework.computed(() => computed4.read()! + 3)
|
|
184
180
|
framework.effect(() => {
|
|
185
|
-
computed5.read()
|
|
181
|
+
computed5.read()
|
|
186
182
|
busy(); // heavy side effect
|
|
187
|
-
})
|
|
183
|
+
})
|
|
188
184
|
|
|
189
185
|
return () => {
|
|
190
186
|
framework.withBatch(() => {
|
|
191
|
-
head.write(1)
|
|
192
|
-
})
|
|
193
|
-
expect(computed5.read()).toBe(6)
|
|
187
|
+
head.write(1)
|
|
188
|
+
})
|
|
189
|
+
expect(computed5.read()).toBe(6)
|
|
194
190
|
for (let i = 0; i < 10; i++) {
|
|
195
191
|
framework.withBatch(() => {
|
|
196
|
-
head.write(i)
|
|
197
|
-
})
|
|
198
|
-
expect(computed5.read()).toBe(6)
|
|
192
|
+
head.write(i)
|
|
193
|
+
})
|
|
194
|
+
expect(computed5.read()).toBe(6)
|
|
199
195
|
}
|
|
200
|
-
}
|
|
201
|
-
})
|
|
196
|
+
}
|
|
197
|
+
})
|
|
202
198
|
|
|
203
199
|
test(`${name} | broad propagation`, () => {
|
|
204
|
-
const head = framework.signal(0)
|
|
205
|
-
let last = head as { read: () => number }
|
|
206
|
-
const callCounter = new Counter()
|
|
200
|
+
const head = framework.signal(0)
|
|
201
|
+
let last = head as { read: () => number }
|
|
202
|
+
const callCounter = new Counter()
|
|
207
203
|
for (let i = 0; i < 50; i++) {
|
|
208
204
|
let current = framework.computed(() => {
|
|
209
|
-
return head.read()! + i
|
|
210
|
-
})
|
|
205
|
+
return head.read()! + i
|
|
206
|
+
})
|
|
211
207
|
let current2 = framework.computed(() => {
|
|
212
|
-
return current.read()! + 1
|
|
213
|
-
})
|
|
208
|
+
return current.read()! + 1
|
|
209
|
+
})
|
|
214
210
|
framework.effect(() => {
|
|
215
|
-
current2.read()
|
|
216
|
-
callCounter.count
|
|
217
|
-
})
|
|
218
|
-
last = current2
|
|
211
|
+
current2.read()
|
|
212
|
+
callCounter.count++
|
|
213
|
+
})
|
|
214
|
+
last = current2
|
|
219
215
|
}
|
|
220
216
|
|
|
221
217
|
return () => {
|
|
222
218
|
framework.withBatch(() => {
|
|
223
|
-
head.write(1)
|
|
224
|
-
})
|
|
225
|
-
const atleast = 50 * 50
|
|
226
|
-
callCounter.count = 0
|
|
219
|
+
head.write(1)
|
|
220
|
+
})
|
|
221
|
+
const atleast = 50 * 50
|
|
222
|
+
callCounter.count = 0
|
|
227
223
|
for (let i = 0; i < 50; i++) {
|
|
228
224
|
framework.withBatch(() => {
|
|
229
|
-
head.write(i)
|
|
230
|
-
})
|
|
231
|
-
expect(last.read()).toBe(i + 50)
|
|
225
|
+
head.write(i)
|
|
226
|
+
})
|
|
227
|
+
expect(last.read()).toBe(i + 50)
|
|
232
228
|
}
|
|
233
|
-
expect(callCounter.count).toBe(atleast)
|
|
234
|
-
}
|
|
235
|
-
})
|
|
229
|
+
expect(callCounter.count).toBe(atleast)
|
|
230
|
+
}
|
|
231
|
+
})
|
|
236
232
|
|
|
237
233
|
test(`${name} | deep propagation`, () => {
|
|
238
|
-
let len = 50
|
|
239
|
-
const head = framework.signal(0)
|
|
240
|
-
let current = head as { read: () => number }
|
|
234
|
+
let len = 50
|
|
235
|
+
const head = framework.signal(0)
|
|
236
|
+
let current = head as { read: () => number }
|
|
241
237
|
for (let i = 0; i < len; i++) {
|
|
242
|
-
let c = current
|
|
238
|
+
let c = current
|
|
243
239
|
current = framework.computed(() => {
|
|
244
|
-
return c.read() + 1
|
|
245
|
-
})
|
|
240
|
+
return c.read() + 1
|
|
241
|
+
})
|
|
246
242
|
}
|
|
247
|
-
let callCounter = new Counter()
|
|
243
|
+
let callCounter = new Counter()
|
|
248
244
|
framework.effect(() => {
|
|
249
|
-
current.read()
|
|
250
|
-
callCounter.count
|
|
251
|
-
})
|
|
252
|
-
const iter = 50
|
|
245
|
+
current.read()
|
|
246
|
+
callCounter.count++
|
|
247
|
+
})
|
|
248
|
+
const iter = 50
|
|
253
249
|
|
|
254
250
|
return () => {
|
|
255
251
|
framework.withBatch(() => {
|
|
256
|
-
head.write(1)
|
|
257
|
-
})
|
|
258
|
-
const atleast = iter
|
|
259
|
-
callCounter.count = 0
|
|
252
|
+
head.write(1)
|
|
253
|
+
})
|
|
254
|
+
const atleast = iter
|
|
255
|
+
callCounter.count = 0
|
|
260
256
|
for (let i = 0; i < iter; i++) {
|
|
261
257
|
framework.withBatch(() => {
|
|
262
|
-
head.write(i)
|
|
263
|
-
})
|
|
264
|
-
expect(current.read()).toBe(len + i)
|
|
258
|
+
head.write(i)
|
|
259
|
+
})
|
|
260
|
+
expect(current.read()).toBe(len + i)
|
|
265
261
|
}
|
|
266
|
-
expect(callCounter.count).toBe(atleast)
|
|
267
|
-
}
|
|
268
|
-
})
|
|
262
|
+
expect(callCounter.count).toBe(atleast)
|
|
263
|
+
}
|
|
264
|
+
})
|
|
269
265
|
|
|
270
266
|
test(`${name} | diamond`, function () {
|
|
271
|
-
let width = 5
|
|
272
|
-
const head = framework.signal(0)
|
|
273
|
-
let current: { read(): number }[] = []
|
|
267
|
+
let width = 5
|
|
268
|
+
const head = framework.signal(0)
|
|
269
|
+
let current: { read(): number }[] = []
|
|
274
270
|
for (let i = 0; i < width; i++) {
|
|
275
271
|
current.push(
|
|
276
272
|
framework.computed(() => head.read() + 1)
|
|
277
|
-
)
|
|
273
|
+
)
|
|
278
274
|
}
|
|
279
275
|
let sum = framework.computed(() => {
|
|
280
|
-
return current.map(x => x.read()).reduce((a, b) => a + b, 0)
|
|
281
|
-
})
|
|
282
|
-
let callCounter = new Counter()
|
|
276
|
+
return current.map(x => x.read()).reduce((a, b) => a + b, 0)
|
|
277
|
+
})
|
|
278
|
+
let callCounter = new Counter()
|
|
283
279
|
framework.effect(() => {
|
|
284
|
-
sum.read()
|
|
285
|
-
callCounter.count
|
|
286
|
-
})
|
|
280
|
+
sum.read()
|
|
281
|
+
callCounter.count++
|
|
282
|
+
})
|
|
287
283
|
|
|
288
284
|
return () => {
|
|
289
285
|
framework.withBatch(() => {
|
|
290
|
-
head.write(1)
|
|
291
|
-
})
|
|
292
|
-
expect(sum.read()).toBe(2 * width)
|
|
293
|
-
const atleast = 500
|
|
294
|
-
callCounter.count = 0
|
|
286
|
+
head.write(1)
|
|
287
|
+
})
|
|
288
|
+
expect(sum.read()).toBe(2 * width)
|
|
289
|
+
const atleast = 500
|
|
290
|
+
callCounter.count = 0
|
|
295
291
|
for (let i = 0; i < 500; i++) {
|
|
296
292
|
framework.withBatch(() => {
|
|
297
|
-
head.write(i)
|
|
298
|
-
})
|
|
299
|
-
expect(sum.read()).toBe((i + 1) * width)
|
|
293
|
+
head.write(i)
|
|
294
|
+
})
|
|
295
|
+
expect(sum.read()).toBe((i + 1) * width)
|
|
300
296
|
}
|
|
301
|
-
expect(callCounter.count).toBe(atleast)
|
|
302
|
-
}
|
|
303
|
-
})
|
|
297
|
+
expect(callCounter.count).toBe(atleast)
|
|
298
|
+
}
|
|
299
|
+
})
|
|
304
300
|
|
|
305
301
|
test(`${name} | mux`, function () {
|
|
306
|
-
let heads = new Array(100).fill(null).map((_) => framework.signal(0))
|
|
302
|
+
let heads = new Array(100).fill(null).map((_) => framework.signal(0))
|
|
307
303
|
const mux = framework.computed(() => {
|
|
308
|
-
return Object.fromEntries(heads.map((h) => h.read()).entries())
|
|
309
|
-
})
|
|
304
|
+
return Object.fromEntries(heads.map((h) => h.read()).entries())
|
|
305
|
+
})
|
|
310
306
|
const splited = heads
|
|
311
307
|
.map((_, index) => framework.computed(() => mux.read()[index]))
|
|
312
|
-
.map((x) => framework.computed(() => x.read() + 1))
|
|
308
|
+
.map((x) => framework.computed(() => x.read() + 1))
|
|
313
309
|
|
|
314
310
|
splited.forEach((x) => {
|
|
315
|
-
framework.effect(() => x.read())
|
|
316
|
-
})
|
|
311
|
+
framework.effect(() => x.read())
|
|
312
|
+
})
|
|
317
313
|
|
|
318
314
|
return () => {
|
|
319
315
|
for (let i = 0; i < 10; i++) {
|
|
320
316
|
framework.withBatch(() => {
|
|
321
|
-
heads[i].write(i)
|
|
322
|
-
})
|
|
323
|
-
expect(splited[i].read()).toBe(i + 1)
|
|
317
|
+
heads[i].write(i)
|
|
318
|
+
})
|
|
319
|
+
expect(splited[i].read()).toBe(i + 1)
|
|
324
320
|
}
|
|
325
321
|
for (let i = 0; i < 10; i++) {
|
|
326
322
|
framework.withBatch(() => {
|
|
327
|
-
heads[i].write(i * 2)
|
|
328
|
-
})
|
|
329
|
-
expect(splited[i].read()).toBe(i * 2 + 1)
|
|
323
|
+
heads[i].write(i * 2)
|
|
324
|
+
})
|
|
325
|
+
expect(splited[i].read()).toBe(i * 2 + 1)
|
|
330
326
|
}
|
|
331
|
-
}
|
|
332
|
-
})
|
|
327
|
+
}
|
|
328
|
+
})
|
|
333
329
|
|
|
334
330
|
test(`${name} | repeated observers`, function () {
|
|
335
|
-
const size = 30
|
|
336
|
-
let head = framework.signal(0)
|
|
331
|
+
const size = 30
|
|
332
|
+
let head = framework.signal(0)
|
|
337
333
|
let current = framework.computed(() => {
|
|
338
|
-
let result = 0
|
|
334
|
+
let result = 0
|
|
339
335
|
for (let i = 0; i < size; i++) {
|
|
340
|
-
result += head.read()
|
|
336
|
+
result += head.read()
|
|
341
337
|
}
|
|
342
|
-
return result
|
|
343
|
-
})
|
|
344
|
-
let callCounter = new Counter()
|
|
338
|
+
return result
|
|
339
|
+
})
|
|
340
|
+
let callCounter = new Counter()
|
|
345
341
|
framework.effect(() => {
|
|
346
|
-
current.read()
|
|
347
|
-
callCounter.count
|
|
348
|
-
})
|
|
342
|
+
current.read()
|
|
343
|
+
callCounter.count++
|
|
344
|
+
})
|
|
349
345
|
|
|
350
346
|
return () => {
|
|
351
347
|
framework.withBatch(() => {
|
|
352
|
-
head.write(1)
|
|
353
|
-
})
|
|
354
|
-
expect(current.read()).toBe(size)
|
|
355
|
-
const atleast = 100
|
|
356
|
-
callCounter.count = 0
|
|
348
|
+
head.write(1)
|
|
349
|
+
})
|
|
350
|
+
expect(current.read()).toBe(size)
|
|
351
|
+
const atleast = 100
|
|
352
|
+
callCounter.count = 0
|
|
357
353
|
for (let i = 0; i < 100; i++) {
|
|
358
354
|
framework.withBatch(() => {
|
|
359
|
-
head.write(i)
|
|
360
|
-
})
|
|
361
|
-
expect(current.read()).toBe(i * size)
|
|
355
|
+
head.write(i)
|
|
356
|
+
})
|
|
357
|
+
expect(current.read()).toBe(i * size)
|
|
362
358
|
}
|
|
363
|
-
expect(callCounter.count).toBe(atleast)
|
|
364
|
-
}
|
|
365
|
-
})
|
|
359
|
+
expect(callCounter.count).toBe(atleast)
|
|
360
|
+
}
|
|
361
|
+
})
|
|
366
362
|
|
|
367
363
|
test(`${name} | triangle`, function () {
|
|
368
|
-
let width = 10
|
|
369
|
-
let head = framework.signal(0)
|
|
370
|
-
let current = head as { read: () => number }
|
|
371
|
-
let list: { read: () => number }[] = []
|
|
364
|
+
let width = 10
|
|
365
|
+
let head = framework.signal(0)
|
|
366
|
+
let current = head as { read: () => number }
|
|
367
|
+
let list: { read: () => number }[] = []
|
|
372
368
|
for (let i = 0; i < width; i++) {
|
|
373
|
-
let c = current
|
|
374
|
-
list.push(current)
|
|
369
|
+
let c = current
|
|
370
|
+
list.push(current)
|
|
375
371
|
current = framework.computed(() => {
|
|
376
|
-
return c.read() + 1
|
|
377
|
-
})
|
|
372
|
+
return c.read() + 1
|
|
373
|
+
})
|
|
378
374
|
}
|
|
379
375
|
let sum = framework.computed(() => {
|
|
380
|
-
return list.map((x) => x.read()).reduce((a, b) => a + b, 0)
|
|
381
|
-
})
|
|
382
|
-
let callCounter = new Counter()
|
|
376
|
+
return list.map((x) => x.read()).reduce((a, b) => a + b, 0)
|
|
377
|
+
})
|
|
378
|
+
let callCounter = new Counter()
|
|
383
379
|
framework.effect(() => {
|
|
384
|
-
sum.read()
|
|
385
|
-
callCounter.count
|
|
386
|
-
})
|
|
380
|
+
sum.read()
|
|
381
|
+
callCounter.count++
|
|
382
|
+
})
|
|
387
383
|
|
|
388
384
|
return () => {
|
|
389
385
|
const count = (number: Number) => {
|
|
390
386
|
return new Array(number)
|
|
391
387
|
.fill(0)
|
|
392
388
|
.map((_, i) => i + 1)
|
|
393
|
-
.reduce((x, y) => x + y, 0)
|
|
389
|
+
.reduce((x, y) => x + y, 0)
|
|
394
390
|
}
|
|
395
|
-
const constant = count(width)
|
|
391
|
+
const constant = count(width)
|
|
396
392
|
framework.withBatch(() => {
|
|
397
|
-
head.write(1)
|
|
398
|
-
})
|
|
399
|
-
expect(sum.read()).toBe(constant)
|
|
400
|
-
const atleast = 100
|
|
401
|
-
callCounter.count = 0
|
|
393
|
+
head.write(1)
|
|
394
|
+
})
|
|
395
|
+
expect(sum.read()).toBe(constant)
|
|
396
|
+
const atleast = 100
|
|
397
|
+
callCounter.count = 0
|
|
402
398
|
for (let i = 0; i < 100; i++) {
|
|
403
399
|
framework.withBatch(() => {
|
|
404
|
-
head.write(i)
|
|
405
|
-
})
|
|
406
|
-
expect(sum.read()).toBe(constant - width + i * width)
|
|
400
|
+
head.write(i)
|
|
401
|
+
})
|
|
402
|
+
expect(sum.read()).toBe(constant - width + i * width)
|
|
407
403
|
}
|
|
408
|
-
expect(callCounter.count).toBe(atleast)
|
|
409
|
-
}
|
|
410
|
-
})
|
|
404
|
+
expect(callCounter.count).toBe(atleast)
|
|
405
|
+
}
|
|
406
|
+
})
|
|
411
407
|
|
|
412
408
|
test(`${name} | unstable`, function () {
|
|
413
|
-
let head = framework.signal(0)
|
|
414
|
-
const double = framework.computed(() => head.read() * 2)
|
|
415
|
-
const inverse = framework.computed(() => -head.read())
|
|
409
|
+
let head = framework.signal(0)
|
|
410
|
+
const double = framework.computed(() => head.read() * 2)
|
|
411
|
+
const inverse = framework.computed(() => -head.read())
|
|
416
412
|
let current = framework.computed(() => {
|
|
417
|
-
let result = 0
|
|
413
|
+
let result = 0
|
|
418
414
|
for (let i = 0; i < 20; i++) {
|
|
419
|
-
result += head.read() % 2 ? double.read() : inverse.read()
|
|
415
|
+
result += head.read() % 2 ? double.read() : inverse.read()
|
|
420
416
|
}
|
|
421
|
-
return result
|
|
422
|
-
})
|
|
423
|
-
let callCounter = new Counter()
|
|
417
|
+
return result
|
|
418
|
+
})
|
|
419
|
+
let callCounter = new Counter()
|
|
424
420
|
framework.effect(() => {
|
|
425
|
-
current.read()
|
|
426
|
-
callCounter.count
|
|
427
|
-
})
|
|
421
|
+
current.read()
|
|
422
|
+
callCounter.count++
|
|
423
|
+
})
|
|
428
424
|
|
|
429
425
|
return () => {
|
|
430
426
|
framework.withBatch(() => {
|
|
431
|
-
head.write(1)
|
|
432
|
-
})
|
|
433
|
-
expect(current.read()).toBe(40)
|
|
434
|
-
const atleast = 100
|
|
435
|
-
callCounter.count = 0
|
|
427
|
+
head.write(1)
|
|
428
|
+
})
|
|
429
|
+
expect(current.read()).toBe(40)
|
|
430
|
+
const atleast = 100
|
|
431
|
+
callCounter.count = 0
|
|
436
432
|
for (let i = 0; i < 100; i++) {
|
|
437
433
|
framework.withBatch(() => {
|
|
438
|
-
head.write(i)
|
|
439
|
-
})
|
|
440
|
-
expect(current.read()).toBe(i % 2 ? i * 2 * 10 : i * -10)
|
|
434
|
+
head.write(i)
|
|
435
|
+
})
|
|
436
|
+
expect(current.read()).toBe(i % 2 ? i * 2 * 10 : i * -10)
|
|
441
437
|
}
|
|
442
|
-
expect(callCounter.count).toBe(atleast)
|
|
443
|
-
}
|
|
444
|
-
})
|
|
445
|
-
})
|
|
438
|
+
expect(callCounter.count).toBe(atleast)
|
|
439
|
+
}
|
|
440
|
+
})
|
|
441
|
+
})
|
|
446
442
|
|
|
447
443
|
/* describe('$mol_wire tests', function () {
|
|
448
|
-
const name = framework.name
|
|
444
|
+
const name = framework.name
|
|
449
445
|
|
|
450
446
|
test(`${name} | $mol_wire benchmark`, function() {
|
|
451
447
|
const fib = (n: number) => {
|
|
452
|
-
if (n < 2) return 1
|
|
453
|
-
return fib(n - 1) + fib(n - 2)
|
|
454
|
-
}
|
|
448
|
+
if (n < 2) return 1
|
|
449
|
+
return fib(n - 1) + fib(n - 2)
|
|
450
|
+
}
|
|
455
451
|
const hard = (n: number, log: string) => {
|
|
456
|
-
return n + fib(16)
|
|
457
|
-
}
|
|
458
|
-
const numbers = Array.from({ length: 5 }, (_, i) => i)
|
|
459
|
-
let res: (() => any)[] = []
|
|
452
|
+
return n + fib(16)
|
|
453
|
+
}
|
|
454
|
+
const numbers = Array.from({ length: 5 }, (_, i) => i)
|
|
455
|
+
let res: (() => any)[] = []
|
|
460
456
|
const iter = framework.withBuild(() => {
|
|
461
|
-
const A = framework.signal(0)
|
|
462
|
-
const B = framework.signal(0)
|
|
463
|
-
const C = framework.computed(() => (A.read() % 2) + (B.read() % 2))
|
|
457
|
+
const A = framework.signal(0)
|
|
458
|
+
const B = framework.signal(0)
|
|
459
|
+
const C = framework.computed(() => (A.read() % 2) + (B.read() % 2))
|
|
464
460
|
const D = framework.computed(() =>
|
|
465
461
|
numbers.map((i) => ({ x: i + (A.read() % 2) - (B.read() % 2) }))
|
|
466
|
-
)
|
|
462
|
+
)
|
|
467
463
|
const E = framework.computed(() =>
|
|
468
464
|
hard(C.read() + A.read() + D.read()[0].x, "E")
|
|
469
|
-
)
|
|
470
|
-
const F = framework.computed(() => hard(D.read()[2].x || B.read(), "F"))
|
|
465
|
+
)
|
|
466
|
+
const F = framework.computed(() => hard(D.read()[2].x || B.read(), "F"))
|
|
471
467
|
const G = framework.computed(
|
|
472
468
|
() => C.read() + (C.read() || E.read() % 2) + D.read()[4].x + F.read()
|
|
473
|
-
)
|
|
474
|
-
framework.effect(() => res.push(hard(G.read(), "H")))
|
|
469
|
+
)
|
|
470
|
+
framework.effect(() => res.push(hard(G.read(), "H")))
|
|
475
471
|
framework.effect(() => res.push(G.read())); // I
|
|
476
|
-
framework.effect(() => res.push(hard(F.read(), "J")))
|
|
477
|
-
framework.effect(() => res[0] = hard(G.read(), "H"))
|
|
472
|
+
framework.effect(() => res.push(hard(F.read(), "J")))
|
|
473
|
+
framework.effect(() => res[0] = hard(G.read(), "H"))
|
|
478
474
|
framework.effect(() => res[1] = G.read()); // I
|
|
479
|
-
framework.effect(() => res[2] = hard(F.read(), "J"))
|
|
475
|
+
framework.effect(() => res[2] = hard(F.read(), "J"))
|
|
480
476
|
|
|
481
477
|
return (i: number) => {
|
|
482
|
-
res.length = 0
|
|
478
|
+
res.length = 0
|
|
483
479
|
framework.withBatch(() => {
|
|
484
|
-
B.write(1)
|
|
485
|
-
A.write(1 + i * 2)
|
|
486
|
-
})
|
|
480
|
+
B.write(1)
|
|
481
|
+
A.write(1 + i * 2)
|
|
482
|
+
})
|
|
487
483
|
framework.withBatch(() => {
|
|
488
|
-
A.write(2 + i * 2)
|
|
489
|
-
B.write(2)
|
|
490
|
-
})
|
|
491
|
-
}
|
|
492
|
-
})
|
|
484
|
+
A.write(2 + i * 2)
|
|
485
|
+
B.write(2)
|
|
486
|
+
})
|
|
487
|
+
}
|
|
488
|
+
})
|
|
493
489
|
|
|
494
|
-
expect(res.toString()).toBe([3201, 1604, 3196].toString())
|
|
495
|
-
})
|
|
496
|
-
})
|
|
490
|
+
expect(res.toString()).toBe([3201, 1604, 3196].toString())
|
|
491
|
+
})
|
|
492
|
+
})
|
|
497
493
|
|
|
498
494
|
describe('CellX tests', function () {
|
|
499
|
-
const name = framework.name
|
|
495
|
+
const name = framework.name
|
|
500
496
|
|
|
501
497
|
test(`${name} | CellX benchmark`, function() {
|
|
502
498
|
const expected = {
|
|
@@ -512,8 +508,8 @@ describe('CellX tests', function () {
|
|
|
512
508
|
[2, 4, -1, -6],
|
|
513
509
|
[-2, 1, -4, -4],
|
|
514
510
|
]
|
|
515
|
-
}
|
|
516
|
-
const results = {}
|
|
511
|
+
}
|
|
512
|
+
const results = {}
|
|
517
513
|
|
|
518
514
|
const cellx = (framework, layers) => {
|
|
519
515
|
const start = {
|
|
@@ -521,62 +517,62 @@ describe('CellX tests', function () {
|
|
|
521
517
|
prop2: framework.signal(2),
|
|
522
518
|
prop3: framework.signal(3),
|
|
523
519
|
prop4: framework.signal(4),
|
|
524
|
-
}
|
|
525
|
-
let layer = start
|
|
520
|
+
}
|
|
521
|
+
let layer = start
|
|
526
522
|
|
|
527
523
|
for (let i = layers; i > 0; i--) {
|
|
528
|
-
const m = layer
|
|
524
|
+
const m = layer
|
|
529
525
|
const s = {
|
|
530
526
|
prop1: framework.computed(() => m.prop2.read()),
|
|
531
527
|
prop2: framework.computed(() => m.prop1.read() - m.prop3.read()),
|
|
532
528
|
prop3: framework.computed(() => m.prop2.read() + m.prop4.read()),
|
|
533
529
|
prop4: framework.computed(() => m.prop3.read()),
|
|
534
|
-
}
|
|
530
|
+
}
|
|
535
531
|
|
|
536
|
-
framework.effect(() => s.prop1.read())
|
|
537
|
-
framework.effect(() => s.prop2.read())
|
|
538
|
-
framework.effect(() => s.prop3.read())
|
|
539
|
-
framework.effect(() => s.prop4.read())
|
|
532
|
+
framework.effect(() => s.prop1.read())
|
|
533
|
+
framework.effect(() => s.prop2.read())
|
|
534
|
+
framework.effect(() => s.prop3.read())
|
|
535
|
+
framework.effect(() => s.prop4.read())
|
|
540
536
|
|
|
541
|
-
s.prop1.read()
|
|
542
|
-
s.prop2.read()
|
|
543
|
-
s.prop3.read()
|
|
544
|
-
s.prop4.read()
|
|
537
|
+
s.prop1.read()
|
|
538
|
+
s.prop2.read()
|
|
539
|
+
s.prop3.read()
|
|
540
|
+
s.prop4.read()
|
|
545
541
|
|
|
546
|
-
layer = s
|
|
542
|
+
layer = s
|
|
547
543
|
}
|
|
548
544
|
|
|
549
|
-
const end = layer
|
|
545
|
+
const end = layer
|
|
550
546
|
|
|
551
547
|
const before = [
|
|
552
548
|
end.prop1.read(),
|
|
553
549
|
end.prop2.read(),
|
|
554
550
|
end.prop3.read(),
|
|
555
551
|
end.prop4.read(),
|
|
556
|
-
]
|
|
552
|
+
]
|
|
557
553
|
|
|
558
554
|
framework.withBatch(() => {
|
|
559
|
-
start.prop1.write(4)
|
|
560
|
-
start.prop2.write(3)
|
|
561
|
-
start.prop3.write(2)
|
|
562
|
-
start.prop4.write(1)
|
|
563
|
-
})
|
|
555
|
+
start.prop1.write(4)
|
|
556
|
+
start.prop2.write(3)
|
|
557
|
+
start.prop3.write(2)
|
|
558
|
+
start.prop4.write(1)
|
|
559
|
+
})
|
|
564
560
|
|
|
565
561
|
const after = [
|
|
566
562
|
end.prop1.read(),
|
|
567
563
|
end.prop2.read(),
|
|
568
564
|
end.prop3.read(),
|
|
569
565
|
end.prop4.read(),
|
|
570
|
-
]
|
|
566
|
+
]
|
|
571
567
|
|
|
572
|
-
return [before, after]
|
|
573
|
-
}
|
|
568
|
+
return [before, after]
|
|
569
|
+
}
|
|
574
570
|
|
|
575
571
|
for (const layers in expected) {
|
|
576
|
-
const [before, after] = cellx(framework, layers)
|
|
577
|
-
const [expectedBefore, expectedAfter] = expected[layers]
|
|
578
|
-
expect(before.toString()).toBe(expectedBefore.toString())
|
|
579
|
-
expect(after.toString()).toBe(expectedAfter.toString())
|
|
572
|
+
const [before, after] = cellx(framework, layers)
|
|
573
|
+
const [expectedBefore, expectedAfter] = expected[layers]
|
|
574
|
+
expect(before.toString()).toBe(expectedBefore.toString())
|
|
575
|
+
expect(after.toString()).toBe(expectedAfter.toString())
|
|
580
576
|
}
|
|
581
|
-
})
|
|
582
|
-
})
|
|
577
|
+
})
|
|
578
|
+
}) */
|