Skip to content

Commit a991694

Browse files
committed
Add a single-value tracker, optimize tracked handle.
1 parent 3a99bb1 commit a991694

File tree

4 files changed

+184
-100
lines changed

4 files changed

+184
-100
lines changed

Diff for: src/entry/mithril.esm.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@ import m from "../core.js"
33
import {debouncer, throttler} from "../std/rate-limit.js"
44
import {link, route} from "../std/router.js"
55
import {match, p, query} from "../std/path-query.js"
6+
import {tracked, trackedList} from "../std/tracked.js"
67
import init from "../std/init.js"
78
import lazy from "../std/lazy.js"
8-
import tracked from "../std/tracked.js"
99
import withProgress from "../std/with-progress.js"
1010

1111
m.route = route
@@ -17,6 +17,7 @@ m.withProgress = withProgress
1717
m.lazy = lazy
1818
m.init = init
1919
m.tracked = tracked
20+
m.trackedList = trackedList
2021
m.throttler = throttler
2122
m.debouncer = debouncer
2223

Diff for: src/std/tracked.js

+80-36
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,15 @@ why that was removed in favor of this:
4343
want to clear some state and not other state. You might want to preserve some elements of a
4444
sibling's state. Embedding it in the renderer would force an opinion on you, and in order to
4545
work around it, you'd have to do something like this anyways.
46+
47+
As for the difference between `m.trackedList()` and `m.tracked()`, the first is for tracking lists
48+
(and is explained above), and `m.tracked()` is for single values (but uses `m.trackedList()`
49+
internally to avoid a ton of code duplication).
4650
*/
4751

48-
import {checkCallback} from "../util.js"
52+
import m from "../core.js"
53+
54+
import {checkCallback, noop} from "../util.js"
4955

5056
/**
5157
* @template K, V
@@ -55,6 +61,7 @@ import {checkCallback} from "../util.js"
5561
* @property {V} value
5662
* @property {AbortSignal} signal
5763
* @property {() => void} release
64+
* @property {() => void} remove
5865
*/
5966

6067
/**
@@ -70,47 +77,64 @@ import {checkCallback} from "../util.js"
7077
* @property {(key: K) => boolean} delete
7178
*/
7279

73-
/**
74-
* @template K, V
75-
* @param {Iterable<[K, V]>} [initial]
76-
* @param {() => void} redraw
77-
* @returns {Tracked<K, V>}
78-
*/
79-
var tracked = (redraw, initial) => {
80+
var trackedState = (redraw) => {
8081
checkCallback(redraw, false, "redraw")
81-
82-
/** @type {Map<K, TrackedHandle<K, V> & {_: AbortController}>} */ var state = new Map()
82+
/** @type {Map<K, AbortController & TrackedHandle<K, V>>} */
83+
var state = new Map()
84+
var removed = new WeakSet()
8385
/** @type {Set<TrackedHandle<K, V>>} */ var live = new Set()
8486

87+
/** @param {null | AbortController & TrackedHandle<K, V>} prev */
8588
var abort = (prev) => {
8689
try {
8790
if (prev) {
88-
if (prev._) prev._.abort()
89-
else live.delete(prev)
91+
if (removed.has(prev)) {
92+
live.delete(prev)
93+
} else {
94+
prev.abort()
95+
}
9096
}
9197
} catch (e) {
9298
console.error(e)
9399
}
94100
}
95101

96-
// Bit 1 forcibly releases the old handle, and bit 2 causes an update notification to be sent
97-
// (something that's unwanted during initialization).
102+
/** @param {K} k */
103+
var remove = (k, r) => {
104+
var prev = state.get(k)
105+
var result = state.delete(k)
106+
abort(prev)
107+
if (r) redraw()
108+
return result
109+
}
110+
111+
/**
112+
* @param {K} k
113+
* @param {V} v
114+
* @param {number} bits
115+
* Bit 1 forcibly releases the old handle, and bit 2 causes an update notification to be sent
116+
* (something that's unwanted during initialization).
117+
*/
98118
var setHandle = (k, v, bits) => {
99119
var prev = state.get(k)
100-
var ctrl = new AbortController()
101-
/** @type {TrackedHandle<K, V>} */
102-
var handle = {
103-
_: ctrl,
104-
key: k,
105-
value: v,
106-
signal: ctrl.signal,
107-
release() {
108-
if (state.get(handle.key) === handle) {
109-
handle._ = null
110-
} else if (live.delete(handle)) {
111-
redraw()
112-
}
113-
},
120+
// Note: it extending `AbortController` is an implementation detail. It exposing a `signal`
121+
// property is *not*.
122+
var handle = /** @type {AbortController & TrackedHandle<K, V>} */ (new AbortController())
123+
handle.key = k
124+
handle.value = v
125+
handle.release = (ev) => {
126+
if (ev) m.capture(ev)
127+
if (!handle) return
128+
if (state.get(handle.key) === handle) {
129+
removed.add(handle)
130+
handle = null
131+
} else if (live.delete(handle)) {
132+
redraw()
133+
}
134+
}
135+
handle.remove = (ev) => {
136+
if (ev) m.capture(ev)
137+
remove(handle.key, 0)
114138
}
115139
state.set(k, handle)
116140
live.add(handle)
@@ -121,6 +145,18 @@ var tracked = (redraw, initial) => {
121145
if (bits & 2) redraw()
122146
}
123147

148+
return {s: state, l: live, h: setHandle, r: remove}
149+
}
150+
151+
/**
152+
* @template K, V
153+
* @param {Iterable<[K, V]>} [initial]
154+
* @param {() => void} redraw
155+
* @returns {TrackedList<K, V>}
156+
*/
157+
var trackedList = (redraw, initial) => {
158+
var {s: state, l: live, h: setHandle, r: remove} = trackedState(redraw)
159+
124160
for (var [k, v] of initial || []) setHandle(k, v, 1)
125161

126162
return {
@@ -130,14 +166,22 @@ var tracked = (redraw, initial) => {
130166
get: (k) => (k = state.get(k)) && k.value,
131167
set: (k, v) => setHandle(k, v, 3),
132168
replace: (k, v) => setHandle(k, v, 2),
133-
delete(k) {
134-
var prev = state.get(k)
135-
var result = state.delete(k)
136-
abort(prev)
137-
redraw()
138-
return result
139-
},
169+
delete: (k) => remove(k, 1),
170+
forget: (k) => (k = state.get(k)) && k.release(),
171+
}
172+
}
173+
174+
var tracked = (redraw) => {
175+
var {l: live, h: setHandle, r: remove} = trackedState(redraw)
176+
var initial = noop
177+
var id = -1
178+
return (state) => {
179+
if (!Object.is(initial, initial = state)) {
180+
remove(id++, 0)
181+
setHandle(id, state, 1)
182+
}
183+
return [...live]
140184
}
141185
}
142186

143-
export {tracked as default}
187+
export {tracked, trackedList}

Diff for: test-utils/global.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ export function setupGlobals(env = {}) {
6464

6565
o.beforeEach(() => {
6666
initialize({...env})
67-
return env.initialize && env.initialize()
67+
return env.initialize && env.initialize(G)
6868
})
6969

7070
o.afterEach(() => {
@@ -87,7 +87,7 @@ export function setupGlobals(env = {}) {
8787
o(errors).deepEquals([])
8888
errors.length = 0
8989
o(mock.queueLength()).equals(0)
90-
return env.cleanup && env.cleanup()
90+
return env.cleanup && env.cleanup(G)
9191
})
9292

9393
return {

0 commit comments

Comments
 (0)