@msb235/blits 0.9.0 → 0.11.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 +167 -0
- package/README.md +16 -5
- package/dist/blend.js +1 -5
- package/dist/book.d.ts +8 -5
- package/dist/book.js +47 -19
- package/dist/chain.d.ts +11 -0
- package/dist/chain.js +24 -2
- package/dist/clock.d.ts +14 -0
- package/dist/clock.js +9 -1
- package/dist/columns.d.ts +4 -5
- package/dist/columns.js +11 -5
- package/dist/crowd.d.ts +1 -1
- package/dist/crowd.js +4 -1
- package/dist/cue.js +52 -5
- package/dist/departed.d.ts +3 -2
- package/dist/departed.js +7 -4
- package/dist/due.d.ts +4 -0
- package/dist/due.js +16 -3
- package/dist/fade.d.ts +12 -5
- package/dist/fade.js +55 -17
- package/dist/fold.d.ts +1 -1
- package/dist/fold.js +53 -6
- package/dist/frames.d.ts +2 -0
- package/dist/frames.js +17 -0
- package/dist/handle.d.ts +5 -2
- package/dist/handle.js +20 -12
- package/dist/held.d.ts +8 -0
- package/dist/held.js +37 -6
- package/dist/history.d.ts +6 -0
- package/dist/history.js +15 -5
- package/dist/hosts.d.ts +1 -1
- package/dist/hosts.js +18 -6
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/keyfill.js +3 -1
- package/dist/lane.d.ts +1 -1
- package/dist/lane.js +3 -1
- package/dist/lanes.d.ts +8 -0
- package/dist/lanes.js +21 -4
- package/dist/lean.d.ts +15 -0
- package/dist/lean.js +23 -0
- package/dist/leavings.d.ts +6 -1
- package/dist/leavings.js +16 -2
- package/dist/marks.d.ts +7 -3
- package/dist/marks.js +102 -27
- package/dist/meet.js +1 -0
- package/dist/mixer.d.ts +23 -7
- package/dist/mixer.js +73 -29
- package/dist/motion.d.ts +4 -1
- package/dist/motion.js +16 -2
- package/dist/motions.d.ts +4 -0
- package/dist/motions.js +45 -11
- package/dist/move.js +28 -10
- package/dist/owner.d.ts +18 -2
- package/dist/owner.js +38 -2
- package/dist/pack.d.ts +1 -0
- package/dist/pack.js +3 -0
- package/dist/placed.d.ts +19 -0
- package/dist/placed.js +54 -0
- package/dist/project.d.ts +5 -2
- package/dist/project.js +236 -32
- package/dist/pull.js +37 -8
- package/dist/qualify.js +8 -2
- package/dist/reading.d.ts +6 -0
- package/dist/reading.js +6 -0
- package/dist/record.js +1 -0
- package/dist/restored.d.ts +7 -4
- package/dist/restored.js +13 -10
- package/dist/revive.d.ts +5 -3
- package/dist/revive.js +10 -3
- package/dist/rows.js +15 -5
- package/dist/seek.d.ts +7 -2
- package/dist/seek.js +90 -35
- package/dist/shared.d.ts +5 -0
- package/dist/shared.js +5 -0
- package/dist/signals.d.ts +10 -0
- package/dist/signals.js +17 -0
- package/dist/store.d.ts +11 -0
- package/dist/store.js +19 -0
- package/dist/strays.js +50 -2
- package/dist/tape.d.ts +29 -4
- package/dist/tape.js +49 -11
- package/dist/transport.d.ts +11 -2
- package/dist/transport.js +20 -9
- package/dist/types/mix.d.ts +42 -15
- package/dist/types/patch.d.ts +5 -1
- package/dist/types/place.d.ts +25 -11
- package/dist/types/transport.d.ts +12 -3
- package/dist/types/voice.d.ts +62 -4
- package/dist/unpage.d.ts +6 -1
- package/dist/unpage.js +34 -2
- package/dist/voice.d.ts +57 -11
- package/dist/voice.js +43 -3
- package/docs/schema.html +75 -40
- package/package.json +7 -2
package/dist/project.js
CHANGED
|
@@ -1,37 +1,52 @@
|
|
|
1
1
|
import { ownBlends } from './blend.js';
|
|
2
|
+
import { heldAt } from './chain.js';
|
|
3
|
+
import { stepOf } from './clock.js';
|
|
2
4
|
import { open, sighted, weightAt } from './everyone.js';
|
|
5
|
+
import { fadesAt } from './fade.js';
|
|
6
|
+
import { meet, origin } from './held.js';
|
|
3
7
|
import { copyHeld, last, lastWithin } from './history.js';
|
|
8
|
+
import { markOf, voiceMarks } from './marks.js';
|
|
4
9
|
import { Mixer } from './mixer.js';
|
|
5
10
|
import { move } from './move.js';
|
|
6
11
|
import { heldByInput, ownerReading, relink } from './owner.js';
|
|
7
|
-
import { pin } from './place.js';
|
|
12
|
+
import { pin, place } from './place.js';
|
|
8
13
|
import { reading } from './reading.js';
|
|
9
14
|
import { record } from './record.js';
|
|
10
15
|
import { recount } from './roster.js';
|
|
11
16
|
import { copyStanding, stands } from './standing.js';
|
|
12
|
-
import { hostAt } from './tape.js';
|
|
17
|
+
import { hostAhead, hostAt, replay } from './tape.js';
|
|
13
18
|
import { Transport, within } from './transport.js';
|
|
14
|
-
import { cover } from './unpage.js';
|
|
19
|
+
import { cover, coverFrame } from './unpage.js';
|
|
15
20
|
import { unreached } from './unreached.js';
|
|
16
21
|
/**
|
|
17
22
|
* Every mix on a transport read at mix time `t`, through copies of them on a transport of their
|
|
18
|
-
* own, moved together so an anchor across mixes answers as it would live.
|
|
23
|
+
* own, moved together so an anchor across mixes answers as it would live. Ahead, they stop at each
|
|
24
|
+
* known event on the way, and a subject asked about is read at each before `t`.
|
|
19
25
|
*/
|
|
20
|
-
export function projectAll(transport,
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
26
|
+
export function projectAll(transport, at) {
|
|
27
|
+
const kept = transport.history !== undefined;
|
|
28
|
+
let t;
|
|
29
|
+
let numbered;
|
|
30
|
+
if (typeof at === 'number')
|
|
31
|
+
t = at;
|
|
32
|
+
else {
|
|
33
|
+
if (!kept)
|
|
34
|
+
throw new Error('blits: reading a frame back needs a mix made with history');
|
|
35
|
+
numbered = coverFrame(transport, at.frame);
|
|
36
|
+
t = numbered.at;
|
|
37
|
+
}
|
|
38
|
+
// The frame being played reads as the mix's own time does; any other is behind, whatever its time.
|
|
39
|
+
const ahead = numbered !== undefined
|
|
40
|
+
? numbered.seq === transport.seq
|
|
41
|
+
: Number.isNaN(transport.now) || t >= transport.now;
|
|
24
42
|
const pace = transport.pace;
|
|
25
|
-
const next = stamp === undefined || pace === null ? stamp : pace.reading(stamp);
|
|
26
|
-
if (next !== undefined && next <= t)
|
|
27
|
-
throw new Error(`blits: the tape holds calls at ${next} that a read ahead to ${t} cannot play; seek there to see them`);
|
|
28
43
|
// Behind, the last frame at or before `t`, read as it showed when `t` is that frame's own time.
|
|
29
44
|
let cut = { seq: transport.seq, strict: false };
|
|
30
|
-
let u = hostAt(transport, t);
|
|
31
|
-
const kept = transport.history !== undefined;
|
|
45
|
+
let u = ahead ? hostAhead(transport, t) : hostAt(transport, t);
|
|
32
46
|
if (!ahead && kept) {
|
|
33
|
-
|
|
34
|
-
|
|
47
|
+
if (numbered === undefined)
|
|
48
|
+
cover(transport, t);
|
|
49
|
+
const frame = numbered ?? transport.frames.at(t);
|
|
35
50
|
if (frame !== undefined) {
|
|
36
51
|
cut = { seq: frame.seq, strict: frame.at === t };
|
|
37
52
|
if (frame.at === t)
|
|
@@ -57,7 +72,7 @@ export function projectAll(transport, t) {
|
|
|
57
72
|
history: undefined,
|
|
58
73
|
lanes: false,
|
|
59
74
|
transport: c,
|
|
60
|
-
});
|
|
75
|
+
}, null);
|
|
61
76
|
copy.slot = mix.slot;
|
|
62
77
|
copy.projecting = true;
|
|
63
78
|
copies.set(mix, copy);
|
|
@@ -70,28 +85,50 @@ export function projectAll(transport, t) {
|
|
|
70
85
|
}
|
|
71
86
|
// A read at the mix's own time lands nothing, as a probe does not: what the host has done since
|
|
72
87
|
// the last sync shows at the next one.
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
88
|
+
let stood = c;
|
|
89
|
+
let earlier = null;
|
|
90
|
+
if (ahead && t !== transport.now) {
|
|
91
|
+
earlier = new Map();
|
|
92
|
+
stood = playAhead(transport, c, copies, t, earlier);
|
|
93
|
+
}
|
|
76
94
|
const read = (f) => {
|
|
77
95
|
reading.live = false;
|
|
96
|
+
reading.ahead = stood.ahead.size > 0 ? stood.ahead : null;
|
|
78
97
|
try {
|
|
79
98
|
return f();
|
|
80
99
|
}
|
|
81
100
|
finally {
|
|
82
101
|
reading.live = true;
|
|
102
|
+
reading.ahead = null;
|
|
83
103
|
}
|
|
84
104
|
};
|
|
85
105
|
return {
|
|
86
106
|
timestamp: t,
|
|
87
107
|
of(mix) {
|
|
88
|
-
const
|
|
108
|
+
const key = mix;
|
|
109
|
+
const copy = copies.get(key);
|
|
89
110
|
if (copy === undefined)
|
|
90
111
|
throw new Error('blits: that mix is not on this transport');
|
|
112
|
+
const stages = earlier?.get(key);
|
|
113
|
+
// A subject asked about is read at every stop first, in order, so the state it carries
|
|
114
|
+
// crosses each event as a frame there would carry it.
|
|
115
|
+
const warm = stages === undefined
|
|
116
|
+
? null
|
|
117
|
+
: (subject) => {
|
|
118
|
+
if (stages.met.has(subject))
|
|
119
|
+
return;
|
|
120
|
+
stages.met.add(subject);
|
|
121
|
+
for (const stage of stages.at)
|
|
122
|
+
stage.fold(subject);
|
|
123
|
+
};
|
|
91
124
|
return {
|
|
92
125
|
timestamp: t,
|
|
93
|
-
probe: (subject, out) => read(() =>
|
|
126
|
+
probe: (subject, out) => read(() => {
|
|
127
|
+
warm?.(subject);
|
|
128
|
+
return copy.fold(subject, out);
|
|
129
|
+
}),
|
|
94
130
|
assess: (subject) => read(() => {
|
|
131
|
+
warm?.(subject);
|
|
95
132
|
copy.fold(subject);
|
|
96
133
|
return doubts(copy, subject);
|
|
97
134
|
}),
|
|
@@ -99,7 +136,150 @@ export function projectAll(transport, t) {
|
|
|
99
136
|
},
|
|
100
137
|
};
|
|
101
138
|
}
|
|
102
|
-
/**
|
|
139
|
+
/**
|
|
140
|
+
* Moves a read ahead's copies to mix time `t`, making the calls the tape holds on the way as a seek
|
|
141
|
+
* there makes them on the mixes. While a voice could carry state across it, it stops at each known
|
|
142
|
+
* event: a call, and every mark of a voice or announced. At each stop the copies are kept for
|
|
143
|
+
* subjects to be read there and copied on, so `copies` ends holding those at `t`. Answers the
|
|
144
|
+
* transport they stand on, and puts the tape back where it stood.
|
|
145
|
+
*/
|
|
146
|
+
function playAhead(transport, c, copies, t, earlier) {
|
|
147
|
+
const tape = transport.tape;
|
|
148
|
+
const depth = tape?.undoDepth() ?? 0;
|
|
149
|
+
let played = false;
|
|
150
|
+
try {
|
|
151
|
+
for (;;) {
|
|
152
|
+
const e = nextEvent(tape, c, t);
|
|
153
|
+
if (!(e < t))
|
|
154
|
+
break;
|
|
155
|
+
played = playTo(transport, c, copies, e) || played;
|
|
156
|
+
if (!carries(c))
|
|
157
|
+
continue;
|
|
158
|
+
// Placed now, as the next move would place them, while a target that has just left is here.
|
|
159
|
+
for (const m of c.members)
|
|
160
|
+
if (m.anchored > 0)
|
|
161
|
+
place(m);
|
|
162
|
+
const next = restage(c);
|
|
163
|
+
for (const [mix, copy] of copies) {
|
|
164
|
+
const on = new Mixer(copy.kit, { ...copy.opts, transport: next }, null);
|
|
165
|
+
on.slot = copy.slot;
|
|
166
|
+
on.projecting = true;
|
|
167
|
+
copyAhead(copy, on);
|
|
168
|
+
const stages = earlier.get(mix);
|
|
169
|
+
if (stages === undefined)
|
|
170
|
+
earlier.set(mix, { at: [copy], met: new Set() });
|
|
171
|
+
else
|
|
172
|
+
stages.at.push(copy);
|
|
173
|
+
copies.set(mix, on);
|
|
174
|
+
}
|
|
175
|
+
c = next;
|
|
176
|
+
}
|
|
177
|
+
played = playTo(transport, c, copies, t) || played;
|
|
178
|
+
}
|
|
179
|
+
finally {
|
|
180
|
+
if (played)
|
|
181
|
+
tape?.goto(depth);
|
|
182
|
+
}
|
|
183
|
+
return c;
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Whether a voice on a read ahead's copies could carry state from one known event to the next: a
|
|
187
|
+
* patch that steps, or a weight given by a signal, which may keep state. Where none could, a read
|
|
188
|
+
* crosses the events in one go, as fast as one with none.
|
|
189
|
+
*/
|
|
190
|
+
function carries(c) {
|
|
191
|
+
for (const m of c.members)
|
|
192
|
+
for (const v of m.cued)
|
|
193
|
+
if (v.state !== 'done' && (v.patch.step !== undefined || typeof v.spec.weight === 'function'))
|
|
194
|
+
return true;
|
|
195
|
+
return false;
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* The earliest known event a read ahead's copies have yet to reach before `t`, mix time: the next
|
|
199
|
+
* call the tape holds, and while a voice could carry state across one, a mark of a voice or one
|
|
200
|
+
* announced. Infinity where there is none.
|
|
201
|
+
*/
|
|
202
|
+
function nextEvent(tape, c, t) {
|
|
203
|
+
const now = c.now;
|
|
204
|
+
let e = Number.POSITIVE_INFINITY;
|
|
205
|
+
const call = tape?.timestampAt(tape.undoDepth());
|
|
206
|
+
if (call !== undefined)
|
|
207
|
+
e = c.pace === null ? call : c.pace.reading(call);
|
|
208
|
+
if (!carries(c))
|
|
209
|
+
return e < t ? e : Number.POSITIVE_INFINITY;
|
|
210
|
+
for (const m of c.members)
|
|
211
|
+
for (const v of m.cued) {
|
|
212
|
+
if (v.state === 'done')
|
|
213
|
+
continue;
|
|
214
|
+
for (const mark of voiceMarks) {
|
|
215
|
+
const at = markOf(m, v, mark);
|
|
216
|
+
if (at !== undefined && at > now && at < e)
|
|
217
|
+
e = at;
|
|
218
|
+
}
|
|
219
|
+
const inner = v.spec.marks;
|
|
220
|
+
if (inner !== undefined)
|
|
221
|
+
for (const mark of Object.keys(inner)) {
|
|
222
|
+
const at = markOf(m, v, mark);
|
|
223
|
+
if (at !== undefined && at > now && at < e)
|
|
224
|
+
e = at;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
for (const a of c.announced) {
|
|
228
|
+
const at = c.pace === null ? a.at : c.pace.at(a.at, c.u);
|
|
229
|
+
if (at > now && at < e)
|
|
230
|
+
e = at;
|
|
231
|
+
}
|
|
232
|
+
return e < t ? e : Number.POSITIVE_INFINITY;
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Makes the calls the tape holds up to mix time `to` on a read ahead's copies and moves them
|
|
236
|
+
* there. Answers whether it moved the tape.
|
|
237
|
+
*/
|
|
238
|
+
function playTo(transport, c, copies, to) {
|
|
239
|
+
const tape = transport.tape;
|
|
240
|
+
const due = (at) => (c.pace === null ? at : c.pace.reading(at)) <= to;
|
|
241
|
+
const next = tape?.timestampAt(tape.undoDepth());
|
|
242
|
+
c.u = hostAhead(c, to);
|
|
243
|
+
let played = false;
|
|
244
|
+
if (next !== undefined && due(next)) {
|
|
245
|
+
// A call may write what a copy shares with the voice it was copied from.
|
|
246
|
+
for (const copy of c.members)
|
|
247
|
+
for (const v of copy.cued) {
|
|
248
|
+
if (v.parts !== null)
|
|
249
|
+
v.parts = new Map(v.parts);
|
|
250
|
+
if (v.parted !== null)
|
|
251
|
+
v.parted = new Map(v.parted);
|
|
252
|
+
}
|
|
253
|
+
played = true;
|
|
254
|
+
const last = replay(transport, due, c, {
|
|
255
|
+
transport: () => c,
|
|
256
|
+
mix: (of) => copies.get(of),
|
|
257
|
+
});
|
|
258
|
+
c.u = Math.max(last, hostAhead(c, to));
|
|
259
|
+
}
|
|
260
|
+
// A call played at `to` itself moved the copies there before it, as a sync does.
|
|
261
|
+
if (!(c.now >= to))
|
|
262
|
+
for (const copy of c.members)
|
|
263
|
+
move(copy, to);
|
|
264
|
+
return played;
|
|
265
|
+
}
|
|
266
|
+
/** A transport for a read ahead's copies to go on from where those on `c` stand. */
|
|
267
|
+
function restage(c) {
|
|
268
|
+
const next = new Transport(undefined, true);
|
|
269
|
+
next.offset = c.offset;
|
|
270
|
+
next.u = c.u;
|
|
271
|
+
next.nextId = c.nextId;
|
|
272
|
+
next.now = c.now;
|
|
273
|
+
next.pace = c.pace === null ? null : c.pace.until(Number.POSITIVE_INFINITY);
|
|
274
|
+
next.announced = c.announced.map((a) => ({ ...a }));
|
|
275
|
+
// The retargets and pushes the tape made carry their times, so every stop shares one list.
|
|
276
|
+
next.ahead = c.ahead;
|
|
277
|
+
return next;
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* A mix's copy for a read ahead: its voices as they stand, each subject carried from the live one,
|
|
281
|
+
* or from the copy at the known event before.
|
|
282
|
+
*/
|
|
103
283
|
function copyAhead(mix, c) {
|
|
104
284
|
mirror(mix, c);
|
|
105
285
|
c.cued = mix.cued
|
|
@@ -112,7 +292,8 @@ function copyAhead(mix, c) {
|
|
|
112
292
|
return copy;
|
|
113
293
|
});
|
|
114
294
|
ownBlends(c.cued);
|
|
115
|
-
|
|
295
|
+
// A copy keeps no list of owners, so the one before says only by its voices.
|
|
296
|
+
if (mix.owners !== null || (mix.projecting && c.cued.some((v) => v.holding !== null)))
|
|
116
297
|
relink(c.cued);
|
|
117
298
|
recount(c);
|
|
118
299
|
}
|
|
@@ -136,11 +317,26 @@ function copyBack(mix, c, t, cut) {
|
|
|
136
317
|
const copy = v.copy((subject) => recall(mix, v, subject, t, cut, c, copy), lastWithin(log, cut) ?? log[0]);
|
|
137
318
|
if (then !== undefined)
|
|
138
319
|
copy.setting.host = then;
|
|
320
|
+
// Its own, which a fade the copy plays out writes to.
|
|
321
|
+
if (v.parts !== null)
|
|
322
|
+
copy.parts = new Map(v.parts);
|
|
323
|
+
if (v.parted !== null)
|
|
324
|
+
copy.parted = new Map(v.parted);
|
|
325
|
+
if (v.left !== null)
|
|
326
|
+
fadesAt(copy, v.left.after(cut), cut.seq);
|
|
327
|
+
if (copy.parted !== null)
|
|
328
|
+
for (const [subject, p] of copy.parted) {
|
|
329
|
+
// A ramp that has ended since was still running at `t`.
|
|
330
|
+
if (p.ramp === undefined || !(t < p.at))
|
|
331
|
+
continue;
|
|
332
|
+
copy.parts ??= new Map();
|
|
333
|
+
copy.parts.set(subject, p.ramp);
|
|
334
|
+
}
|
|
139
335
|
return copy;
|
|
140
336
|
});
|
|
141
337
|
ownBlends(c.cued);
|
|
142
338
|
if (mix.owners !== null)
|
|
143
|
-
relink(c.cued);
|
|
339
|
+
relink(c.cued, cut.seq);
|
|
144
340
|
stateAt(c, t);
|
|
145
341
|
recount(c);
|
|
146
342
|
}
|
|
@@ -202,12 +398,16 @@ function carry(mix, voice, subject, c, copy) {
|
|
|
202
398
|
* voice's start with fresh state, which is exact for a voice stepped at a fixed interval.
|
|
203
399
|
*/
|
|
204
400
|
export function recall(mix, voice, subject, t, cut, c, copy) {
|
|
205
|
-
const live = voice.left?.at(subject, cut) ??
|
|
401
|
+
const live = voice.left?.at(subject, cut) ??
|
|
402
|
+
heldAt(voice, subject, recordOf(mix, voice, subject), t, cut.seq);
|
|
206
403
|
if (live === undefined)
|
|
207
404
|
return undefined;
|
|
208
405
|
const snap = live.snaps && last(live.snaps, t, true);
|
|
209
406
|
if (snap) {
|
|
210
407
|
const h = copyHeld(voice, snap.held);
|
|
408
|
+
// A start still ahead when the copy was kept may have moved before `t`, with no copy between.
|
|
409
|
+
if (c !== undefined && copy !== undefined && !(h.since <= snap.at))
|
|
410
|
+
origin(c, copy, h);
|
|
211
411
|
h.from = h.stepped;
|
|
212
412
|
h.replay = live.inputs;
|
|
213
413
|
return h;
|
|
@@ -215,17 +415,16 @@ export function recall(mix, voice, subject, t, cut, c, copy) {
|
|
|
215
415
|
// Its origin by the controls the voice had then: a seek or an anchor has moved a waiting
|
|
216
416
|
// voice's since.
|
|
217
417
|
const since = c === undefined || copy === undefined ? live.since : c.sinceOf(copy, live.delay);
|
|
218
|
-
const stepped = since < t ? since : t;
|
|
219
418
|
const h = record(voice, live.reaches, live.reaches && voice.patch.state
|
|
220
419
|
? voice.patch.state(subject)
|
|
221
420
|
: undefined);
|
|
222
421
|
h.delay = live.delay;
|
|
223
422
|
h.since = since;
|
|
224
423
|
h.shown = c === undefined || copy === undefined ? live.shown : c.shownOf(copy, since);
|
|
225
|
-
|
|
424
|
+
const fresh = meet(mix, voice, h, t);
|
|
226
425
|
h.rebuilt = voice.rebuilds;
|
|
227
|
-
h.from = stepped;
|
|
228
|
-
h.unknown = voice.spec.from === 'current';
|
|
426
|
+
h.from = h.stepped;
|
|
427
|
+
h.unknown = voice.spec.from === 'current' || fresh;
|
|
229
428
|
h.replay = live.inputs;
|
|
230
429
|
return h;
|
|
231
430
|
}
|
|
@@ -286,10 +485,15 @@ function doubtOf(mix, voice, held) {
|
|
|
286
485
|
return 'held';
|
|
287
486
|
}
|
|
288
487
|
if (mix.now > (held.from ?? mix.now)) {
|
|
289
|
-
const
|
|
290
|
-
|
|
291
|
-
|
|
488
|
+
const fixed = stepOf(mix, voice) !== undefined;
|
|
489
|
+
// A read ahead reads a signal's state at every known event, and one step across a stretch
|
|
490
|
+
// with none in it is as good as any number; a read back steps it from a copy in one go.
|
|
491
|
+
const kept = held.kept !== null && held.kept.size > 0 && !replayed && mix.backward;
|
|
492
|
+
if ((voice.patch.step && !fixed) || kept)
|
|
292
493
|
return 'stepped';
|
|
293
494
|
}
|
|
495
|
+
// A seek stepped it from a copy, and what it keeps now goes on from there.
|
|
496
|
+
if (held.copied && held.kept !== null && held.kept.size > 0 && !replayed)
|
|
497
|
+
return 'stepped';
|
|
294
498
|
return 'exact';
|
|
295
499
|
}
|
package/dist/pull.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { axesOf } from './channels.js';
|
|
2
2
|
import { copy } from './clone.js';
|
|
3
3
|
import { clampRun, flush, pullRun, writeLater } from './columns.js';
|
|
4
|
+
/** What `pull` remembers at a position it has read no subject at. */
|
|
5
|
+
const UNREAD = Symbol('unread');
|
|
4
6
|
/** Writes a folded pose's value into subject `n`'s places in a column. */
|
|
5
7
|
function writeValue(c, v, n) {
|
|
6
8
|
const at = n * c.axes;
|
|
@@ -76,19 +78,33 @@ export function pull(mix, subjects, into) {
|
|
|
76
78
|
// all of them at once while nothing has relinked since the last pull, else one by one.
|
|
77
79
|
const was = mix.pulled;
|
|
78
80
|
const heads = mix.pulledHeads;
|
|
79
|
-
was.length = list.length;
|
|
80
|
-
heads.length = list.length;
|
|
81
81
|
if (mix.pulledSlots.length < list.length) {
|
|
82
|
-
const slots = new Int32Array(Math.max(64, list.length));
|
|
82
|
+
const slots = new Int32Array(Math.max(64, list.length)).fill(-1);
|
|
83
83
|
slots.set(mix.pulledSlots);
|
|
84
84
|
mix.pulledSlots = slots;
|
|
85
85
|
}
|
|
86
|
+
// A position the list no longer has, or has anew, holds no head: its slot says so.
|
|
87
|
+
if (list.length < was.length)
|
|
88
|
+
mix.pulledSlots.fill(-1, list.length, was.length);
|
|
89
|
+
// A new position holds a mark no subject equals: a hole would read as `undefined`, a subject.
|
|
90
|
+
for (let n = was.length; n < list.length; n++)
|
|
91
|
+
was.push(UNREAD);
|
|
92
|
+
was.length = list.length;
|
|
93
|
+
heads.length = list.length;
|
|
86
94
|
const version = mix.version;
|
|
87
95
|
const relinks = mix.relinks;
|
|
88
96
|
const live = !Number.isNaN(now);
|
|
89
97
|
const current = live && mix.pulledVersion === version && mix.pulledRelinks === relinks;
|
|
98
|
+
// Which remembered heads `pullRun` takes as current: all while nothing has changed; those
|
|
99
|
+
// `unlink` has not stamped since `pulledRelinks` while the version they were linked at stands;
|
|
100
|
+
// none otherwise.
|
|
101
|
+
const since = current
|
|
102
|
+
? Number.POSITIVE_INFINITY
|
|
103
|
+
: live && mix.pulledVersion === version
|
|
104
|
+
? mix.pulledRelinks
|
|
105
|
+
: -1;
|
|
90
106
|
try {
|
|
91
|
-
readAll(mix, list, columns, live, current, whole);
|
|
107
|
+
readAll(mix, list, columns, live, current, since, whole);
|
|
92
108
|
}
|
|
93
109
|
finally {
|
|
94
110
|
if (lanes !== null)
|
|
@@ -104,7 +120,7 @@ export function pull(mix, subjects, into) {
|
|
|
104
120
|
* compile the loop on the stack does not leave `pull`'s own code compiled before its tail had run:
|
|
105
121
|
* under churn that code deoptimized at the tail on every frame.
|
|
106
122
|
*/
|
|
107
|
-
function readAll(mix, list, columns, live, current, whole) {
|
|
123
|
+
function readAll(mix, list, columns, live, current, since, whole) {
|
|
108
124
|
const lanes = mix.lanes;
|
|
109
125
|
const now = mix.now;
|
|
110
126
|
const scratch = mix.scratch;
|
|
@@ -116,7 +132,7 @@ function readAll(mix, list, columns, live, current, whole) {
|
|
|
116
132
|
const runs = live && whole;
|
|
117
133
|
for (let n = 0; n < list.length; n++) {
|
|
118
134
|
if (runs) {
|
|
119
|
-
n = pullRun(lanes, slots, list, was,
|
|
135
|
+
n = pullRun(lanes, slots, list, was, since, n, columns, now, mix.version);
|
|
120
136
|
if (n === list.length)
|
|
121
137
|
break;
|
|
122
138
|
}
|
|
@@ -149,13 +165,24 @@ function readAll(mix, list, columns, live, current, whole) {
|
|
|
149
165
|
writeValue(c, pose[c.key], n);
|
|
150
166
|
}
|
|
151
167
|
}
|
|
152
|
-
/**
|
|
168
|
+
/**
|
|
169
|
+
* The kit slot, width, rest and bounds of every channel `pull` was handed an array for: one record
|
|
170
|
+
* a channel for the mix's life, pointed at the array this call was handed.
|
|
171
|
+
*/
|
|
153
172
|
function columnsOf(mix, into) {
|
|
154
173
|
const columns = [];
|
|
174
|
+
mix.pullColumns ??= new Map();
|
|
175
|
+
const known = mix.pullColumns;
|
|
155
176
|
for (const key of Object.keys(into)) {
|
|
156
177
|
const out = into[key];
|
|
157
178
|
if (out === undefined)
|
|
158
179
|
continue;
|
|
180
|
+
const had = known.get(key);
|
|
181
|
+
if (had !== undefined) {
|
|
182
|
+
had.out = out;
|
|
183
|
+
columns.push(had);
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
159
186
|
const slot = mix.slotOf.get(key);
|
|
160
187
|
if (slot === undefined)
|
|
161
188
|
throw new Error(`blits: pull was handed ${key}, which the kit lacks`);
|
|
@@ -170,7 +197,9 @@ function columnsOf(mix, into) {
|
|
|
170
197
|
at.set(rest);
|
|
171
198
|
if (bounds !== undefined)
|
|
172
199
|
clampRun(at, 0, axes, bounds);
|
|
173
|
-
|
|
200
|
+
const column = { key, slot, axes, out, rest: at, bounds };
|
|
201
|
+
known.set(key, column);
|
|
202
|
+
columns.push(column);
|
|
174
203
|
}
|
|
175
204
|
return columns;
|
|
176
205
|
}
|
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 { byId } from './due.js';
|
|
4
5
|
import { weighAt } from './everyone.js';
|
|
5
6
|
import { wake } from './fill.js';
|
|
6
7
|
import { Lane } from './lane.js';
|
|
@@ -231,8 +232,13 @@ export function open(lanes, v) {
|
|
|
231
232
|
* A crowd a quarter empty rows is compacted.
|
|
232
233
|
*/
|
|
233
234
|
export function retouch(lanes) {
|
|
234
|
-
const touched =
|
|
235
|
-
lanes.touched
|
|
235
|
+
const touched = byId(lanes.touched);
|
|
236
|
+
lanes.touched = [];
|
|
237
|
+
let kept = 0;
|
|
238
|
+
for (let i = 0; i < touched.length; i++)
|
|
239
|
+
if (i === 0 || touched[i] !== touched[i - 1])
|
|
240
|
+
touched[kept++] = touched[i];
|
|
241
|
+
touched.length = kept;
|
|
236
242
|
// The qualify a false answer calls takes up every touched voice, crowd rows starting included.
|
|
237
243
|
const qualify = () => {
|
|
238
244
|
lanes.touched = touched;
|
package/dist/reading.d.ts
CHANGED
|
@@ -25,4 +25,10 @@ export declare const reading: {
|
|
|
25
25
|
* since it read it. One count for every mix, as `moved` is.
|
|
26
26
|
*/
|
|
27
27
|
inputs: number;
|
|
28
|
+
/**
|
|
29
|
+
* While a read ahead that played its tape's calls reads: the retargets and pushes those calls
|
|
30
|
+
* made, by motion patch and then subject, in time order. A patch applies them with its own and
|
|
31
|
+
* commits none. Null for every other read.
|
|
32
|
+
*/
|
|
33
|
+
ahead: Map<object, Map<unknown, unknown[]>> | null;
|
|
28
34
|
};
|
package/dist/reading.js
CHANGED
|
@@ -25,4 +25,10 @@ export const reading = {
|
|
|
25
25
|
* since it read it. One count for every mix, as `moved` is.
|
|
26
26
|
*/
|
|
27
27
|
inputs: 0,
|
|
28
|
+
/**
|
|
29
|
+
* While a read ahead that played its tape's calls reads: the retargets and pushes those calls
|
|
30
|
+
* made, by motion patch and then subject, in time order. A patch applies them with its own and
|
|
31
|
+
* commits none. Null for every other read.
|
|
32
|
+
*/
|
|
33
|
+
ahead: null,
|
|
28
34
|
};
|
package/dist/record.js
CHANGED
package/dist/restored.d.ts
CHANGED
|
@@ -11,6 +11,7 @@ import type { Subject } from './voice.js';
|
|
|
11
11
|
*/
|
|
12
12
|
export declare class Restored<I> extends Store<I, Subject<unknown>> {
|
|
13
13
|
private readonly own;
|
|
14
|
+
private readonly now;
|
|
14
15
|
private readonly make;
|
|
15
16
|
/** Seeks back so far. */
|
|
16
17
|
private n;
|
|
@@ -26,13 +27,15 @@ export declare class Restored<I> extends Store<I, Subject<unknown>> {
|
|
|
26
27
|
/** The latest seek's moment. */
|
|
27
28
|
private t;
|
|
28
29
|
/**
|
|
29
|
-
* `own` holds the records as they stood before the first seek
|
|
30
|
-
*
|
|
30
|
+
* `own` holds the records as they stood before the first seek, and `now` gives the one a subject
|
|
31
|
+
* that has not left since has there, as a seek to `t`, the end of frame `seq`, takes it. `make`
|
|
32
|
+
* puts a record back as it stood at `t`, undefined where it had none then; `top` is the latest
|
|
33
|
+
* seek's moment.
|
|
31
34
|
*/
|
|
32
|
-
constructor(own: Store<I, Subject<unknown>>, make: (live: Subject<unknown>, t: number, top: number) => Subject<unknown> | undefined);
|
|
35
|
+
constructor(own: Store<I, Subject<unknown>>, now: (key: I, held: Subject<unknown> | undefined, t: number, seq: number) => Subject<unknown> | undefined, make: (live: Subject<unknown>, t: number, top: number) => Subject<unknown> | undefined);
|
|
33
36
|
/** A seek back to `t`: `left` has the records subjects left with after `cut`, null for none. */
|
|
34
37
|
back(t: number, cut: Cut, left: Leavings<I> | null): void;
|
|
35
|
-
/** The earliest moment sought by seek `from` or any after it. */
|
|
38
|
+
/** The earliest moment sought by seek `from` or any after it, and the frame it ended. */
|
|
36
39
|
private since;
|
|
37
40
|
get(key: I): Subject<unknown> | undefined;
|
|
38
41
|
set(key: I, value: Subject<unknown>): void;
|
package/dist/restored.js
CHANGED
|
@@ -8,6 +8,7 @@ import { Store } from './store.js';
|
|
|
8
8
|
*/
|
|
9
9
|
export class Restored extends Store {
|
|
10
10
|
own;
|
|
11
|
+
now;
|
|
11
12
|
make;
|
|
12
13
|
/** Seeks back so far. */
|
|
13
14
|
n = 0;
|
|
@@ -23,12 +24,15 @@ export class Restored extends Store {
|
|
|
23
24
|
/** The latest seek's moment. */
|
|
24
25
|
t = Number.NaN;
|
|
25
26
|
/**
|
|
26
|
-
* `own` holds the records as they stood before the first seek
|
|
27
|
-
*
|
|
27
|
+
* `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 `t`, the end of frame `seq`, takes it. `make`
|
|
29
|
+
* puts a record back as it stood at `t`, undefined where it had none then; `top` is the latest
|
|
30
|
+
* seek's moment.
|
|
28
31
|
*/
|
|
29
|
-
constructor(own, make) {
|
|
32
|
+
constructor(own, now, make) {
|
|
30
33
|
super();
|
|
31
34
|
this.own = own;
|
|
35
|
+
this.now = now;
|
|
32
36
|
this.make = make;
|
|
33
37
|
}
|
|
34
38
|
/** A seek back to `t`: `left` has the records subjects left with after `cut`, null for none. */
|
|
@@ -37,14 +41,14 @@ export class Restored extends Store {
|
|
|
37
41
|
const least = this.least;
|
|
38
42
|
while (least.length > 0 && least[least.length - 1].t >= t)
|
|
39
43
|
least.pop();
|
|
40
|
-
least.push({ n, t });
|
|
44
|
+
least.push({ n, t, seq: cut.seq });
|
|
41
45
|
this.t = t;
|
|
42
46
|
if (left !== null) {
|
|
43
47
|
this.futures ??= new Map();
|
|
44
48
|
this.futures.set(n, { left, cut });
|
|
45
49
|
}
|
|
46
50
|
}
|
|
47
|
-
/** The earliest moment sought by seek `from` or any after it. */
|
|
51
|
+
/** The earliest moment sought by seek `from` or any after it, and the frame it ended. */
|
|
48
52
|
since(from) {
|
|
49
53
|
const least = this.least;
|
|
50
54
|
let lo = 0;
|
|
@@ -56,7 +60,7 @@ export class Restored extends Store {
|
|
|
56
60
|
else
|
|
57
61
|
lo = mid + 1;
|
|
58
62
|
}
|
|
59
|
-
return least[lo]
|
|
63
|
+
return least[lo];
|
|
60
64
|
}
|
|
61
65
|
get(key) {
|
|
62
66
|
const at = this.at.get(key) ?? 0;
|
|
@@ -66,10 +70,9 @@ export class Restored extends Store {
|
|
|
66
70
|
// A subject that left after the moment the next seek sought had the record it left with,
|
|
67
71
|
// whatever it has now. Leaving is a write, so no later seek's leavings can hold it.
|
|
68
72
|
const future = this.futures?.get(at + 1);
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
const made = this.make(live, this.since(at + 1), this.t);
|
|
73
|
+
const { t, seq } = this.since(at + 1);
|
|
74
|
+
const live = future?.left.at(key, future.cut) ?? this.now(key, this.own.get(key), t, seq);
|
|
75
|
+
const made = live && this.make(live, t, this.t);
|
|
73
76
|
if (made === undefined)
|
|
74
77
|
this.own.delete(key);
|
|
75
78
|
else
|
package/dist/revive.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ import type { Past } from './origin.js';
|
|
|
4
4
|
import { type PackedRecord } from './pack.js';
|
|
5
5
|
import { type Key } from './paging.js';
|
|
6
6
|
import type { Described } from './types.js';
|
|
7
|
-
import { type Controls, type Ramp, type Rise, Voice } from './voice.js';
|
|
7
|
+
import { type Controls, type Parted, type Parting, type Ramp, type Rise, Voice } from './voice.js';
|
|
8
8
|
/** A voice that has left, as plain data: the descriptor it was cued with, its controls and its records. */
|
|
9
9
|
interface PackedVoice {
|
|
10
10
|
as: Described;
|
|
@@ -43,9 +43,11 @@ interface PackedVoice {
|
|
|
43
43
|
playedSeq: number;
|
|
44
44
|
records: [Key, PackedRecord][];
|
|
45
45
|
runs: [Key, Run][];
|
|
46
|
-
|
|
46
|
+
/** The fades are absent from a store written before a leaving kept them. */
|
|
47
|
+
left: [Key, number, number, PackedRecord, boolean, (Parting | null)?, (Parted | null)?][];
|
|
47
48
|
parts: [Key, number, number, number][];
|
|
48
|
-
|
|
49
|
+
/** With the ramp that took the subject out; absent from a store written before a voice kept it. */
|
|
50
|
+
parted: [Key, number, number, Parting?][];
|
|
49
51
|
}
|
|
50
52
|
/**
|
|
51
53
|
* Voices a history store holds whose handles something still holds, by id: a seek that revives
|