@msb235/blits 0.16.0 → 0.16.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 +149 -0
- package/README.md +1 -1
- package/dist/ahead.js +6 -9
- package/dist/clock.d.ts +11 -1
- package/dist/clock.js +28 -9
- package/dist/contribution.js +11 -4
- package/dist/cue.js +12 -3
- package/dist/due.d.ts +18 -0
- package/dist/due.js +101 -2
- package/dist/fade.d.ts +12 -2
- package/dist/fade.js +41 -8
- package/dist/fill.js +2 -1
- package/dist/fit.js +8 -0
- package/dist/gone.js +1 -0
- package/dist/handle.d.ts +1 -1
- package/dist/handle.js +3 -1
- package/dist/held.d.ts +2 -1
- package/dist/held.js +14 -6
- package/dist/hosts.js +5 -1
- package/dist/lane.d.ts +2 -0
- package/dist/lane.js +2 -0
- package/dist/lanes.js +2 -1
- package/dist/meet.js +9 -0
- package/dist/mixer.d.ts +2 -0
- package/dist/mixer.js +2 -0
- package/dist/motion.js +6 -6
- package/dist/move.js +309 -82
- package/dist/origin.d.ts +2 -0
- package/dist/origin.js +4 -0
- package/dist/owner.js +4 -2
- package/dist/paging.d.ts +5 -1
- package/dist/paging.js +13 -3
- package/dist/patch.js +24 -4
- package/dist/place.js +67 -31
- package/dist/project.d.ts +5 -2
- package/dist/project.js +7 -4
- package/dist/qualify.js +6 -1
- package/dist/rests.js +9 -1
- package/dist/revive.d.ts +11 -1
- package/dist/revive.js +7 -0
- package/dist/roles.js +5 -2
- package/dist/rows.js +4 -1
- package/dist/seat.d.ts +5 -1
- package/dist/seat.js +5 -1
- package/dist/seek.js +6 -4
- package/dist/shared.d.ts +1 -1
- package/dist/shared.js +1 -1
- package/dist/spans.d.ts +10 -4
- package/dist/spans.js +121 -31
- package/dist/trace.js +7 -2
- package/dist/types/transport.d.ts +2 -1
- package/dist/voice.d.ts +42 -4
- package/dist/voice.js +45 -5
- package/dist/weigh.js +1 -1
- package/docs/schema.html +1 -1
- package/package.json +3 -1
package/dist/project.d.ts
CHANGED
|
@@ -12,8 +12,11 @@ import type { Voice } from './voice.js';
|
|
|
12
12
|
export declare function projectAll(transport: Transport, at: number | {
|
|
13
13
|
readonly frame: number;
|
|
14
14
|
}): TransportProjection;
|
|
15
|
-
/**
|
|
16
|
-
|
|
15
|
+
/**
|
|
16
|
+
* Sets each copied voice's state to what its clock and fade make it at `t`; with `cut`, the frames
|
|
17
|
+
* up to which history says which had started.
|
|
18
|
+
*/
|
|
19
|
+
export declare function stateAt<I, O>(c: Mixer<I, O>, t: number, cut?: Cut): void;
|
|
17
20
|
/**
|
|
18
21
|
* A projection back starts each subject from the latest copy kept at or before `t`, else from its
|
|
19
22
|
* voice's start with fresh state, which is exact for a voice stepped at a fixed interval.
|
package/dist/project.js
CHANGED
|
@@ -178,13 +178,16 @@ function copyBack(mix, c, t, cut) {
|
|
|
178
178
|
ownBlends(c.cued);
|
|
179
179
|
if (mix.owners !== null)
|
|
180
180
|
relink(c.cued, cut.seq);
|
|
181
|
-
stateAt(c, t);
|
|
181
|
+
stateAt(c, t, cut);
|
|
182
182
|
recount(c);
|
|
183
183
|
}
|
|
184
|
-
/**
|
|
185
|
-
|
|
184
|
+
/**
|
|
185
|
+
* Sets each copied voice's state to what its clock and fade make it at `t`; with `cut`, the frames
|
|
186
|
+
* up to which history says which had started.
|
|
187
|
+
*/
|
|
188
|
+
export function stateAt(c, t, cut) {
|
|
186
189
|
for (const copy of c.cued)
|
|
187
|
-
copy.state = copy.stateAt(t);
|
|
190
|
+
copy.state = copy.stateAt(t, cut);
|
|
188
191
|
}
|
|
189
192
|
/**
|
|
190
193
|
* A projection back starts each subject from the latest copy kept at or before `t`, else from its
|
package/dist/qualify.js
CHANGED
|
@@ -96,7 +96,11 @@ export function requalify(lanes, version) {
|
|
|
96
96
|
crowded++;
|
|
97
97
|
continue;
|
|
98
98
|
}
|
|
99
|
-
|
|
99
|
+
let lane = lanes.byId.get(v.id);
|
|
100
|
+
if (lane === undefined) {
|
|
101
|
+
lane = new Lane(v);
|
|
102
|
+
lane.made = lanes.probes;
|
|
103
|
+
}
|
|
100
104
|
v.laned = true;
|
|
101
105
|
if (lane.epoch === 0 && v.state !== 'pending')
|
|
102
106
|
opened(lanes, lane);
|
|
@@ -395,6 +399,7 @@ function placeAll(lanes, lane) {
|
|
|
395
399
|
*/
|
|
396
400
|
function enlane(lanes, v) {
|
|
397
401
|
const lane = new Lane(v);
|
|
402
|
+
lane.made = lanes.probes;
|
|
398
403
|
v.laned = true;
|
|
399
404
|
if (v.state !== 'pending')
|
|
400
405
|
opened(lanes, lane);
|
package/dist/rests.js
CHANGED
|
@@ -56,7 +56,15 @@ export function still(voice, now) {
|
|
|
56
56
|
if (voice.state === 'frozen' || voice.holding !== null)
|
|
57
57
|
return true;
|
|
58
58
|
const motion = voice.motion;
|
|
59
|
-
|
|
59
|
+
if (motion === undefined || !(voice.elapsedAt(now) >= voice.latest))
|
|
60
|
+
return false;
|
|
61
|
+
// A subject met inside its stagger and not read since has no run yet, and moves once it is.
|
|
62
|
+
const early = voice.early;
|
|
63
|
+
if (early !== null)
|
|
64
|
+
for (const h of early)
|
|
65
|
+
if (h.since <= now && !(h.probed >= h.since))
|
|
66
|
+
return false;
|
|
67
|
+
return motion.landed();
|
|
60
68
|
}
|
|
61
69
|
export function restsLaned() {
|
|
62
70
|
return this.lanes.whole && this.bounded.length === 0;
|
package/dist/revive.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ import { type Key } from './paging.js';
|
|
|
5
5
|
import type { Run } from './stretch.js';
|
|
6
6
|
import type { Parted, Parting } from './subject.js';
|
|
7
7
|
import type { Described } from './types.js';
|
|
8
|
-
import { type Controls, type Ramp, type Rise, Voice } from './voice.js';
|
|
8
|
+
import { type Controls, type Earlier, type Ramp, type Rise, Voice } from './voice.js';
|
|
9
9
|
/** A voice that has left, as plain data: the descriptor it was cued with, its controls and its records. */
|
|
10
10
|
interface PackedVoice {
|
|
11
11
|
as: Described;
|
|
@@ -21,6 +21,8 @@ interface PackedVoice {
|
|
|
21
21
|
weight: number;
|
|
22
22
|
out: Ramp | null;
|
|
23
23
|
back: Rise | null;
|
|
24
|
+
/** Absent from a store written before a voice kept them. */
|
|
25
|
+
stalls?: readonly number[] | null;
|
|
24
26
|
outAt: number;
|
|
25
27
|
outOver: number;
|
|
26
28
|
outSet: boolean;
|
|
@@ -28,12 +30,20 @@ interface PackedVoice {
|
|
|
28
30
|
hold: number;
|
|
29
31
|
past: number;
|
|
30
32
|
clocks: Past[] | null;
|
|
33
|
+
/** Absent from a store written before a voice kept it. */
|
|
34
|
+
earlier?: Earlier | null;
|
|
35
|
+
/** Absent from a store written before a voice kept it. */
|
|
36
|
+
later?: Earlier | null;
|
|
31
37
|
cuedAt: number;
|
|
32
38
|
cuedSeq: number;
|
|
39
|
+
/** Absent from a store written before a voice kept it. */
|
|
40
|
+
begunSeq?: number;
|
|
33
41
|
doneAt: number;
|
|
34
42
|
doneSeq: number;
|
|
35
43
|
opened: number;
|
|
36
44
|
pinned: number;
|
|
45
|
+
/** Absent from a store written before a voice kept it. */
|
|
46
|
+
offset?: number;
|
|
37
47
|
latest: number;
|
|
38
48
|
/** Absent from a store written before a voice kept it. */
|
|
39
49
|
soonest?: number;
|
package/dist/revive.js
CHANGED
|
@@ -79,19 +79,24 @@ function packVoice(voice, keys) {
|
|
|
79
79
|
weight: voice.weight,
|
|
80
80
|
out: voice.out,
|
|
81
81
|
back: voice.back,
|
|
82
|
+
stalls: voice.stalls,
|
|
82
83
|
outAt: voice.outAt,
|
|
83
84
|
outOver: voice.outOver,
|
|
84
85
|
outSet: voice.outSet,
|
|
85
86
|
rebuilds: voice.rebuilds,
|
|
86
87
|
hold: voice.hold,
|
|
87
88
|
past: voice.clocks?.length ?? 0,
|
|
89
|
+
earlier: voice.earlier,
|
|
90
|
+
later: voice.later,
|
|
88
91
|
clocks: voice.clocks,
|
|
89
92
|
cuedAt: voice.cuedAt,
|
|
90
93
|
cuedSeq: voice.cuedSeq,
|
|
94
|
+
begunSeq: voice.begunSeq,
|
|
91
95
|
doneAt: voice.doneAt,
|
|
92
96
|
doneSeq: voice.doneSeq,
|
|
93
97
|
opened: voice.opened,
|
|
94
98
|
pinned: voice.pinned,
|
|
99
|
+
offset: voice.offset,
|
|
95
100
|
latest: voice.latest,
|
|
96
101
|
soonest: voice.soonest,
|
|
97
102
|
keeping: voice.keeping,
|
|
@@ -127,10 +132,12 @@ export function reviveVoice(mix, id, d) {
|
|
|
127
132
|
voice.take(d);
|
|
128
133
|
voice.cuedAt = d.cuedAt;
|
|
129
134
|
voice.cuedSeq = d.cuedSeq;
|
|
135
|
+
voice.begunSeq = d.begunSeq ?? Number.POSITIVE_INFINITY;
|
|
130
136
|
voice.doneAt = d.doneAt;
|
|
131
137
|
voice.doneSeq = d.doneSeq;
|
|
132
138
|
voice.opened = d.opened;
|
|
133
139
|
voice.pinned = d.pinned;
|
|
140
|
+
voice.offset = d.offset ?? 0;
|
|
134
141
|
voice.latest = d.latest;
|
|
135
142
|
voice.soonest = d.soonest ?? 0;
|
|
136
143
|
voice.keeping = d.keeping;
|
package/dist/roles.js
CHANGED
|
@@ -58,9 +58,12 @@ export function foldRoles(subject, pose, head, laned, dry, except, owed) {
|
|
|
58
58
|
this.lanes.paid(voice.id, owed, held.weight);
|
|
59
59
|
if (!mine)
|
|
60
60
|
q = this.shared(voice, head.slot, held, q);
|
|
61
|
-
if (delta === null)
|
|
62
|
-
continue;
|
|
63
61
|
const role = voice.spec.role;
|
|
62
|
+
if (delta === null) {
|
|
63
|
+
if (role === undefined && !(held.weight > 0))
|
|
64
|
+
unband(held);
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
64
67
|
let group = -1;
|
|
65
68
|
if (role !== undefined) {
|
|
66
69
|
group = k.names.indexOf(role);
|
package/dist/rows.js
CHANGED
|
@@ -106,7 +106,7 @@ export function join(lanes, v, from, at, old) {
|
|
|
106
106
|
newRow(lanes, c, p, v);
|
|
107
107
|
if (c.hot[p * c.stride + 6 /* Hot.EPOCH */] === 0 && v.state !== 'pending')
|
|
108
108
|
c.hot[p * c.stride + 6 /* Hot.EPOCH */] = open(lanes, v);
|
|
109
|
-
const seat = new Seat(lanes, c, p);
|
|
109
|
+
const seat = new Seat(lanes, c, p, from === undefined ? lanes.probes : 0);
|
|
110
110
|
lanes.seats.set(v.id, seat);
|
|
111
111
|
v.seat = seat;
|
|
112
112
|
v.laned = true;
|
|
@@ -298,5 +298,8 @@ export function reportedRow(lanes, c, p, slot) {
|
|
|
298
298
|
return undefined;
|
|
299
299
|
if (c.list[p] !== slot)
|
|
300
300
|
return undefined;
|
|
301
|
+
const made = seatOf(lanes, c.voices[p])?.made ?? 0;
|
|
302
|
+
if (!(lanes.per[o + 3 /* Per.LANE_PROBE */] > made))
|
|
303
|
+
return undefined;
|
|
301
304
|
return c.data[p * 8 /* Row.STRIDE */ + (lanes.per[o + 5 /* Per.LANE_FILL */] === lanes.fills ? 2 /* Row.WEIGHT */ : 3 /* Row.PROBED */)];
|
|
302
305
|
}
|
package/dist/seat.d.ts
CHANGED
|
@@ -9,7 +9,11 @@ export declare class Seat<I, O> {
|
|
|
9
9
|
lanes: object | null;
|
|
10
10
|
readonly crowd: Crowd<I, O>;
|
|
11
11
|
row: number;
|
|
12
|
-
|
|
12
|
+
/** The lanes' probe count when its row was made new, 0 for a row copied from another crowd. */
|
|
13
|
+
readonly made: number;
|
|
14
|
+
constructor(lanes: object | null, crowd: Crowd<I, O>, row: number,
|
|
15
|
+
/** The lanes' probe count when its row was made new, 0 for a row copied from another crowd. */
|
|
16
|
+
made?: number);
|
|
13
17
|
}
|
|
14
18
|
/** `voice`'s seat in `lanes`, null where it has none there. */
|
|
15
19
|
export declare function seatOf<I, O>(lanes: object, voice: Voice<I, O>): Seat<I, O> | null;
|
package/dist/seat.js
CHANGED
|
@@ -7,10 +7,14 @@ export class Seat {
|
|
|
7
7
|
lanes;
|
|
8
8
|
crowd;
|
|
9
9
|
row;
|
|
10
|
-
|
|
10
|
+
made;
|
|
11
|
+
constructor(lanes, crowd, row,
|
|
12
|
+
/** The lanes' probe count when its row was made new, 0 for a row copied from another crowd. */
|
|
13
|
+
made = 0) {
|
|
11
14
|
this.lanes = lanes;
|
|
12
15
|
this.crowd = crowd;
|
|
13
16
|
this.row = row;
|
|
17
|
+
this.made = made;
|
|
14
18
|
}
|
|
15
19
|
}
|
|
16
20
|
/** `voice`'s seat in `lanes`, null where it has none there. */
|
package/dist/seek.js
CHANGED
|
@@ -11,7 +11,6 @@ import { unpull } from './pull.js';
|
|
|
11
11
|
import { isRested } from './record.js';
|
|
12
12
|
import { Restored } from './restored.js';
|
|
13
13
|
import { recount } from './roster.js';
|
|
14
|
-
import { refitAll } from './spans.js';
|
|
15
14
|
import { Store } from './store.js';
|
|
16
15
|
import { hostAhead, hostAt, replay } from './tape.js';
|
|
17
16
|
import { cover, coverFrame, cut } from './unpage.js';
|
|
@@ -321,8 +320,12 @@ function back(mix, t, seq, on) {
|
|
|
321
320
|
kept.push(voice);
|
|
322
321
|
for (const voice of kept)
|
|
323
322
|
restoreVoice(mix, voice, t, seq);
|
|
324
|
-
|
|
325
|
-
|
|
323
|
+
const cut = { seq, strict: false };
|
|
324
|
+
for (const voice of kept) {
|
|
325
|
+
voice.state = voice.stateAt(t, cut);
|
|
326
|
+
if (voice.begunSeq > seq)
|
|
327
|
+
voice.begunSeq = Number.POSITIVE_INFINITY;
|
|
328
|
+
}
|
|
326
329
|
mix.cued = kept;
|
|
327
330
|
mix.gone = gone;
|
|
328
331
|
mix.retired = [];
|
|
@@ -388,7 +391,6 @@ function back(mix, t, seq, on) {
|
|
|
388
391
|
mix.pins ??= new Map();
|
|
389
392
|
mix.pins.set(voice, voice.pinned);
|
|
390
393
|
}
|
|
391
|
-
refitAll(mix);
|
|
392
394
|
mix.due.clear();
|
|
393
395
|
for (const voice of kept)
|
|
394
396
|
schedule(mix, voice);
|
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.16.
|
|
5
|
+
export declare const release = "0.16.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.16.
|
|
5
|
+
export const release = '0.16.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
|
@@ -15,11 +15,17 @@ export declare class Fitting {
|
|
|
15
15
|
}
|
|
16
16
|
/** Refuses a voice a span cannot fit: one it would not own the timing of, or one with no end. */
|
|
17
17
|
export declare function checkChild<I, O>(spec: VoiceSpec<I, O>, child: Voice<I, O>): void;
|
|
18
|
+
/**
|
|
19
|
+
* Puts a child whose owner's clock ran back to before a fit changed its rate, or skipped it, back
|
|
20
|
+
* on the clock it ran on before, so it reads, run back over, what it read going forward; and once
|
|
21
|
+
* the owner's clock reaches that change again, takes the change again, as the layout stands.
|
|
22
|
+
*/
|
|
23
|
+
export declare function unfit<I, O>(mix: Mixer<I, O>, child: Voice<I, O>, now: number): void;
|
|
18
24
|
/**
|
|
19
25
|
* Lays out a span's children and fits them into what is left of its budget, from its clock at mix
|
|
20
26
|
* time `from`, default now. Only what is still to come moves: a playing child keeps its place and
|
|
21
27
|
* may change rate, a pending one is placed. A span held by a span is fitted again by its own span
|
|
22
|
-
* after.
|
|
28
|
+
* after. A span its clock ran back over keeps the layout it made going forward.
|
|
23
29
|
*/
|
|
24
30
|
export declare function refit<I, O>(mix: Mixer<I, O>, span: Voice<I, O>, from?: number): void;
|
|
25
31
|
/**
|
|
@@ -31,8 +37,8 @@ export declare function refloor<I, O>(mix: Mixer<I, O>): void;
|
|
|
31
37
|
export declare function lingers<I, O>(span: Voice<I, O>, at: number): boolean;
|
|
32
38
|
/**
|
|
33
39
|
* Lets the spans out of budget that hold no child still playing leave, at the moment their budget
|
|
34
|
-
* ran out.
|
|
40
|
+
* ran out, or, under history, stay quiet with an owner holding them.
|
|
35
41
|
*/
|
|
36
42
|
export declare function lapse<I, O>(mix: Mixer<I, O>, now: number): void;
|
|
37
|
-
/**
|
|
38
|
-
export declare function
|
|
43
|
+
/** Whether a span's budget is out by `now` with no child of it still playing. */
|
|
44
|
+
export declare function lapses<I, O>(span: Voice<I, O>, now: number): boolean;
|
package/dist/spans.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { arcEnd } from './arcs.js';
|
|
2
2
|
import { elapsedWith, retime } from './clock.js';
|
|
3
|
-
import {
|
|
3
|
+
import { release } from './fade.js';
|
|
4
4
|
import { defaultFit, layout, settle } from './fit.js';
|
|
5
5
|
import { reorigin } from './held.js';
|
|
6
6
|
import { noted } from './history.js';
|
|
@@ -52,40 +52,115 @@ export function checkChild(spec, child) {
|
|
|
52
52
|
else if (!Number.isFinite(child.span))
|
|
53
53
|
throw new Error('blits: a span cannot fit a voice that never ends');
|
|
54
54
|
}
|
|
55
|
+
/**
|
|
56
|
+
* Whether a span's clock went back over a child at span ms `local`: it holds one kept quiet that
|
|
57
|
+
* ran back to its start, or that starts after `local`, which only a clock gone back leaves quiet.
|
|
58
|
+
* Its layout up to there was made going forward, and stands until the clock passes it again.
|
|
59
|
+
*/
|
|
60
|
+
function ranBackOver(span, local) {
|
|
61
|
+
for (const child of span.holding.children)
|
|
62
|
+
if (child.out?.stays === true && (child.out.back === true || child.start > local))
|
|
63
|
+
return true;
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Puts a child whose owner's clock ran back to before a fit changed its rate, or skipped it, back
|
|
68
|
+
* on the clock it ran on before, so it reads, run back over, what it read going forward; and once
|
|
69
|
+
* the owner's clock reaches that change again, takes the change again, as the layout stands.
|
|
70
|
+
*/
|
|
71
|
+
export function unfit(mix, child, now) {
|
|
72
|
+
const t = ownerReading(child.owner, now);
|
|
73
|
+
let moved = false;
|
|
74
|
+
for (let e = child.earlier; e !== null && t < child.anchorNow; e = child.earlier) {
|
|
75
|
+
child.later = { ...standing(child), prev: child.later };
|
|
76
|
+
take(child, e);
|
|
77
|
+
child.earlier = e.prev;
|
|
78
|
+
moved = true;
|
|
79
|
+
}
|
|
80
|
+
for (let e = child.later; e !== null && t >= e.anchorNow; e = child.later) {
|
|
81
|
+
child.earlier = { ...standing(child), prev: child.earlier };
|
|
82
|
+
take(child, e);
|
|
83
|
+
child.later = e.prev;
|
|
84
|
+
moved = true;
|
|
85
|
+
}
|
|
86
|
+
if (!moved)
|
|
87
|
+
return;
|
|
88
|
+
mix.frame = nextFrame();
|
|
89
|
+
reorigin(mix, child, now);
|
|
90
|
+
noted(mix, child);
|
|
91
|
+
mix.lanes?.refill();
|
|
92
|
+
}
|
|
93
|
+
/** A child's clock as it stands, and whether a fit skipped it. */
|
|
94
|
+
function standing(c) {
|
|
95
|
+
const { anchorNow, anchorElapsed, rate, ramp, skipped } = c;
|
|
96
|
+
return { anchorNow, anchorElapsed, rate, ramp, skipped };
|
|
97
|
+
}
|
|
98
|
+
/** Puts a child on a clock it had, which jumps where a fit skipped it on one and not the other. */
|
|
99
|
+
function take(c, e) {
|
|
100
|
+
c.anchorNow = e.anchorNow;
|
|
101
|
+
c.anchorElapsed = e.anchorElapsed;
|
|
102
|
+
c.rate = e.rate;
|
|
103
|
+
c.ramp = e.ramp;
|
|
104
|
+
if (c.skipped !== e.skipped)
|
|
105
|
+
c.seeks++;
|
|
106
|
+
c.skipped = e.skipped;
|
|
107
|
+
}
|
|
55
108
|
/** A span's own clock at mix time `at`, from 0 while it has not started. */
|
|
56
109
|
function clockOf(span, at) {
|
|
57
110
|
if (Number.isNaN(at))
|
|
58
111
|
return 0;
|
|
59
|
-
const t =
|
|
112
|
+
const t = readingAt(span, at);
|
|
60
113
|
return Number.isFinite(t) ? Math.max(0, t) : 0;
|
|
61
114
|
}
|
|
115
|
+
/**
|
|
116
|
+
* What a voice's clock reads at mix time `at` through its owners' clocks as they stand, inverting
|
|
117
|
+
* `mixTime`: -Infinity where one yet to start has not reached its start. Unlike `ownerReading`, an
|
|
118
|
+
* owner that has started reads on its clock before its start, where a seek of the owner put a time
|
|
119
|
+
* its children left at.
|
|
120
|
+
*/
|
|
121
|
+
function readingAt(voice, at) {
|
|
122
|
+
const t = voice.owner === null ? at : readingAt(voice.owner, at);
|
|
123
|
+
if (t === Number.NEGATIVE_INFINITY || (voice.state === 'pending' && !(t >= voice.start)))
|
|
124
|
+
return Number.NEGATIVE_INFINITY;
|
|
125
|
+
return elapsedWith(voice, t);
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Whether a child has started, or would have by span ms `by` had a frame come between: a fit from a
|
|
129
|
+
* moment between frames keeps a child whose start that moment had passed where it is.
|
|
130
|
+
*/
|
|
131
|
+
function begun(child, by) {
|
|
132
|
+
return child.state !== 'pending' || child.start < by;
|
|
133
|
+
}
|
|
62
134
|
/** What a child's own clock reads at its span's `local`, 0 while it waits. */
|
|
63
|
-
function elapsedIn(child, local) {
|
|
64
|
-
return child
|
|
135
|
+
function elapsedIn(child, local, by) {
|
|
136
|
+
return begun(child, by) ? Math.max(0, elapsedWith(child, local)) : 0;
|
|
65
137
|
}
|
|
66
138
|
/** How much of a child is left, voice ms at its own clock: what its span still has to fit. */
|
|
67
|
-
function leftOf(child, local) {
|
|
139
|
+
function leftOf(child, local, by) {
|
|
68
140
|
const length = child.fitting !== null ? child.fitting.end : child.arc !== null ? arcEnd(child) : child.span;
|
|
69
|
-
return Math.max(0, length - elapsedIn(child, local));
|
|
141
|
+
return Math.max(0, length - elapsedIn(child, local, by));
|
|
70
142
|
}
|
|
71
143
|
/**
|
|
72
144
|
* Lays out a span's children and fits them into what is left of its budget, from its clock at mix
|
|
73
145
|
* time `from`, default now. Only what is still to come moves: a playing child keeps its place and
|
|
74
146
|
* may change rate, a pending one is placed. A span held by a span is fitted again by its own span
|
|
75
|
-
* after.
|
|
147
|
+
* after. A span its clock ran back over keeps the layout it made going forward.
|
|
76
148
|
*/
|
|
77
149
|
export function refit(mix, span, from = mix.now) {
|
|
78
150
|
const fitting = span.fitting;
|
|
79
151
|
if (mix.projecting || span.state === 'done')
|
|
80
152
|
return;
|
|
81
153
|
const local = clockOf(span, from);
|
|
82
|
-
|
|
154
|
+
if (ranBackOver(span, local))
|
|
155
|
+
return;
|
|
156
|
+
const by = from < mix.now ? local : Number.NEGATIVE_INFINITY;
|
|
157
|
+
const { kids, floors, waiting } = lineUp(mix, span, local, by);
|
|
83
158
|
const claims = kids.map((c, i) => {
|
|
84
159
|
const s = c.spec;
|
|
85
160
|
return {
|
|
86
161
|
floor: floors[i],
|
|
87
|
-
natural: leftOf(c, local) / (s.rate ?? 1),
|
|
88
|
-
state: c
|
|
162
|
+
natural: leftOf(c, local, by) / (s.rate ?? 1),
|
|
163
|
+
state: begun(c, by) ? 'playing' : 'pending',
|
|
89
164
|
faster: Math.max(1, s.faster ?? 1),
|
|
90
165
|
slower: Math.max(1, s.slower ?? 1),
|
|
91
166
|
overlap: s.overlap === true,
|
|
@@ -99,9 +174,10 @@ export function refit(mix, span, from = mix.now) {
|
|
|
99
174
|
kids.forEach((c, i) => {
|
|
100
175
|
const rate = (c.spec.rate ?? 1) * plan.rate[i];
|
|
101
176
|
const skipped = plan.skip[i] && c.holding === null;
|
|
177
|
+
const wasSkipped = c.skipped;
|
|
102
178
|
c.skipped = skipped;
|
|
103
179
|
let moved = false;
|
|
104
|
-
if (c
|
|
180
|
+
if (!begun(c, by)) {
|
|
105
181
|
const start = local + at[i];
|
|
106
182
|
const elapsed = skipped ? c.span : 0;
|
|
107
183
|
if (c.start !== start || c.rate !== rate || c.anchorElapsed !== elapsed || c.ramp !== null) {
|
|
@@ -111,10 +187,16 @@ export function refit(mix, span, from = mix.now) {
|
|
|
111
187
|
c.rate = rate;
|
|
112
188
|
c.ramp = null;
|
|
113
189
|
c.clocks = null;
|
|
190
|
+
c.earlier = null;
|
|
191
|
+
c.later = null;
|
|
114
192
|
moved = true;
|
|
115
193
|
}
|
|
116
194
|
}
|
|
117
195
|
else {
|
|
196
|
+
if (skipped || c.rate !== rate || c.ramp !== null) {
|
|
197
|
+
c.earlier = { ...standing(c), skipped: wasSkipped, prev: c.earlier };
|
|
198
|
+
c.later = null;
|
|
199
|
+
}
|
|
118
200
|
if (skipped) {
|
|
119
201
|
leave(c, local);
|
|
120
202
|
c.rebase(local);
|
|
@@ -141,6 +223,8 @@ export function refit(mix, span, from = mix.now) {
|
|
|
141
223
|
c.anchorNow = Number.POSITIVE_INFINITY;
|
|
142
224
|
c.anchorElapsed = 0;
|
|
143
225
|
c.clocks = null;
|
|
226
|
+
c.earlier = null;
|
|
227
|
+
c.later = null;
|
|
144
228
|
mix.frame = nextFrame();
|
|
145
229
|
reorigin(mix, c, mix.now);
|
|
146
230
|
noted(mix, c);
|
|
@@ -151,19 +235,26 @@ export function refit(mix, span, from = mix.now) {
|
|
|
151
235
|
const over = Number.isFinite(fitting.budget) ? Math.max(0, reach - fitting.budget) : 0;
|
|
152
236
|
const within = over < 1e-6;
|
|
153
237
|
const end = !Number.isFinite(fitting.budget) ? reach : within ? fitting.budget : reach;
|
|
154
|
-
fitting.report
|
|
238
|
+
const was = fitting.report;
|
|
239
|
+
const report = {
|
|
155
240
|
budget: fitting.budget,
|
|
156
241
|
length: reach,
|
|
157
242
|
over: within ? 0 : over,
|
|
158
243
|
skipped: plan.skip.filter((s, i) => s && kids[i]?.holding === null).length,
|
|
159
244
|
fell,
|
|
160
245
|
};
|
|
246
|
+
fitting.report = report;
|
|
161
247
|
if (end !== fitting.end) {
|
|
162
248
|
fitting.end = end;
|
|
163
249
|
noted(mix, span);
|
|
164
250
|
if (span.owner?.fitting)
|
|
165
251
|
refit(mix, span.owner, from);
|
|
166
252
|
}
|
|
253
|
+
else if (report.length !== was.length ||
|
|
254
|
+
report.over !== was.over ||
|
|
255
|
+
report.skipped !== was.skipped ||
|
|
256
|
+
report.fell !== was.fell)
|
|
257
|
+
noted(mix, span);
|
|
167
258
|
}
|
|
168
259
|
/**
|
|
169
260
|
* A span's children still to fit, in the order it lays them out: those playing first, as cued, then
|
|
@@ -171,13 +262,14 @@ export function refit(mix, span, from = mix.now) {
|
|
|
171
262
|
* 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
263
|
* so does every child after it, since each is laid out from the one before.
|
|
173
264
|
*/
|
|
174
|
-
function lineUp(mix, span, local) {
|
|
175
|
-
|
|
176
|
-
const
|
|
265
|
+
function lineUp(mix, span, local, by) {
|
|
266
|
+
// A child fading, or kept quiet past its passes, has nothing left to fit.
|
|
267
|
+
const live = (span.holding?.children ?? []).filter((c) => c.state !== 'done' && c.state !== 'frozen' && c.out === null && leftOf(c, local, by) > 0);
|
|
268
|
+
const kids = live.filter((c) => begun(c, by));
|
|
177
269
|
const floors = kids.map(() => undefined);
|
|
178
270
|
const waiting = [];
|
|
179
271
|
const pending = live
|
|
180
|
-
.filter((c) => c
|
|
272
|
+
.filter((c) => !begun(c, by))
|
|
181
273
|
.sort((a, b) => (a.spec.rank ?? 0) - (b.spec.rank ?? 0));
|
|
182
274
|
const chained = span.fitting.claim.order !== 'together';
|
|
183
275
|
for (const c of pending) {
|
|
@@ -224,23 +316,21 @@ export function lingers(span, at) {
|
|
|
224
316
|
}
|
|
225
317
|
/**
|
|
226
318
|
* Lets the spans out of budget that hold no child still playing leave, at the moment their budget
|
|
227
|
-
* ran out.
|
|
319
|
+
* ran out, or, under history, stay quiet with an owner holding them.
|
|
228
320
|
*/
|
|
229
321
|
export function lapse(mix, now) {
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
if (
|
|
235
|
-
|
|
236
|
-
if (ownerReading(span, now) >= f.budget)
|
|
237
|
-
retire(mix, span, span.timeAt(f.budget));
|
|
322
|
+
const owners = mix.owners ?? [];
|
|
323
|
+
// Those held first, cued after what holds them, so a span whose last span lapses goes with it.
|
|
324
|
+
for (let i = owners.length - 1; i >= 0; i--) {
|
|
325
|
+
const span = owners[i];
|
|
326
|
+
if (span.out?.stays !== true && lapses(span, now))
|
|
327
|
+
release(mix, span, span.timeAt(span.fitting.budget));
|
|
238
328
|
}
|
|
239
329
|
}
|
|
240
|
-
/**
|
|
241
|
-
export function
|
|
242
|
-
const
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
330
|
+
/** Whether a span's budget is out by `now` with no child of it still playing. */
|
|
331
|
+
export function lapses(span, now) {
|
|
332
|
+
const f = span.fitting;
|
|
333
|
+
if (f === null || span.state === 'done' || !Number.isFinite(f.budget))
|
|
334
|
+
return false;
|
|
335
|
+
return !plays(span, now) && ownerReading(span, now) >= f.budget;
|
|
246
336
|
}
|
package/dist/trace.js
CHANGED
|
@@ -151,6 +151,8 @@ function shapeOf(mix, voice, subject, from, to, plan) {
|
|
|
151
151
|
return false;
|
|
152
152
|
if (voice.ramp !== null || !(voice.rate > 0) || voice.out !== null || voice.back !== null)
|
|
153
153
|
return false;
|
|
154
|
+
if (voice.stalls !== null)
|
|
155
|
+
return false;
|
|
154
156
|
if (voice.parts !== null || voice.parted !== null || voice.left !== null)
|
|
155
157
|
return false;
|
|
156
158
|
if (Number.isFinite(voice.outAt) || voice.state === 'fading')
|
|
@@ -190,9 +192,12 @@ function shapeOf(mix, voice, subject, from, to, plan) {
|
|
|
190
192
|
at(ends);
|
|
191
193
|
if (!voice.freezesAfter && Number.isFinite(ends)) {
|
|
192
194
|
const over = reduced ? 0 : Number.isNaN(voice.outOver) ? (voice.fade.out ?? 0) : voice.outOver;
|
|
195
|
+
// Ended before its start, as a seek past its end leaves it, it fades from its start.
|
|
196
|
+
const fades = Math.max(voice.start, ends);
|
|
197
|
+
at(fades);
|
|
193
198
|
if (over > 0) {
|
|
194
|
-
bent.push([
|
|
195
|
-
at(
|
|
199
|
+
bent.push([fades, fades + over]);
|
|
200
|
+
at(fades + over);
|
|
196
201
|
}
|
|
197
202
|
// It leaves when its latest subject ends, which a subject first met later can move.
|
|
198
203
|
if (spec.stagger !== undefined)
|
|
@@ -45,7 +45,8 @@ export interface Transport {
|
|
|
45
45
|
/**
|
|
46
46
|
* Loads from the history `store` what a seek or projection to `time` will need. That is, what
|
|
47
47
|
* `seek(time)` and `project(time)` need where it reaches past memory. Without a store, or within
|
|
48
|
-
* memory, it loads nothing.
|
|
48
|
+
* memory, it loads nothing. What it loads is kept until a seek or read takes it, or until
|
|
49
|
+
* `history.ms` of mix time passes, after which the time needs `prepare` again.
|
|
49
50
|
*/
|
|
50
51
|
prepare(time: number): Promise<void>;
|
|
51
52
|
readonly now: number;
|