@msb235/blits 0.7.0 → 0.8.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/CHANGELOG.md +196 -0
- package/README.md +22 -4
- package/dist/book.js +4 -4
- package/dist/chain.d.ts +1 -1
- package/dist/chain.js +57 -54
- package/dist/channels.d.ts +26 -5
- package/dist/channels.js +12 -8
- package/dist/clock.d.ts +8 -1
- package/dist/clock.js +14 -3
- package/dist/clone.d.ts +3 -0
- package/dist/clone.js +16 -1
- package/dist/closed.d.ts +1 -1
- package/dist/closed.js +3 -2
- package/dist/color.d.ts +0 -11
- package/dist/color.js +4 -24
- package/dist/columns.js +0 -1
- package/dist/copyout.d.ts +6 -0
- package/dist/copyout.js +38 -0
- package/dist/crowd.d.ts +7 -5
- package/dist/crowd.js +32 -18
- package/dist/cue.d.ts +3 -1
- package/dist/cue.js +33 -20
- package/dist/departed.d.ts +34 -0
- package/dist/departed.js +73 -0
- package/dist/easing.d.ts +5 -0
- package/dist/easing.js +31 -2
- package/dist/everyone.d.ts +56 -0
- package/dist/everyone.js +221 -0
- package/dist/fade.d.ts +8 -1
- package/dist/fade.js +36 -6
- package/dist/fill.d.ts +22 -13
- package/dist/fill.js +104 -41
- package/dist/fit.js +2 -1
- package/dist/fold.d.ts +37 -0
- package/dist/fold.js +213 -28
- package/dist/gather.d.ts +15 -9
- package/dist/gather.js +60 -25
- package/dist/gone.d.ts +32 -0
- package/dist/gone.js +144 -0
- package/dist/handle.d.ts +16 -5
- package/dist/handle.js +56 -6
- package/dist/held.d.ts +8 -5
- package/dist/held.js +56 -58
- package/dist/history.d.ts +17 -13
- package/dist/history.js +49 -28
- package/dist/hosts.js +57 -9
- package/dist/index.d.ts +9 -4
- package/dist/index.js +5 -2
- package/dist/keyfill.d.ts +7 -0
- package/dist/keyfill.js +124 -0
- package/dist/keyrows.d.ts +1 -0
- package/dist/keyrows.js +1 -1
- package/dist/lane.d.ts +32 -2
- package/dist/lane.js +5 -0
- package/dist/lanes.d.ts +32 -8
- package/dist/lanes.js +69 -37
- package/dist/leavings.d.ts +26 -0
- package/dist/leavings.js +70 -0
- package/dist/meet.js +14 -8
- package/dist/mixer.d.ts +43 -7
- package/dist/mixer.js +60 -11
- package/dist/motion.d.ts +2 -0
- package/dist/motion.js +4 -1
- package/dist/motions.d.ts +22 -15
- package/dist/motions.js +40 -34
- package/dist/move.js +31 -20
- package/dist/moved.d.ts +11 -0
- package/dist/moved.js +29 -0
- package/dist/origin.d.ts +19 -0
- package/dist/origin.js +68 -0
- package/dist/owed.d.ts +4 -4
- package/dist/owed.js +8 -8
- package/dist/owner.d.ts +1 -1
- package/dist/owner.js +2 -2
- package/dist/pace.d.ts +13 -5
- package/dist/pace.js +21 -8
- package/dist/pack.d.ts +3 -1
- package/dist/pack.js +15 -24
- package/dist/paging.d.ts +8 -2
- package/dist/paging.js +44 -4
- package/dist/patch.d.ts +14 -9
- package/dist/patch.js +83 -21
- package/dist/place.js +188 -30
- package/dist/project.d.ts +12 -1
- package/dist/project.js +101 -83
- package/dist/pull.d.ts +2 -0
- package/dist/pull.js +8 -0
- package/dist/qualify.js +10 -2
- package/dist/reading.d.ts +5 -0
- package/dist/reading.js +5 -0
- package/dist/record.d.ts +9 -0
- package/dist/record.js +42 -0
- package/dist/relink.d.ts +11 -5
- package/dist/relink.js +7 -5
- package/dist/restored.d.ts +41 -0
- package/dist/restored.js +91 -0
- package/dist/revive.d.ts +10 -3
- package/dist/revive.js +31 -23
- package/dist/roster.d.ts +11 -0
- package/dist/roster.js +46 -0
- package/dist/rotation.d.ts +29 -0
- package/dist/rotation.js +91 -0
- package/dist/rows.d.ts +5 -5
- package/dist/rows.js +5 -5
- package/dist/sample.d.ts +3 -2
- package/dist/sample.js +32 -15
- package/dist/scored.d.ts +57 -0
- package/dist/scored.js +196 -0
- package/dist/seek.js +97 -114
- package/dist/shared.d.ts +6 -0
- package/dist/shared.js +15 -0
- package/dist/signals.d.ts +24 -3
- package/dist/signals.js +50 -11
- package/dist/spans.js +7 -0
- package/dist/stack.d.ts +11 -0
- package/dist/stack.js +47 -0
- package/dist/standing.d.ts +10 -0
- package/dist/standing.js +79 -0
- package/dist/store.d.ts +3 -3
- package/dist/store.js +3 -3
- package/dist/strays.js +3 -0
- package/dist/tape.d.ts +8 -5
- package/dist/tape.js +21 -13
- package/dist/testing.d.ts +35 -0
- package/dist/testing.js +116 -0
- package/dist/touch.d.ts +6 -0
- package/dist/touch.js +28 -0
- package/dist/transport.d.ts +53 -6
- package/dist/transport.js +102 -20
- package/dist/turns.d.ts +18 -0
- package/dist/turns.js +72 -0
- package/dist/tweened.js +1 -1
- package/dist/types/channel.d.ts +46 -0
- package/dist/types/channel.js +1 -0
- package/dist/types/history.d.ts +139 -0
- package/dist/types/history.js +1 -0
- package/dist/types/mix.d.ts +336 -0
- package/dist/types/mix.js +1 -0
- package/dist/types/patch.d.ts +187 -0
- package/dist/types/patch.js +1 -0
- package/dist/types/place.d.ts +157 -0
- package/dist/types/place.js +1 -0
- package/dist/types/transport.d.ts +71 -0
- package/dist/types/transport.js +1 -0
- package/dist/types/voice.d.ts +323 -0
- package/dist/types/voice.js +1 -0
- package/dist/types.d.ts +7 -1162
- package/dist/unpage.d.ts +2 -2
- package/dist/unpage.js +55 -25
- package/dist/voice.d.ts +92 -22
- package/dist/voice.js +108 -43
- package/dist/wave.d.ts +42 -0
- package/dist/wave.js +62 -0
- package/dist/weigh.js +1 -1
- package/docs/schema.html +42 -36
- package/package.json +8 -3
package/dist/project.js
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
import { ownBlends } from './blend.js';
|
|
2
|
-
import {
|
|
3
|
-
import { copyHeld, last,
|
|
2
|
+
import { open, sighted, weightAt } from './everyone.js';
|
|
3
|
+
import { copyHeld, last, lastWithin } from './history.js';
|
|
4
4
|
import { Mixer } from './mixer.js';
|
|
5
5
|
import { move } from './move.js';
|
|
6
6
|
import { heldByInput, ownerReading, relink } from './owner.js';
|
|
7
7
|
import { pin } from './place.js';
|
|
8
8
|
import { reading } from './reading.js';
|
|
9
|
-
import {
|
|
9
|
+
import { record } from './record.js';
|
|
10
|
+
import { recount } from './roster.js';
|
|
11
|
+
import { copyStanding, stands } from './standing.js';
|
|
10
12
|
import { hostAt } from './tape.js';
|
|
11
|
-
import { Transport } from './transport.js';
|
|
13
|
+
import { Transport, within } from './transport.js';
|
|
12
14
|
import { cover } from './unpage.js';
|
|
13
15
|
import { unreached } from './unreached.js';
|
|
14
16
|
/**
|
|
@@ -18,24 +20,36 @@ import { unreached } from './unreached.js';
|
|
|
18
20
|
export function projectAll(transport, t) {
|
|
19
21
|
const ahead = Number.isNaN(transport.now) || t >= transport.now;
|
|
20
22
|
const tape = transport.tape;
|
|
21
|
-
const
|
|
23
|
+
const stamp = ahead && tape !== undefined ? tape.timestampAt(tape.undoDepth()) : undefined;
|
|
24
|
+
const pace = transport.pace;
|
|
25
|
+
const next = stamp === undefined || pace === null ? stamp : pace.reading(stamp);
|
|
22
26
|
if (next !== undefined && next <= t)
|
|
23
27
|
throw new Error(`blits: the tape holds calls at ${next} that a read ahead to ${t} cannot play; seek there to see them`);
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
+
// Behind, the last frame at or before `t`, read as it showed when `t` is that frame's own time.
|
|
29
|
+
let cut = { seq: transport.seq, strict: false };
|
|
30
|
+
let u = hostAt(transport, t);
|
|
31
|
+
const kept = transport.history !== undefined;
|
|
32
|
+
if (!ahead && kept) {
|
|
28
33
|
cover(transport, t);
|
|
34
|
+
const frame = transport.frameAt(t);
|
|
35
|
+
if (frame !== undefined) {
|
|
36
|
+
cut = { seq: frame.seq, strict: frame.at === t };
|
|
37
|
+
if (frame.at === t)
|
|
38
|
+
u = frame.u;
|
|
39
|
+
}
|
|
29
40
|
}
|
|
30
|
-
|
|
31
|
-
|
|
41
|
+
else if (!ahead)
|
|
42
|
+
stands(transport, t);
|
|
32
43
|
const c = new Transport(undefined, true);
|
|
33
44
|
c.offset = transport.offset;
|
|
34
45
|
c.u = u;
|
|
35
46
|
c.nextId = transport.nextId;
|
|
36
47
|
c.now = ahead ? transport.now : t;
|
|
37
|
-
c.pace =
|
|
38
|
-
|
|
48
|
+
c.pace =
|
|
49
|
+
pace === null || !within(cut, transport.pacedSeq)
|
|
50
|
+
? null
|
|
51
|
+
: pace.until(ahead ? Number.POSITIVE_INFINITY : u);
|
|
52
|
+
c.announced = (ahead ? transport.announced : transport.announced.filter((a) => within(cut, a.seq))).map((a) => ({ ...a }));
|
|
39
53
|
const copies = new Map();
|
|
40
54
|
for (const mix of transport.members) {
|
|
41
55
|
const copy = new Mixer(mix.kit, {
|
|
@@ -49,8 +63,10 @@ export function projectAll(transport, t) {
|
|
|
49
63
|
copies.set(mix, copy);
|
|
50
64
|
if (ahead)
|
|
51
65
|
copyAhead(mix, copy);
|
|
66
|
+
else if (kept)
|
|
67
|
+
copyBack(mix, copy, t, cut);
|
|
52
68
|
else
|
|
53
|
-
|
|
69
|
+
copyStanding(mix, copy, t, cut);
|
|
54
70
|
}
|
|
55
71
|
if (ahead)
|
|
56
72
|
for (const copy of copies.values())
|
|
@@ -83,13 +99,11 @@ export function projectAll(transport, t) {
|
|
|
83
99
|
}
|
|
84
100
|
/** A mix's copy for a read ahead: its voices as they stand, each subject carried from the live one. */
|
|
85
101
|
function copyAhead(mix, c) {
|
|
86
|
-
c
|
|
87
|
-
c.wantsPose = mix.wantsPose;
|
|
88
|
-
c.reducedNow = mix.reducedNow;
|
|
102
|
+
mirror(mix, c);
|
|
89
103
|
c.cued = mix.cued
|
|
90
104
|
.filter((v) => v.state !== 'done')
|
|
91
105
|
.map((v) => {
|
|
92
|
-
const copy = v.copy((subject) => carry(mix, v, subject));
|
|
106
|
+
const copy = v.copy((subject) => carry(mix, v, subject, c, copy), undefined, true);
|
|
93
107
|
const at = mix.pins?.get(v);
|
|
94
108
|
if (at !== undefined)
|
|
95
109
|
pin(c, copy, at);
|
|
@@ -98,15 +112,14 @@ function copyAhead(mix, c) {
|
|
|
98
112
|
ownBlends(c.cued);
|
|
99
113
|
if (mix.owners !== null)
|
|
100
114
|
relink(c.cued);
|
|
101
|
-
|
|
115
|
+
recount(c);
|
|
102
116
|
}
|
|
103
|
-
/** A mix's copy for a read back to `t`: its voices then, from the controls and copies kept. */
|
|
104
|
-
function copyBack(mix, c, t) {
|
|
105
|
-
c
|
|
106
|
-
c.wantsPose = mix.wantsPose;
|
|
107
|
-
c.reducedNow = mix.reducedNow;
|
|
117
|
+
/** A mix's copy for a read back to `t`, cut at `cut`: its voices then, from the controls and copies kept. */
|
|
118
|
+
function copyBack(mix, c, t, cut) {
|
|
119
|
+
mirror(mix, c);
|
|
108
120
|
c.backward = true;
|
|
109
|
-
|
|
121
|
+
// Copied as the frame's first probe began, so its own frame shows it.
|
|
122
|
+
const was = lastWithin(mix.hostLog, { seq: cut.seq, strict: false });
|
|
110
123
|
const host = mix.opts.host;
|
|
111
124
|
const then = was && typeof host === 'object' && host !== null
|
|
112
125
|
? Object.assign(Object.create(host), was.fields)
|
|
@@ -114,11 +127,11 @@ function copyBack(mix, c, t) {
|
|
|
114
127
|
if (was)
|
|
115
128
|
c.hostThen = was;
|
|
116
129
|
c.cued = [...mix.cued, ...mix.gone]
|
|
117
|
-
.filter((v) => v.
|
|
130
|
+
.filter((v) => v.cuedSeq <= cut.seq && !within(cut, v.doneSeq, true))
|
|
118
131
|
.sort((a, b) => a.id - b.id)
|
|
119
132
|
.map((v) => {
|
|
120
133
|
const log = v.log;
|
|
121
|
-
const copy = v.copy((subject) => recall(mix, v, subject, t),
|
|
134
|
+
const copy = v.copy((subject) => recall(mix, v, subject, t, cut), lastWithin(log, cut) ?? log[0]);
|
|
122
135
|
if (then !== undefined)
|
|
123
136
|
copy.setting.host = then;
|
|
124
137
|
return copy;
|
|
@@ -126,30 +139,39 @@ function copyBack(mix, c, t) {
|
|
|
126
139
|
ownBlends(c.cued);
|
|
127
140
|
if (mix.owners !== null)
|
|
128
141
|
relink(c.cued);
|
|
142
|
+
stateAt(c, t);
|
|
143
|
+
recount(c);
|
|
144
|
+
}
|
|
145
|
+
/** Sets each copied voice's state to what its clock and fade make it at `t`. */
|
|
146
|
+
export function stateAt(c, t) {
|
|
129
147
|
for (const copy of c.cued)
|
|
130
|
-
copy.state =
|
|
131
|
-
|
|
148
|
+
copy.state = copy.out
|
|
149
|
+
? 'fading'
|
|
150
|
+
: (copy.owner === null ? t : ownerReading(copy.owner, t)) < copy.start
|
|
132
151
|
? 'pending'
|
|
133
|
-
: copy.
|
|
134
|
-
? '
|
|
135
|
-
:
|
|
136
|
-
? 'frozen'
|
|
137
|
-
: 'live';
|
|
138
|
-
count(c);
|
|
152
|
+
: copy.freezesAfter && copy.elapsedAt(t) >= copy.span + copy.latest
|
|
153
|
+
? 'frozen'
|
|
154
|
+
: 'live';
|
|
139
155
|
}
|
|
140
|
-
/**
|
|
141
|
-
function
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
for (const voice of mix.cued)
|
|
146
|
-
index(mix, voice);
|
|
147
|
-
mix.loci = mix.cued.filter((v) => v.spec.locus !== undefined).length;
|
|
148
|
-
mix.anchored = mix.cued.filter((v) => v.spec.anchor !== undefined).length;
|
|
149
|
-
mix.steps.push(null, ++mix.version);
|
|
156
|
+
/** Gives a projection's copy what it reads of the live mix as it is, before its voices are copied. */
|
|
157
|
+
export function mirror(mix, c) {
|
|
158
|
+
c.pose = mix.pose;
|
|
159
|
+
c.wantsPose = mix.wantsPose;
|
|
160
|
+
c.reducedNow = mix.reducedNow;
|
|
150
161
|
}
|
|
151
162
|
/** A voice's record of a subject, the one it shares among those it does not reach included. */
|
|
152
163
|
function recordOf(mix, voice, subject) {
|
|
164
|
+
// A voice sharing one record has that for every subject a chain has asked it about, pointed at
|
|
165
|
+
// the subject for the caller to copy from.
|
|
166
|
+
if (voice.sharing) {
|
|
167
|
+
const slot = mix.chains.get(subject)?.slot ?? -1;
|
|
168
|
+
if (!sighted(voice, slot))
|
|
169
|
+
return undefined;
|
|
170
|
+
const all = voice.everyone;
|
|
171
|
+
open(voice, all, slot);
|
|
172
|
+
all.weight = weightAt(voice, slot);
|
|
173
|
+
return all;
|
|
174
|
+
}
|
|
153
175
|
const held = voice.subjects.get(subject);
|
|
154
176
|
if (held !== undefined || voice.unreachedBits === null)
|
|
155
177
|
return held;
|
|
@@ -157,12 +179,19 @@ function recordOf(mix, voice, subject) {
|
|
|
157
179
|
? voice.unreached
|
|
158
180
|
: undefined;
|
|
159
181
|
}
|
|
160
|
-
/**
|
|
161
|
-
|
|
182
|
+
/**
|
|
183
|
+
* A projection ahead starts each subject from where the live mix holds it, its `since` worked out
|
|
184
|
+
* again where it was still ahead, since the copy's clock may have moved it.
|
|
185
|
+
*/
|
|
186
|
+
function carry(mix, voice, subject, c, copy) {
|
|
162
187
|
const live = recordOf(mix, voice, subject);
|
|
163
188
|
if (live === undefined)
|
|
164
189
|
return undefined;
|
|
165
190
|
const h = copyHeld(voice, live);
|
|
191
|
+
if (h.reaches && !(h.since <= mix.now)) {
|
|
192
|
+
h.since = c.sinceOf(copy, h.delay);
|
|
193
|
+
h.shown = c.shownOf(copy, h.since);
|
|
194
|
+
}
|
|
166
195
|
h.from = h.stepped;
|
|
167
196
|
return h;
|
|
168
197
|
}
|
|
@@ -170,8 +199,8 @@ function carry(mix, voice, subject) {
|
|
|
170
199
|
* A projection back starts each subject from the latest copy kept at or before `t`, else from its
|
|
171
200
|
* voice's start with fresh state, which is exact for a voice stepped at a fixed interval.
|
|
172
201
|
*/
|
|
173
|
-
function recall(mix, voice, subject, t) {
|
|
174
|
-
const live =
|
|
202
|
+
export function recall(mix, voice, subject, t, cut) {
|
|
203
|
+
const live = voice.left?.at(subject, cut) ?? recordOf(mix, voice, subject);
|
|
175
204
|
if (live === undefined)
|
|
176
205
|
return undefined;
|
|
177
206
|
const snap = live.snaps && last(live.snaps, t, true);
|
|
@@ -182,33 +211,18 @@ function recall(mix, voice, subject, t) {
|
|
|
182
211
|
return h;
|
|
183
212
|
}
|
|
184
213
|
const stepped = live.since < t ? live.since : t;
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
ticks: 0,
|
|
198
|
-
probed: Number.NaN,
|
|
199
|
-
delta: null,
|
|
200
|
-
phase: 0,
|
|
201
|
-
seeks: 0,
|
|
202
|
-
kept: null,
|
|
203
|
-
voice,
|
|
204
|
-
next: null,
|
|
205
|
-
version: Number.NaN,
|
|
206
|
-
loci: null,
|
|
207
|
-
slot: -1,
|
|
208
|
-
from: stepped,
|
|
209
|
-
unknown: voice.spec.from === 'current',
|
|
210
|
-
replay: live.inputs,
|
|
211
|
-
};
|
|
214
|
+
const h = record(voice, live.reaches, live.reaches && voice.patch.state
|
|
215
|
+
? voice.patch.state(subject)
|
|
216
|
+
: undefined);
|
|
217
|
+
h.delay = live.delay;
|
|
218
|
+
h.since = live.since;
|
|
219
|
+
h.shown = live.shown;
|
|
220
|
+
h.stepped = stepped;
|
|
221
|
+
h.rebuilt = voice.rebuilds;
|
|
222
|
+
h.from = stepped;
|
|
223
|
+
h.unknown = voice.spec.from === 'current';
|
|
224
|
+
h.replay = live.inputs;
|
|
225
|
+
return h;
|
|
212
226
|
}
|
|
213
227
|
/** Per channel, the least sure voice that fed it this frame. */
|
|
214
228
|
function doubts(mix, subject) {
|
|
@@ -231,19 +245,23 @@ function doubts(mix, subject) {
|
|
|
231
245
|
for (const slot of voice.slots)
|
|
232
246
|
out[mix.names[slot]] = 'held';
|
|
233
247
|
}
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
continue;
|
|
238
|
-
if (held.weight <= 0)
|
|
239
|
-
continue;
|
|
248
|
+
const feed = (voice, held) => {
|
|
249
|
+
if (voice.state === 'done' || held.weight <= 0)
|
|
250
|
+
return;
|
|
240
251
|
const d = doubtOf(mix, voice, held);
|
|
241
252
|
for (const slot of voice.slots) {
|
|
242
253
|
const name = mix.names[slot];
|
|
243
254
|
if (rank[d] > rank[out[name]])
|
|
244
255
|
out[name] = d;
|
|
245
256
|
}
|
|
246
|
-
}
|
|
257
|
+
};
|
|
258
|
+
for (let held = mix.chain(subject); held !== null; held = held.next)
|
|
259
|
+
if (held.voice !== null)
|
|
260
|
+
feed(held.voice, held);
|
|
261
|
+
// A voice sharing one record is in no chain; its record holds the weight the fold just gave.
|
|
262
|
+
for (const voice of mix.sharers)
|
|
263
|
+
if (voice.everyone !== null && voice.state !== 'pending')
|
|
264
|
+
feed(voice, voice.everyone);
|
|
247
265
|
return out;
|
|
248
266
|
}
|
|
249
267
|
function doubtOf(mix, voice, held) {
|
package/dist/pull.d.ts
CHANGED
|
@@ -3,3 +3,5 @@ import type { Columns } from './types.js';
|
|
|
3
3
|
/** After a probe, keeps the pose a retarget from `'current'` reads as the subject's last. */
|
|
4
4
|
export declare function keep<I, O>(this: Mixer<I, O>, subject: I, pose: O, out: O | undefined): void;
|
|
5
5
|
export declare function pull<I, O>(mix: Mixer<I, O>, subjects: Iterable<I>, into: Columns<O>): void;
|
|
6
|
+
/** Forgets the array `pull` last read, whose heads a seek has made stale. */
|
|
7
|
+
export declare function unpull<I, O>(mix: Mixer<I, O>): void;
|
package/dist/pull.js
CHANGED
|
@@ -174,3 +174,11 @@ function columnsOf(mix, into) {
|
|
|
174
174
|
}
|
|
175
175
|
return columns;
|
|
176
176
|
}
|
|
177
|
+
/** Forgets the array `pull` last read, whose heads a seek has made stale. */
|
|
178
|
+
export function unpull(mix) {
|
|
179
|
+
mix.pulled = [];
|
|
180
|
+
mix.pulledHeads = [];
|
|
181
|
+
mix.pulledSlots = new Int32Array(0);
|
|
182
|
+
mix.pulledVersion = Number.NaN;
|
|
183
|
+
mix.pulledRelinks = -1;
|
|
184
|
+
}
|
package/dist/qualify.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { numericOf } from './channels.js';
|
|
2
2
|
import { flush } from './columns.js';
|
|
3
3
|
import { crowdable } from './crowd.js';
|
|
4
|
+
import { weighAt } from './everyone.js';
|
|
4
5
|
import { wake } from './fill.js';
|
|
5
6
|
import { Lane } from './lane.js';
|
|
6
7
|
import { bury, compactSparse, join, regroup, retouchRow, sameCrowds } from './rows.js';
|
|
@@ -169,6 +170,9 @@ export function requalify(lanes, version) {
|
|
|
169
170
|
const ch = {
|
|
170
171
|
name: host.names[slot],
|
|
171
172
|
op: n.op,
|
|
173
|
+
plain: n.op !== 'last' && n.op !== 'own' && n.turn === undefined,
|
|
174
|
+
turn: n.turn ?? 0,
|
|
175
|
+
channel,
|
|
172
176
|
scalar,
|
|
173
177
|
rest: typeof rest === 'number' ? rest : rest[0],
|
|
174
178
|
axes: n.axes,
|
|
@@ -332,8 +336,12 @@ function leave(lanes, lane) {
|
|
|
332
336
|
const rec = lane.records[p];
|
|
333
337
|
const slot = lane.list[p];
|
|
334
338
|
const w = done ? undefined : lanes.reported(lane, slot);
|
|
335
|
-
if (rec !== undefined && w !== undefined)
|
|
336
|
-
|
|
339
|
+
if (rec !== undefined && w !== undefined) {
|
|
340
|
+
if (lane.voice.sharing)
|
|
341
|
+
weighAt(lane.voice, slot, w, lanes.cap);
|
|
342
|
+
else
|
|
343
|
+
rec.weight = w;
|
|
344
|
+
}
|
|
337
345
|
if (rec !== undefined && lane.motion !== undefined)
|
|
338
346
|
settle(lanes, lane, p, slot, rec);
|
|
339
347
|
}
|
package/dist/reading.d.ts
CHANGED
|
@@ -20,4 +20,9 @@ export declare const reading: {
|
|
|
20
20
|
* One count for every mix: a change to a spring in one mix refills the lanes of all of them.
|
|
21
21
|
*/
|
|
22
22
|
moved: number;
|
|
23
|
+
/**
|
|
24
|
+
* How many changes an `input` signal has reported, in any mix, so a fill can tell an input moved
|
|
25
|
+
* since it read it. One count for every mix, as `moved` is.
|
|
26
|
+
*/
|
|
27
|
+
inputs: number;
|
|
23
28
|
};
|
package/dist/reading.js
CHANGED
|
@@ -20,4 +20,9 @@ export const reading = {
|
|
|
20
20
|
* One count for every mix: a change to a spring in one mix refills the lanes of all of them.
|
|
21
21
|
*/
|
|
22
22
|
moved: 0,
|
|
23
|
+
/**
|
|
24
|
+
* How many changes an `input` signal has reported, in any mix, so a fill can tell an input moved
|
|
25
|
+
* since it read it. One count for every mix, as `moved` is.
|
|
26
|
+
*/
|
|
27
|
+
inputs: 0,
|
|
23
28
|
};
|
package/dist/record.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Subject } from './voice.js';
|
|
2
|
+
/**
|
|
3
|
+
* A subject's record for `voice` holding every field one can come to hold, in one order, so every
|
|
4
|
+
* record in a mix shares one hidden class: a record built elsewhere, or given `base`, `snaps` or
|
|
5
|
+
* `from` later, took a class of its own, and a fold walking a chain of several kinds reads each
|
|
6
|
+
* field the slow way. The caller sets what it knows; nothing here takes a number, which a call V8
|
|
7
|
+
* does not inline would box.
|
|
8
|
+
*/
|
|
9
|
+
export declare function record(voice: object | null, reaches: boolean, state: unknown): Subject<unknown>;
|
package/dist/record.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A subject's record for `voice` holding every field one can come to hold, in one order, so every
|
|
3
|
+
* record in a mix shares one hidden class: a record built elsewhere, or given `base`, `snaps` or
|
|
4
|
+
* `from` later, took a class of its own, and a fold walking a chain of several kinds reads each
|
|
5
|
+
* field the slow way. The caller sets what it knows; nothing here takes a number, which a call V8
|
|
6
|
+
* does not inline would box.
|
|
7
|
+
*/
|
|
8
|
+
export function record(voice, reaches, state) {
|
|
9
|
+
return {
|
|
10
|
+
reaches,
|
|
11
|
+
delay: 0,
|
|
12
|
+
since: 0,
|
|
13
|
+
shown: 0,
|
|
14
|
+
weight: 0,
|
|
15
|
+
rested: false,
|
|
16
|
+
bands: null,
|
|
17
|
+
state,
|
|
18
|
+
stepped: 0,
|
|
19
|
+
ticks: 0,
|
|
20
|
+
probed: Number.NaN,
|
|
21
|
+
delta: null,
|
|
22
|
+
phase: 0,
|
|
23
|
+
seeks: 0,
|
|
24
|
+
rebuilt: 0,
|
|
25
|
+
base: undefined,
|
|
26
|
+
slope: undefined,
|
|
27
|
+
kept: null,
|
|
28
|
+
unkept: undefined,
|
|
29
|
+
snaps: undefined,
|
|
30
|
+
from: undefined,
|
|
31
|
+
unknown: undefined,
|
|
32
|
+
inputs: undefined,
|
|
33
|
+
replay: undefined,
|
|
34
|
+
voice,
|
|
35
|
+
next: null,
|
|
36
|
+
version: Number.NaN,
|
|
37
|
+
loci: null,
|
|
38
|
+
slot: -1,
|
|
39
|
+
run: -1,
|
|
40
|
+
freed: -1,
|
|
41
|
+
};
|
|
42
|
+
}
|
package/dist/relink.d.ts
CHANGED
|
@@ -3,12 +3,16 @@ import type { Subject } from './voice.js';
|
|
|
3
3
|
interface Ordered {
|
|
4
4
|
readonly id: number;
|
|
5
5
|
}
|
|
6
|
+
/** What `patch` asks a mix: a voice's record for a subject where a full relink would link it. */
|
|
7
|
+
interface Linker<V, I> {
|
|
8
|
+
linkable(voice: V, subject: I, slot: number): Subject<unknown> | null;
|
|
9
|
+
}
|
|
6
10
|
/**
|
|
7
11
|
* The voices over every subject behind each step of a mix's `version`, so a chain linked a few
|
|
8
12
|
* steps back can be brought up to date by those voices alone; null for a step that changed
|
|
9
13
|
* anything else, after which every chain links afresh.
|
|
10
14
|
*/
|
|
11
|
-
export declare class Steps<V extends Ordered> {
|
|
15
|
+
export declare class Steps<V extends Ordered, I> {
|
|
12
16
|
private list;
|
|
13
17
|
/** The version before `list[0]`. */
|
|
14
18
|
private from;
|
|
@@ -16,10 +20,12 @@ export declare class Steps<V extends Ordered> {
|
|
|
16
20
|
push(voice: V | null, version: number): void;
|
|
17
21
|
/**
|
|
18
22
|
* A chain linked at `version`, its first record `head` (null where it holds none), with each voice
|
|
19
|
-
* stepped since taken off it or put on at its place in voice order by `
|
|
20
|
-
* voice's record where a full relink would link it and null where it would not. undefined
|
|
21
|
-
* the steps since do not say, so the chain must link afresh.
|
|
23
|
+
* stepped since taken off it or put on at its place in voice order by `mix.linkable`, which gives
|
|
24
|
+
* the voice's record where a full relink would link it and null where it would not. undefined
|
|
25
|
+
* where the steps since do not say, so the chain must link afresh. It takes the mix and the
|
|
26
|
+
* subject, not a function closed over them: a closure and its context a subject cost the `swap`
|
|
27
|
+
* row about 138 B each (bench/allocs.mjs, 2026-10-09).
|
|
22
28
|
*/
|
|
23
|
-
patch(head: Subject<unknown> | null, version: number,
|
|
29
|
+
patch(head: Subject<unknown> | null, version: number, mix: Linker<V, I>, subject: I, slot: number): Subject<unknown> | null | undefined;
|
|
24
30
|
}
|
|
25
31
|
export {};
|
package/dist/relink.js
CHANGED
|
@@ -21,11 +21,13 @@ export class Steps {
|
|
|
21
21
|
}
|
|
22
22
|
/**
|
|
23
23
|
* A chain linked at `version`, its first record `head` (null where it holds none), with each voice
|
|
24
|
-
* stepped since taken off it or put on at its place in voice order by `
|
|
25
|
-
* voice's record where a full relink would link it and null where it would not. undefined
|
|
26
|
-
* the steps since do not say, so the chain must link afresh.
|
|
24
|
+
* stepped since taken off it or put on at its place in voice order by `mix.linkable`, which gives
|
|
25
|
+
* the voice's record where a full relink would link it and null where it would not. undefined
|
|
26
|
+
* where the steps since do not say, so the chain must link afresh. It takes the mix and the
|
|
27
|
+
* subject, not a function closed over them: a closure and its context a subject cost the `swap`
|
|
28
|
+
* row about 138 B each (bench/allocs.mjs, 2026-10-09).
|
|
27
29
|
*/
|
|
28
|
-
patch(head, version,
|
|
30
|
+
patch(head, version, mix, subject, slot) {
|
|
29
31
|
if (!(version >= this.from))
|
|
30
32
|
return undefined;
|
|
31
33
|
const list = this.list;
|
|
@@ -40,7 +42,7 @@ export class Steps {
|
|
|
40
42
|
at = at.next;
|
|
41
43
|
}
|
|
42
44
|
const here = at !== null && at.voice === voice ? at : null;
|
|
43
|
-
const want =
|
|
45
|
+
const want = mix.linkable(voice, subject, slot);
|
|
44
46
|
if (want === here)
|
|
45
47
|
continue;
|
|
46
48
|
const after = here === null ? at : here.next;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { Leavings } from './leavings.js';
|
|
2
|
+
import { Store } from './store.js';
|
|
3
|
+
import type { Cut } from './transport.js';
|
|
4
|
+
import type { Subject } from './voice.js';
|
|
5
|
+
/**
|
|
6
|
+
* A voice's records once a seek has gone back: each subject's is put back from the record it had,
|
|
7
|
+
* the first time it is asked for after a seek, since the subjects a store holds cannot be listed.
|
|
8
|
+
* It is one store however many seeks there are: by subject it keeps how many seeks its record has
|
|
9
|
+
* been put back for, and a record behind is put back to the earliest moment sought since, as a
|
|
10
|
+
* seek back cuts what came after it and a later seek ahead of that moment must not read past it.
|
|
11
|
+
*/
|
|
12
|
+
export declare class Restored<I> extends Store<I, Subject<unknown>> {
|
|
13
|
+
private readonly own;
|
|
14
|
+
private readonly make;
|
|
15
|
+
/** Seeks back so far. */
|
|
16
|
+
private n;
|
|
17
|
+
/** By subject, the `n` its record was last put back, written or deleted at; none reads as 0. */
|
|
18
|
+
private readonly at;
|
|
19
|
+
/**
|
|
20
|
+
* Each seek back sought to an earlier moment than every one after it, oldest first: the earliest
|
|
21
|
+
* moment sought since seek `n` is that of the first here at or after it.
|
|
22
|
+
*/
|
|
23
|
+
private readonly least;
|
|
24
|
+
/** By seek, the records subjects left with after its moment, and where it cut; null until one has any. */
|
|
25
|
+
private futures;
|
|
26
|
+
/** The latest seek's moment. */
|
|
27
|
+
private t;
|
|
28
|
+
/**
|
|
29
|
+
* `own` holds the records as they stood before the first seek. `make` puts a record back as it
|
|
30
|
+
* stood at `t`, undefined where it had none then; `top` is the latest seek's moment.
|
|
31
|
+
*/
|
|
32
|
+
constructor(own: Store<I, Subject<unknown>>, make: (live: Subject<unknown>, t: number, top: number) => Subject<unknown> | undefined);
|
|
33
|
+
/** A seek back to `t`: `left` has the records subjects left with after `cut`, null for none. */
|
|
34
|
+
back(t: number, cut: Cut, left: Leavings<I> | null): void;
|
|
35
|
+
/** The earliest moment sought by seek `from` or any after it. */
|
|
36
|
+
private since;
|
|
37
|
+
get(key: I): Subject<unknown> | undefined;
|
|
38
|
+
set(key: I, value: Subject<unknown>): void;
|
|
39
|
+
delete(key: I): void;
|
|
40
|
+
clear(): void;
|
|
41
|
+
}
|
package/dist/restored.js
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { Store } from './store.js';
|
|
2
|
+
/**
|
|
3
|
+
* A voice's records once a seek has gone back: each subject's is put back from the record it had,
|
|
4
|
+
* 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 how many seeks its record has
|
|
6
|
+
* been put back for, and a record behind is put back to the earliest moment sought since, as a
|
|
7
|
+
* seek back cuts what came after it and a later seek ahead of that moment must not read past it.
|
|
8
|
+
*/
|
|
9
|
+
export class Restored extends Store {
|
|
10
|
+
own;
|
|
11
|
+
make;
|
|
12
|
+
/** Seeks back so far. */
|
|
13
|
+
n = 0;
|
|
14
|
+
/** By subject, the `n` its record was last put back, written or deleted at; none reads as 0. */
|
|
15
|
+
at = new Store();
|
|
16
|
+
/**
|
|
17
|
+
* Each seek back sought to an earlier moment than every one after it, oldest first: the earliest
|
|
18
|
+
* moment sought since seek `n` is that of the first here at or after it.
|
|
19
|
+
*/
|
|
20
|
+
least = [];
|
|
21
|
+
/** By seek, the records subjects left with after its moment, and where it cut; null until one has any. */
|
|
22
|
+
futures = null;
|
|
23
|
+
/** The latest seek's moment. */
|
|
24
|
+
t = Number.NaN;
|
|
25
|
+
/**
|
|
26
|
+
* `own` holds the records as they stood before the first seek. `make` puts a record back as it
|
|
27
|
+
* stood at `t`, undefined where it had none then; `top` is the latest seek's moment.
|
|
28
|
+
*/
|
|
29
|
+
constructor(own, make) {
|
|
30
|
+
super();
|
|
31
|
+
this.own = own;
|
|
32
|
+
this.make = make;
|
|
33
|
+
}
|
|
34
|
+
/** A seek back to `t`: `left` has the records subjects left with after `cut`, null for none. */
|
|
35
|
+
back(t, cut, left) {
|
|
36
|
+
const n = ++this.n;
|
|
37
|
+
const least = this.least;
|
|
38
|
+
while (least.length > 0 && least[least.length - 1].t >= t)
|
|
39
|
+
least.pop();
|
|
40
|
+
least.push({ n, t });
|
|
41
|
+
this.t = t;
|
|
42
|
+
if (left !== null) {
|
|
43
|
+
this.futures ??= new Map();
|
|
44
|
+
this.futures.set(n, { left, cut });
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/** The earliest moment sought by seek `from` or any after it. */
|
|
48
|
+
since(from) {
|
|
49
|
+
const least = this.least;
|
|
50
|
+
let lo = 0;
|
|
51
|
+
let hi = least.length - 1;
|
|
52
|
+
while (lo < hi) {
|
|
53
|
+
const mid = (lo + hi) >> 1;
|
|
54
|
+
if (least[mid].n >= from)
|
|
55
|
+
hi = mid;
|
|
56
|
+
else
|
|
57
|
+
lo = mid + 1;
|
|
58
|
+
}
|
|
59
|
+
return least[lo].t;
|
|
60
|
+
}
|
|
61
|
+
get(key) {
|
|
62
|
+
const at = this.at.get(key) ?? 0;
|
|
63
|
+
if (at === this.n)
|
|
64
|
+
return this.own.get(key);
|
|
65
|
+
this.at.set(key, this.n);
|
|
66
|
+
// A subject that left after the moment the next seek sought had the record it left with,
|
|
67
|
+
// whatever it has now. Leaving is a write, so no later seek's leavings can hold it.
|
|
68
|
+
const future = this.futures?.get(at + 1);
|
|
69
|
+
const live = future?.left.at(key, future.cut) ?? this.own.get(key);
|
|
70
|
+
if (live === undefined)
|
|
71
|
+
return undefined;
|
|
72
|
+
const made = this.make(live, this.since(at + 1), this.t);
|
|
73
|
+
if (made === undefined)
|
|
74
|
+
this.own.delete(key);
|
|
75
|
+
else
|
|
76
|
+
this.own.set(key, made);
|
|
77
|
+
return made;
|
|
78
|
+
}
|
|
79
|
+
set(key, value) {
|
|
80
|
+
this.own.set(key, value);
|
|
81
|
+
this.at.set(key, this.n);
|
|
82
|
+
}
|
|
83
|
+
delete(key) {
|
|
84
|
+
this.own.delete(key);
|
|
85
|
+
this.at.set(key, this.n);
|
|
86
|
+
}
|
|
87
|
+
clear() {
|
|
88
|
+
this.own.clear();
|
|
89
|
+
this.at.clear();
|
|
90
|
+
}
|
|
91
|
+
}
|
package/dist/revive.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Mixer } from './mixer.js';
|
|
2
2
|
import type { Run } from './motions.js';
|
|
3
|
+
import type { Past } from './origin.js';
|
|
3
4
|
import { type PackedRecord } from './pack.js';
|
|
4
5
|
import { type Key } from './paging.js';
|
|
5
6
|
import type { Described } from './types.js';
|
|
@@ -22,8 +23,13 @@ interface PackedVoice {
|
|
|
22
23
|
outAt: number;
|
|
23
24
|
outOver: number;
|
|
24
25
|
outSet: boolean;
|
|
26
|
+
rebuilds: number;
|
|
27
|
+
past: number;
|
|
28
|
+
clocks: Past[] | null;
|
|
25
29
|
cuedAt: number;
|
|
30
|
+
cuedSeq: number;
|
|
26
31
|
doneAt: number;
|
|
32
|
+
doneSeq: number;
|
|
27
33
|
opened: number;
|
|
28
34
|
pinned: number;
|
|
29
35
|
latest: number;
|
|
@@ -32,11 +38,12 @@ interface PackedVoice {
|
|
|
32
38
|
log: Controls[] | null;
|
|
33
39
|
played: boolean | undefined;
|
|
34
40
|
playedAt: number;
|
|
41
|
+
playedSeq: number;
|
|
35
42
|
records: [Key, PackedRecord][];
|
|
36
43
|
runs: [Key, Run][];
|
|
37
|
-
left: [Key, number, PackedRecord][];
|
|
38
|
-
parts: [Key, number, number][];
|
|
39
|
-
parted: [Key, number][];
|
|
44
|
+
left: [Key, number, number, PackedRecord, boolean][];
|
|
45
|
+
parts: [Key, number, number, number][];
|
|
46
|
+
parted: [Key, number, number][];
|
|
40
47
|
}
|
|
41
48
|
/**
|
|
42
49
|
* Voices a history store holds whose handles something still holds, by id: a seek that revives
|