@msb235/blits 0.11.0 → 0.12.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/CHANGELOG.md +110 -0
- package/dist/arcs.d.ts +40 -0
- package/dist/arcs.js +241 -0
- package/dist/chain.d.ts +6 -1
- package/dist/chain.js +33 -13
- package/dist/channels.d.ts +2 -0
- package/dist/channels.js +12 -1
- package/dist/clone.js +16 -4
- package/dist/cue.js +11 -2
- package/dist/departed.d.ts +5 -1
- package/dist/departed.js +71 -27
- package/dist/easing.d.ts +12 -1
- package/dist/easing.js +7 -1
- package/dist/everyone.d.ts +14 -1
- package/dist/everyone.js +27 -2
- package/dist/fade.js +3 -3
- package/dist/fill.js +16 -9
- package/dist/fit.d.ts +8 -1
- package/dist/fit.js +7 -3
- package/dist/fold.js +69 -13
- package/dist/gather.js +38 -7
- package/dist/handle.d.ts +2 -0
- package/dist/handle.js +8 -0
- package/dist/held.d.ts +5 -2
- package/dist/held.js +6 -3
- package/dist/history.d.ts +19 -2
- package/dist/history.js +149 -31
- package/dist/hosts.js +27 -11
- package/dist/index.d.ts +4 -1
- package/dist/index.js +2 -0
- package/dist/lanes.d.ts +23 -7
- package/dist/lanes.js +36 -2
- package/dist/lean.js +2 -2
- package/dist/marks.js +3 -0
- package/dist/mixer.d.ts +16 -1
- package/dist/mixer.js +60 -30
- package/dist/move.js +7 -2
- package/dist/numbers.d.ts +5 -1
- package/dist/numbers.js +4 -0
- package/dist/owner.js +6 -1
- package/dist/place.d.ts +8 -0
- package/dist/place.js +16 -8
- package/dist/project.js +3 -3
- package/dist/qualify.d.ts +10 -0
- package/dist/qualify.js +52 -1
- package/dist/reading.d.ts +2 -1
- package/dist/reading.js +2 -1
- package/dist/record.d.ts +6 -0
- package/dist/record.js +1 -0
- package/dist/restored.d.ts +67 -18
- package/dist/restored.js +159 -49
- package/dist/revive.d.ts +1 -0
- package/dist/revive.js +1 -0
- package/dist/roster.js +2 -1
- package/dist/seek.js +72 -18
- package/dist/shared.d.ts +1 -1
- package/dist/shared.js +1 -1
- package/dist/spans.d.ts +5 -0
- package/dist/spans.js +75 -5
- package/dist/store.d.ts +2 -0
- package/dist/store.js +4 -0
- package/dist/touch.js +3 -2
- package/dist/transport.js +6 -1
- package/dist/types/arc.d.ts +55 -0
- package/dist/types/arc.js +1 -0
- package/dist/types/mix.d.ts +24 -0
- package/dist/types/voice.d.ts +10 -1
- package/dist/types/watch.d.ts +126 -0
- package/dist/types/watch.js +1 -0
- package/dist/types.d.ts +2 -0
- package/dist/voice.d.ts +37 -6
- package/dist/voice.js +17 -0
- package/dist/watch.d.ts +28 -0
- package/dist/watch.js +467 -0
- package/dist/weigh.d.ts +13 -2
- package/dist/weigh.js +10 -3
- package/docs/schema.html +39 -3
- package/package.json +5 -4
package/dist/restored.js
CHANGED
|
@@ -1,33 +1,70 @@
|
|
|
1
1
|
import { Store } from './store.js';
|
|
2
|
+
const stamp = (of) => ({
|
|
3
|
+
t: Number.NaN,
|
|
4
|
+
seq: 0,
|
|
5
|
+
up: null,
|
|
6
|
+
rank: 0,
|
|
7
|
+
left: null,
|
|
8
|
+
cut: null,
|
|
9
|
+
of,
|
|
10
|
+
});
|
|
11
|
+
/** What a reaching record a store has let go of is stamped with, so setting it again is seen. */
|
|
12
|
+
const gone = stamp(null);
|
|
13
|
+
/** The root `s` was joined to, halving the path there for the next read. */
|
|
14
|
+
function rootOf(s) {
|
|
15
|
+
let x = s;
|
|
16
|
+
let up = x.up;
|
|
17
|
+
while (up !== null) {
|
|
18
|
+
const next = up.up;
|
|
19
|
+
if (next === null)
|
|
20
|
+
return up;
|
|
21
|
+
x.up = next;
|
|
22
|
+
x = next;
|
|
23
|
+
up = x.up;
|
|
24
|
+
}
|
|
25
|
+
return x;
|
|
26
|
+
}
|
|
2
27
|
/**
|
|
3
28
|
* A voice's records once a seek has gone back: each subject's is put back from the record it had,
|
|
4
29
|
* the first time it is asked for after a seek, since the subjects a store holds cannot be listed.
|
|
5
|
-
* It is one store however many seeks there are: by subject it keeps
|
|
6
|
-
*
|
|
7
|
-
* seek back cuts what came after it and a later seek ahead of that moment must not
|
|
30
|
+
* It is one store however many seeks there are: by subject it keeps the stamp of the seek its
|
|
31
|
+
* record was last put back at, and a record behind is put back to the earliest moment sought
|
|
32
|
+
* since, as a seek back cuts what came after it and a later seek ahead of that moment must not
|
|
33
|
+
* read past it.
|
|
8
34
|
*/
|
|
9
35
|
export class Restored extends Store {
|
|
10
36
|
own;
|
|
11
37
|
now;
|
|
12
38
|
make;
|
|
13
|
-
/**
|
|
14
|
-
|
|
15
|
-
/**
|
|
39
|
+
/** What a record put back now is stamped with, filled by the next seek. */
|
|
40
|
+
stamp = stamp(this);
|
|
41
|
+
/** What a record never stamped reads as: the first seek's. */
|
|
42
|
+
first = this.stamp;
|
|
43
|
+
/**
|
|
44
|
+
* By subject, the stamp its record was last put back, written or deleted at; none reads as
|
|
45
|
+
* `first`. A record that reaches its subject is its own, and holds it as `restored` instead,
|
|
46
|
+
* sparing a write here per subject a seek puts back; one that does not may stand for many.
|
|
47
|
+
*/
|
|
16
48
|
at = new Store();
|
|
17
49
|
/**
|
|
18
|
-
*
|
|
19
|
-
*
|
|
50
|
+
* The roots, each moment earlier than the next, held weakly: one no record can reach is never
|
|
51
|
+
* read again, and those are cleared out once the list doubles.
|
|
52
|
+
*/
|
|
53
|
+
roots = [];
|
|
54
|
+
kept = 0;
|
|
55
|
+
/**
|
|
56
|
+
* Stamps made a batch ahead of their seeks: one made at its seek is young when every old record
|
|
57
|
+
* is written with it, and V8 notes each such write for its next scavenge.
|
|
20
58
|
*/
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
futures = null;
|
|
59
|
+
ready = [];
|
|
60
|
+
aging = [];
|
|
24
61
|
/** The latest seek's moment. */
|
|
25
|
-
|
|
62
|
+
top = Number.NaN;
|
|
26
63
|
/**
|
|
27
64
|
* `own` holds the records as they stood before the first seek, and `now` gives the one a subject
|
|
28
|
-
* that has not left since has there, as a seek to `
|
|
29
|
-
*
|
|
30
|
-
*
|
|
65
|
+
* that has not left since and is not reached has there, as a seek to `at` takes it. `make` puts
|
|
66
|
+
* a record back as it stood at `at`, undefined where it had none then; `mine` says `live` is the
|
|
67
|
+
* record this store held, which nothing else does.
|
|
31
68
|
*/
|
|
32
69
|
constructor(own, now, make) {
|
|
33
70
|
super();
|
|
@@ -37,57 +74,130 @@ export class Restored extends Store {
|
|
|
37
74
|
}
|
|
38
75
|
/** A seek back to `t`: `left` has the records subjects left with after `cut`, null for none. */
|
|
39
76
|
back(t, cut, left) {
|
|
40
|
-
const
|
|
41
|
-
const least = this.least;
|
|
42
|
-
while (least.length > 0 && least[least.length - 1].t >= t)
|
|
43
|
-
least.pop();
|
|
44
|
-
least.push({ n, t, seq: cut.seq });
|
|
45
|
-
this.t = t;
|
|
77
|
+
const s = this.stamp;
|
|
46
78
|
if (left !== null) {
|
|
47
|
-
|
|
48
|
-
|
|
79
|
+
s.left = left;
|
|
80
|
+
s.cut = cut;
|
|
81
|
+
}
|
|
82
|
+
// Every root at or after `t` now reads `t`, so they and `s` are joined under one.
|
|
83
|
+
const roots = this.roots;
|
|
84
|
+
let root = s;
|
|
85
|
+
let ref = null;
|
|
86
|
+
while (roots.length > 0) {
|
|
87
|
+
const r = roots[roots.length - 1].deref();
|
|
88
|
+
if (r !== undefined && r.t < t)
|
|
89
|
+
break;
|
|
90
|
+
const w = roots.pop();
|
|
91
|
+
if (r === undefined)
|
|
92
|
+
continue;
|
|
93
|
+
if (r.rank > root.rank) {
|
|
94
|
+
root.up = r;
|
|
95
|
+
root = r;
|
|
96
|
+
ref = w;
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
r.up = root;
|
|
100
|
+
if (r.rank === root.rank)
|
|
101
|
+
root.rank++;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
root.t = t;
|
|
105
|
+
root.seq = cut.seq;
|
|
106
|
+
roots.push(ref ?? new WeakRef(root));
|
|
107
|
+
if (roots.length > 2 * this.kept + 16) {
|
|
108
|
+
this.roots = roots.filter((w) => w.deref() !== undefined);
|
|
109
|
+
this.kept = this.roots.length;
|
|
49
110
|
}
|
|
111
|
+
this.stamp = this.fresh();
|
|
112
|
+
this.top = t;
|
|
50
113
|
}
|
|
51
|
-
/** The
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
hi = mid;
|
|
60
|
-
else
|
|
61
|
-
lo = mid + 1;
|
|
114
|
+
/** The next seek's stamp, from a batch made a batch of seeks ago, twice as many each time to 64. */
|
|
115
|
+
fresh() {
|
|
116
|
+
if (this.ready.length === 0) {
|
|
117
|
+
const n = Math.min(64, Math.max(1, this.aging.length * 2));
|
|
118
|
+
this.ready = this.aging.reverse();
|
|
119
|
+
this.aging = [];
|
|
120
|
+
for (let i = 0; i < n; i++)
|
|
121
|
+
this.aging.push(stamp(this));
|
|
62
122
|
}
|
|
63
|
-
return
|
|
123
|
+
return this.ready.pop() ?? stamp(this);
|
|
64
124
|
}
|
|
125
|
+
/**
|
|
126
|
+
* Whether a reaching record stamped by this store is still its record for the key it was found
|
|
127
|
+
* under: every record let go is stamped `gone`, copies are stamped with nothing, and this turns
|
|
128
|
+
* false if a stamped record is set again, which could put it under a second key.
|
|
129
|
+
*/
|
|
130
|
+
sure = true;
|
|
65
131
|
get(key) {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
132
|
+
return this.from(key, this.own.get(key));
|
|
133
|
+
}
|
|
134
|
+
/** A chain's record for a subject, found again without a lookup where this store stamped it. */
|
|
135
|
+
find(key, guess) {
|
|
136
|
+
const s = guess.restored;
|
|
137
|
+
return s !== null && s.of === this && this.sure ? this.from(key, guess) : this.get(key);
|
|
138
|
+
}
|
|
139
|
+
/** `get`, given what `own` holds for `key`. */
|
|
140
|
+
from(key, own) {
|
|
141
|
+
const mark = own !== undefined && own.reaches;
|
|
142
|
+
const at = (mark ? own.restored : this.at.get(key)) ?? this.first;
|
|
143
|
+
const now = this.stamp;
|
|
144
|
+
if (at === now)
|
|
145
|
+
return own;
|
|
146
|
+
if (mark)
|
|
147
|
+
own.restored = now;
|
|
148
|
+
else
|
|
149
|
+
this.at.set(key, now);
|
|
70
150
|
// A subject that left after the moment the next seek sought had the record it left with,
|
|
71
151
|
// whatever it has now. Leaving is a write, so no later seek's leavings can hold it.
|
|
72
|
-
const
|
|
73
|
-
const
|
|
74
|
-
const live =
|
|
75
|
-
const made = live && this.make(live,
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
152
|
+
const root = rootOf(at);
|
|
153
|
+
const left = at.left?.at(key, at.cut);
|
|
154
|
+
const live = left ?? this.now(key, own, root);
|
|
155
|
+
const made = live && this.make(live, root, left === undefined);
|
|
156
|
+
// Looked up again, not taken from `own`: a call made while it was put back may have set it.
|
|
157
|
+
if (made === undefined) {
|
|
158
|
+
this.shed(key);
|
|
159
|
+
if (mark)
|
|
160
|
+
this.at.set(key, now);
|
|
161
|
+
}
|
|
162
|
+
else if (made !== own)
|
|
163
|
+
this.set(key, made);
|
|
164
|
+
else if (!made.reaches)
|
|
165
|
+
this.at.set(key, now);
|
|
80
166
|
return made;
|
|
81
167
|
}
|
|
82
168
|
set(key, value) {
|
|
169
|
+
const old = this.own.get(key);
|
|
170
|
+
if (old === value) {
|
|
171
|
+
if (value.reaches)
|
|
172
|
+
value.restored = this.stamp;
|
|
173
|
+
else
|
|
174
|
+
this.at.set(key, this.stamp);
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
if (old?.reaches)
|
|
178
|
+
old.restored = gone;
|
|
179
|
+
// Stamped already, it may be in a chain under another key.
|
|
180
|
+
if (value.reaches && value.restored !== null)
|
|
181
|
+
this.sure = false;
|
|
83
182
|
this.own.set(key, value);
|
|
84
|
-
|
|
183
|
+
if (value.reaches)
|
|
184
|
+
value.restored = this.stamp;
|
|
185
|
+
else
|
|
186
|
+
this.at.set(key, this.stamp);
|
|
85
187
|
}
|
|
86
188
|
delete(key) {
|
|
189
|
+
this.shed(key);
|
|
190
|
+
this.at.set(key, this.stamp);
|
|
191
|
+
}
|
|
192
|
+
/** Lets go of the record held for `key`, stamping it `gone`. */
|
|
193
|
+
shed(key) {
|
|
194
|
+
const old = this.own.get(key);
|
|
195
|
+
if (old?.reaches)
|
|
196
|
+
old.restored = gone;
|
|
87
197
|
this.own.delete(key);
|
|
88
|
-
this.at.set(key, this.n);
|
|
89
198
|
}
|
|
90
199
|
clear() {
|
|
200
|
+
this.sure = false;
|
|
91
201
|
this.own.clear();
|
|
92
202
|
this.at.clear();
|
|
93
203
|
}
|
package/dist/revive.d.ts
CHANGED
package/dist/revive.js
CHANGED
package/dist/roster.js
CHANGED
|
@@ -4,7 +4,8 @@ import { Store } from './store.js';
|
|
|
4
4
|
function tally(mix, voice, by) {
|
|
5
5
|
if (voice.spec.locus !== undefined)
|
|
6
6
|
mix.loci += by;
|
|
7
|
-
|
|
7
|
+
const e = voice.spec.earliest;
|
|
8
|
+
if (voice.spec.anchor !== undefined || (e !== undefined && typeof e !== 'number'))
|
|
8
9
|
mix.anchored += by;
|
|
9
10
|
}
|
|
10
11
|
/** Counts a voice just put in the list into everything a sync and a fold skip work by. */
|
package/dist/seek.js
CHANGED
|
@@ -2,7 +2,7 @@ import { heldAt } from './chain.js';
|
|
|
2
2
|
import { schedule } from './due.js';
|
|
3
3
|
import { fadesAt } from './fade.js';
|
|
4
4
|
import { origin } from './held.js';
|
|
5
|
-
import { apart, copyHeld,
|
|
5
|
+
import { apart, copyHeld, copyHeldInto, lastIndex, lastWithin, unread } from './history.js';
|
|
6
6
|
import { laneHost } from './hosts.js';
|
|
7
7
|
import { Leavings } from './leavings.js';
|
|
8
8
|
import { move, nextFrame } from './move.js';
|
|
@@ -22,12 +22,15 @@ import { cover, coverFrame, cut } from './unpage.js';
|
|
|
22
22
|
* probe by `t` read is not kept, and a lane holds it apart from the record, so the record reads 0
|
|
23
23
|
* until the next probe.
|
|
24
24
|
*/
|
|
25
|
-
function restore(mix, voice, live,
|
|
25
|
+
function restore(mix, voice, live, at, every, mine) {
|
|
26
|
+
// Read here, not passed: a double passed to a call is boxed, once per record put back.
|
|
27
|
+
const t = at.t;
|
|
26
28
|
const inputs = live.inputs?.filter((e) => e.at <= t);
|
|
27
29
|
const snaps = live.snaps;
|
|
28
30
|
if (snaps === undefined) {
|
|
29
|
-
// Nothing a step or `setting.keep` holds: what it keeps does not move with time.
|
|
30
|
-
|
|
31
|
+
// Nothing a step or `setting.keep` holds: what it keeps does not move with time. One record
|
|
32
|
+
// stands for every subject a voice does not reach, so that one is copied.
|
|
33
|
+
const h = mine && live.reaches ? unread(live) : copyHeld(voice, live);
|
|
31
34
|
if (h.stepped > t)
|
|
32
35
|
h.stepped = t;
|
|
33
36
|
// Whether it had rested by `t` is not kept; the next probe finds it again.
|
|
@@ -38,37 +41,84 @@ function restore(mix, voice, live, t, every) {
|
|
|
38
41
|
origin(mix, voice, h);
|
|
39
42
|
return h;
|
|
40
43
|
}
|
|
41
|
-
|
|
42
|
-
|
|
44
|
+
// A seek back to just before now finds its copy among the newest, looked at before searching.
|
|
45
|
+
const n = snaps.length;
|
|
46
|
+
const i = n > 0 && snaps[n - 1].at <= t
|
|
47
|
+
? n - 1
|
|
48
|
+
: n > 1 && snaps[n - 2].at <= t
|
|
49
|
+
? n - 2
|
|
50
|
+
: lastIndex(snaps, t, true);
|
|
51
|
+
if (i < 0)
|
|
43
52
|
return undefined;
|
|
44
|
-
const
|
|
53
|
+
const snap = snaps[i];
|
|
54
|
+
// What the record replaced held, which a record put back over it would lose.
|
|
55
|
+
const { ticks, since, rebuilt, state, slot, restored } = live;
|
|
56
|
+
let h;
|
|
57
|
+
if (mine && live.reaches) {
|
|
58
|
+
// Its chain may still read its number from it, and its store has just stamped it.
|
|
59
|
+
h = copyHeldInto(voice, snap.held, live, true);
|
|
60
|
+
h.slot = slot;
|
|
61
|
+
h.restored = restored;
|
|
62
|
+
}
|
|
63
|
+
else
|
|
64
|
+
h = copyHeld(voice, snap.held, true);
|
|
45
65
|
h.weight = 0;
|
|
46
66
|
if (snap.at < t)
|
|
47
67
|
h.copied = true;
|
|
48
|
-
|
|
68
|
+
const kept = i + 1;
|
|
69
|
+
// The record replaced is let go, so its list is cut where it stands.
|
|
70
|
+
if (mine)
|
|
71
|
+
trim(voice, snaps, kept);
|
|
72
|
+
else
|
|
73
|
+
for (let i = 0; i < kept; i++)
|
|
74
|
+
snaps[i].shared = true;
|
|
75
|
+
h.snaps = mine ? snaps : snaps.slice(0, kept);
|
|
49
76
|
h.inputs = inputs;
|
|
50
77
|
// A start still ahead when the copy was kept may have moved before `t`, with no copy between.
|
|
51
78
|
if (!(h.since <= snap.at))
|
|
52
79
|
origin(mix, voice, h);
|
|
53
|
-
|
|
54
|
-
|
|
80
|
+
// The record a seek replaces, stepped on from this copy with nothing since rebuilding or moving
|
|
81
|
+
// its start, holds the copy's state at its own `ticks`, which a probe there need not step to.
|
|
82
|
+
const was = mine && ticks > h.ticks && since === h.since && rebuilt === h.rebuilt;
|
|
83
|
+
if (voice.patch.step !== undefined && (snap.ahead !== undefined || t - snap.at >= every || was)) {
|
|
84
|
+
let ahead = snap.ahead;
|
|
55
85
|
// Steps counted from a start that has moved since are not this record's.
|
|
56
|
-
if (ahead === undefined || ahead.since !== h.since)
|
|
57
|
-
|
|
86
|
+
if (ahead === undefined || ahead.since !== h.since) {
|
|
87
|
+
ahead = {
|
|
58
88
|
put: h,
|
|
59
89
|
sought: t,
|
|
60
90
|
since: h.since,
|
|
61
91
|
at: snap.at,
|
|
62
92
|
ticks: h.ticks,
|
|
63
93
|
state: undefined,
|
|
94
|
+
leftTicks: 0,
|
|
95
|
+
left: undefined,
|
|
64
96
|
};
|
|
97
|
+
snap.ahead = ahead;
|
|
98
|
+
}
|
|
65
99
|
else {
|
|
66
100
|
ahead.put = h;
|
|
67
101
|
ahead.sought = t;
|
|
68
102
|
}
|
|
103
|
+
if (was) {
|
|
104
|
+
// Nothing else holds the record replaced, so its state is taken, not copied.
|
|
105
|
+
ahead.leftTicks = ticks;
|
|
106
|
+
ahead.left = state;
|
|
107
|
+
}
|
|
69
108
|
}
|
|
70
109
|
return h;
|
|
71
110
|
}
|
|
111
|
+
/** Cuts a record's list of copies to its first `kept`, keeping those no other list holds as spares. */
|
|
112
|
+
function trim(voice, snaps, kept) {
|
|
113
|
+
while (snaps.length > kept) {
|
|
114
|
+
const snap = snaps.pop();
|
|
115
|
+
if (snap.shared)
|
|
116
|
+
continue;
|
|
117
|
+
snap.ahead = undefined;
|
|
118
|
+
voice.spares ??= [];
|
|
119
|
+
voice.spares.push(snap);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
72
122
|
/** Takes a voice cued after the moment sought out of the mix, until the tape cues it again. */
|
|
73
123
|
function park(mix, voice) {
|
|
74
124
|
const owner = voice.owner;
|
|
@@ -122,6 +172,7 @@ function restoreVoice(mix, voice, t, seq) {
|
|
|
122
172
|
voice.unreached = null;
|
|
123
173
|
voice.unreachedBits = null;
|
|
124
174
|
voice.seeks++;
|
|
175
|
+
voice.spares = null;
|
|
125
176
|
const left = voice.left?.all() ?? [];
|
|
126
177
|
if (voice.left !== null)
|
|
127
178
|
fadesAt(voice, voice.left.after(cut), seq);
|
|
@@ -130,21 +181,22 @@ function restoreVoice(mix, voice, t, seq) {
|
|
|
130
181
|
let records = voice.subjects;
|
|
131
182
|
if (!(records instanceof Restored)) {
|
|
132
183
|
const every = apart(mix.opts.history);
|
|
133
|
-
const now = (subject, held,
|
|
134
|
-
|
|
135
|
-
const h = restore(mix, voice, live,
|
|
184
|
+
const now = (subject, held, at) => heldAt(voice, subject, held, at.t, at.seq);
|
|
185
|
+
const restored = new Restored(records, now, (live, at, mine) => {
|
|
186
|
+
const h = restore(mix, voice, live, at, every, mine);
|
|
136
187
|
if (h === undefined)
|
|
137
188
|
return h;
|
|
138
189
|
if (h.reaches)
|
|
139
190
|
voice.seen++;
|
|
140
191
|
if (h.rested)
|
|
141
192
|
voice.restedCount++;
|
|
142
|
-
if (!(h.since <= top)) {
|
|
193
|
+
if (!(h.since <= restored.top)) {
|
|
143
194
|
voice.early ??= [];
|
|
144
195
|
voice.early.push(h);
|
|
145
196
|
}
|
|
146
197
|
return h;
|
|
147
198
|
});
|
|
199
|
+
records = restored;
|
|
148
200
|
voice.subjects = records;
|
|
149
201
|
}
|
|
150
202
|
records.back(t, cut, future);
|
|
@@ -277,10 +329,12 @@ function back(mix, t, seq, on) {
|
|
|
277
329
|
voice.holding.back(seq);
|
|
278
330
|
}
|
|
279
331
|
// Rebuilt from the voices that are back: the lanes, the chains and every count the fold reads.
|
|
332
|
+
// Subject numbers carry over, and with them the chains that hold them, each linked afresh.
|
|
280
333
|
const oldLanes = mix.lanes;
|
|
281
334
|
if (oldLanes !== null && mix.lanesOf !== null)
|
|
282
|
-
mix.lanes = new mix.lanesOf(laneHost(mix));
|
|
283
|
-
|
|
335
|
+
mix.lanes = new mix.lanesOf(laneHost(mix), oldLanes);
|
|
336
|
+
else
|
|
337
|
+
mix.chains = new Store();
|
|
284
338
|
mix.owners = kept.some((v) => v.holding !== null) ? kept.filter((v) => v.holding !== null) : null;
|
|
285
339
|
recount(mix);
|
|
286
340
|
mix.parters.clear();
|
package/dist/shared.d.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* The package version, for a fact whose shape follows the code too closely to version by hand:
|
|
3
3
|
* copies of different releases keep apart. `test/copies.test.ts` holds it to `package.json`.
|
|
4
4
|
*/
|
|
5
|
-
export declare const release = "0.
|
|
5
|
+
export declare const release = "0.12.1";
|
|
6
6
|
/**
|
|
7
7
|
* A WeakMap every copy of blits loaded in one realm shares, so a fact one copy records about a
|
|
8
8
|
* patch or channel it made is read by another copy's mix. The name carries a version, bumped when
|
package/dist/shared.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* The package version, for a fact whose shape follows the code too closely to version by hand:
|
|
3
3
|
* copies of different releases keep apart. `test/copies.test.ts` holds it to `package.json`.
|
|
4
4
|
*/
|
|
5
|
-
export const release = '0.
|
|
5
|
+
export const release = '0.12.1';
|
|
6
6
|
/**
|
|
7
7
|
* A WeakMap every copy of blits loaded in one realm shares, so a fact one copy records about a
|
|
8
8
|
* patch or channel it made is read by another copy's mix. The name carries a version, bumped when
|
package/dist/spans.d.ts
CHANGED
|
@@ -22,6 +22,11 @@ export declare function checkChild<I, O>(spec: VoiceSpec<I, O>, child: Voice<I,
|
|
|
22
22
|
* after.
|
|
23
23
|
*/
|
|
24
24
|
export declare function refit<I, O>(mix: Mixer<I, O>, span: Voice<I, O>, from?: number): void;
|
|
25
|
+
/**
|
|
26
|
+
* Fits again each span with a child still to start whose anchored `earliest` answers differently
|
|
27
|
+
* than at its last fit: an anchor that has come to have an answer, or moved.
|
|
28
|
+
*/
|
|
29
|
+
export declare function refloor<I, O>(mix: Mixer<I, O>): void;
|
|
25
30
|
/** Whether a span whose last child just left at mix time `at` stays, its budget not yet out. */
|
|
26
31
|
export declare function lingers<I, O>(span: Voice<I, O>, at: number): boolean;
|
|
27
32
|
/** Lets the spans that are empty and out of budget leave, at the moment their budget ran out. */
|
package/dist/spans.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { arcEnd } from './arcs.js';
|
|
1
2
|
import { elapsedWith, retime } from './clock.js';
|
|
2
3
|
import { retire } from './fade.js';
|
|
3
4
|
import { defaultFit, layout, settle } from './fit.js';
|
|
@@ -6,6 +7,7 @@ import { noted } from './history.js';
|
|
|
6
7
|
import { nextFrame } from './move.js';
|
|
7
8
|
import { leave } from './origin.js';
|
|
8
9
|
import { ownerReading } from './owner.js';
|
|
10
|
+
import { resolve } from './place.js';
|
|
9
11
|
/** What a span keeps beside its owner: its claim, its fit, and how the last fit came out. */
|
|
10
12
|
export class Fitting {
|
|
11
13
|
budget;
|
|
@@ -39,9 +41,13 @@ export class Fitting {
|
|
|
39
41
|
export function checkChild(spec, child) {
|
|
40
42
|
if (spec.start !== undefined || spec.anchor?.start !== undefined || spec.anchor?.in !== undefined)
|
|
41
43
|
throw new Error('blits: a span places the voices it holds, so one takes no start of its own');
|
|
42
|
-
if (child.
|
|
44
|
+
if (child.arc !== null) {
|
|
45
|
+
if (!Number.isFinite(arcEnd(child)))
|
|
46
|
+
throw new Error('blits: a span cannot fit an arc held open, which never ends');
|
|
47
|
+
}
|
|
48
|
+
else if (child.holding !== null) {
|
|
43
49
|
if (child.fitting === null)
|
|
44
|
-
throw new Error('blits: a span holds voices and
|
|
50
|
+
throw new Error('blits: a span holds voices, spans and arcs, not owners');
|
|
45
51
|
}
|
|
46
52
|
else if (!Number.isFinite(child.span))
|
|
47
53
|
throw new Error('blits: a span cannot fit a voice that never ends');
|
|
@@ -59,7 +65,7 @@ function elapsedIn(child, local) {
|
|
|
59
65
|
}
|
|
60
66
|
/** How much of a child is left, voice ms at its own clock: what its span still has to fit. */
|
|
61
67
|
function leftOf(child, local) {
|
|
62
|
-
const length = child.fitting !== null ? child.fitting.end : child.span;
|
|
68
|
+
const length = child.fitting !== null ? child.fitting.end : child.arc !== null ? arcEnd(child) : child.span;
|
|
63
69
|
return Math.max(0, length - elapsedIn(child, local));
|
|
64
70
|
}
|
|
65
71
|
/**
|
|
@@ -73,10 +79,11 @@ export function refit(mix, span, from = mix.now) {
|
|
|
73
79
|
if (mix.projecting || span.state === 'done')
|
|
74
80
|
return;
|
|
75
81
|
const local = clockOf(span, from);
|
|
76
|
-
const kids
|
|
77
|
-
const claims = kids.map((c) => {
|
|
82
|
+
const { kids, floors, waiting } = lineUp(mix, span, local);
|
|
83
|
+
const claims = kids.map((c, i) => {
|
|
78
84
|
const s = c.spec;
|
|
79
85
|
return {
|
|
86
|
+
floor: floors[i],
|
|
80
87
|
natural: leftOf(c, local) / (s.rate ?? 1),
|
|
81
88
|
state: c.state === 'pending' ? 'pending' : 'playing',
|
|
82
89
|
faster: Math.max(1, s.faster ?? 1),
|
|
@@ -127,6 +134,17 @@ export function refit(mix, span, from = mix.now) {
|
|
|
127
134
|
noted(mix, c);
|
|
128
135
|
}
|
|
129
136
|
});
|
|
137
|
+
for (const c of waiting) {
|
|
138
|
+
if (c.start === Number.POSITIVE_INFINITY)
|
|
139
|
+
continue;
|
|
140
|
+
c.start = Number.POSITIVE_INFINITY;
|
|
141
|
+
c.anchorNow = Number.POSITIVE_INFINITY;
|
|
142
|
+
c.anchorElapsed = 0;
|
|
143
|
+
c.clocks = null;
|
|
144
|
+
mix.frame = nextFrame();
|
|
145
|
+
reorigin(mix, c, mix.now);
|
|
146
|
+
noted(mix, c);
|
|
147
|
+
}
|
|
130
148
|
if (kids.length > 0)
|
|
131
149
|
mix.lanes?.refill();
|
|
132
150
|
const reach = local + length;
|
|
@@ -147,6 +165,58 @@ export function refit(mix, span, from = mix.now) {
|
|
|
147
165
|
refit(mix, span.owner, from);
|
|
148
166
|
}
|
|
149
167
|
}
|
|
168
|
+
/**
|
|
169
|
+
* A span's children still to fit, in the order it lays them out: those playing first, as cued, then
|
|
170
|
+
* those still to start by rank. Each pending one's floor is where its `earliest` answers, span ms
|
|
171
|
+
* from `local`, and before `local` only in the frame it was cued. One whose anchor has no answer yet waits unplaced, and under `queue` or `stagger`
|
|
172
|
+
* so does every child after it, since each is laid out from the one before.
|
|
173
|
+
*/
|
|
174
|
+
function lineUp(mix, span, local) {
|
|
175
|
+
const live = (span.holding?.children ?? []).filter((c) => c.state !== 'done' && c.state !== 'frozen' && c.state !== 'fading' && leftOf(c, local) > 0);
|
|
176
|
+
const kids = live.filter((c) => c.state !== 'pending');
|
|
177
|
+
const floors = kids.map(() => undefined);
|
|
178
|
+
const waiting = [];
|
|
179
|
+
const pending = live
|
|
180
|
+
.filter((c) => c.state === 'pending')
|
|
181
|
+
.sort((a, b) => (a.spec.rank ?? 0) - (b.spec.rank ?? 0));
|
|
182
|
+
const chained = span.fitting.claim.order !== 'together';
|
|
183
|
+
for (const c of pending) {
|
|
184
|
+
const e = c.spec.earliest;
|
|
185
|
+
const t = e === undefined ? undefined : resolve(mix, e, c, Number.NEGATIVE_INFINITY);
|
|
186
|
+
c.floorSeen = t ?? Number.NaN;
|
|
187
|
+
if ((e !== undefined && t === undefined) || (chained && waiting.length > 0)) {
|
|
188
|
+
waiting.push(c);
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
kids.push(c);
|
|
192
|
+
// An anchor that answered before the span began has no time on its clock: no bound.
|
|
193
|
+
const floor = t === undefined || t === Number.NEGATIVE_INFINITY ? undefined : t - local;
|
|
194
|
+
// A past floor places a child only as it is cued: one waiting since waits on those before it.
|
|
195
|
+
floors.push(floor === undefined || c.cuedSeq === mix.transport.seq ? floor : Math.max(0, floor));
|
|
196
|
+
}
|
|
197
|
+
return { kids, floors, waiting };
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Fits again each span with a child still to start whose anchored `earliest` answers differently
|
|
201
|
+
* than at its last fit: an anchor that has come to have an answer, or moved.
|
|
202
|
+
*/
|
|
203
|
+
export function refloor(mix) {
|
|
204
|
+
if (mix.projecting)
|
|
205
|
+
return;
|
|
206
|
+
for (const span of mix.owners ?? []) {
|
|
207
|
+
if (span.fitting === null || span.state === 'done')
|
|
208
|
+
continue;
|
|
209
|
+
const moved = (span.holding?.children ?? []).some((c) => {
|
|
210
|
+
const e = c.spec.earliest;
|
|
211
|
+
if (c.state !== 'pending' || e === undefined || typeof e === 'number')
|
|
212
|
+
return false;
|
|
213
|
+
const t = resolve(mix, e, c, Number.NEGATIVE_INFINITY) ?? Number.NaN;
|
|
214
|
+
return !Object.is(t, c.floorSeen);
|
|
215
|
+
});
|
|
216
|
+
if (moved)
|
|
217
|
+
refit(mix, span);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
150
220
|
/** Whether a span whose last child just left at mix time `at` stays, its budget not yet out. */
|
|
151
221
|
export function lingers(span, at) {
|
|
152
222
|
const f = span.fitting;
|
package/dist/store.d.ts
CHANGED
|
@@ -16,6 +16,8 @@ export declare class Store<K, V> {
|
|
|
16
16
|
private oneValue;
|
|
17
17
|
get(key: K): V | undefined;
|
|
18
18
|
set(key: K, value: V): void;
|
|
19
|
+
/** As `get`, where `guess` may be the value for `key`, which a store that can tell spares a lookup. */
|
|
20
|
+
find(key: K, _guess: V): V | undefined;
|
|
19
21
|
has(key: K): boolean;
|
|
20
22
|
delete(key: K): void;
|
|
21
23
|
clear(): void;
|
package/dist/store.js
CHANGED
|
@@ -43,6 +43,10 @@ export class Store {
|
|
|
43
43
|
}
|
|
44
44
|
this.strong.set(key, value);
|
|
45
45
|
}
|
|
46
|
+
/** As `get`, where `guess` may be the value for `key`, which a store that can tell spares a lookup. */
|
|
47
|
+
find(key, _guess) {
|
|
48
|
+
return this.get(key);
|
|
49
|
+
}
|
|
46
50
|
has(key) {
|
|
47
51
|
return this.get(key) !== undefined;
|
|
48
52
|
}
|
package/dist/touch.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { headOf } from './chain.js';
|
|
1
2
|
import { restamp } from './everyone.js';
|
|
2
3
|
import { nextFrame } from './move.js';
|
|
3
4
|
/**
|
|
@@ -16,10 +17,10 @@ export function touch(mix, subject) {
|
|
|
16
17
|
}
|
|
17
18
|
else {
|
|
18
19
|
// No voice's `seeks` is below 0, so no record stamped with it reads as current.
|
|
19
|
-
let held = mix
|
|
20
|
+
let held = headOf(mix, subject) ?? null;
|
|
20
21
|
for (; held !== null; held = held.next)
|
|
21
22
|
held.seeks = -1;
|
|
22
|
-
const slot = mix
|
|
23
|
+
const slot = headOf(mix, subject)?.slot ?? -1;
|
|
23
24
|
for (const voice of mix.sharers)
|
|
24
25
|
restamp(voice, slot);
|
|
25
26
|
}
|