@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/fade.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { changed, headOf, unlink } from './chain.js';
|
|
2
|
-
import { envelope } from './clock.js';
|
|
2
|
+
import { envelope, fadeInAt } from './clock.js';
|
|
3
3
|
import { schedule } from './due.js';
|
|
4
4
|
import { leave, noted, releasing } from './history.js';
|
|
5
5
|
import { markOf } from './marks.js';
|
|
6
|
+
import { startedAt } from './origin.js';
|
|
6
7
|
import { orphan, ownerReading, plays } from './owner.js';
|
|
7
8
|
import { startOf } from './place.js';
|
|
8
9
|
import { isRested } from './record.js';
|
|
@@ -27,12 +28,26 @@ export function fadeSubject(mix, voice, subject, over) {
|
|
|
27
28
|
schedule(mix, voice);
|
|
28
29
|
mix.lanes?.refill();
|
|
29
30
|
}
|
|
30
|
-
/**
|
|
31
|
+
/**
|
|
32
|
+
* What a subject's own ramp out of a voice leaves of its weight this frame. It also holds the
|
|
33
|
+
* voice's fade in where the ramp began, as a fade out of the whole voice does, so it is a ratio to
|
|
34
|
+
* the fade in the voice's envelope gives.
|
|
35
|
+
*/
|
|
31
36
|
export function parting(voice, subject, now) {
|
|
32
37
|
const r = voice.parts?.get(subject);
|
|
33
38
|
if (r === undefined)
|
|
34
39
|
return 1;
|
|
35
|
-
|
|
40
|
+
const reduced = this.reducedNow;
|
|
41
|
+
const w = envelope(0, { at: r.at, over: r.over, rest: false }, voice.ease, reduced, now, 0);
|
|
42
|
+
const fadeIn = voice.fade.in ?? 0;
|
|
43
|
+
const since = voice.subjects.get(subject)?.shown;
|
|
44
|
+
if (!(fadeIn > 0) || reduced || since === undefined || !(r.at < now))
|
|
45
|
+
return w;
|
|
46
|
+
const out = voice.out;
|
|
47
|
+
const climbing = fadeInAt(fadeIn, out, voice.ease, now, since, voice.stalls);
|
|
48
|
+
if (!(climbing > 0))
|
|
49
|
+
return w;
|
|
50
|
+
return w * (fadeInAt(fadeIn, out, voice.ease, now, since, voice.stalls, r.at) / climbing);
|
|
36
51
|
}
|
|
37
52
|
/**
|
|
38
53
|
* Takes a subject out of one voice for good, as of mix time `at`: the voice forgets its record,
|
|
@@ -164,7 +179,8 @@ export function beginFade(mix, voice, opts, at) {
|
|
|
164
179
|
}
|
|
165
180
|
/**
|
|
166
181
|
* Keeps a voice faded at the end of its passes with its owner, live and quiet, rather than letting
|
|
167
|
-
* it leave; the owner
|
|
182
|
+
* it leave; the owner, where it holds no child still playing, leaves at `at`, or stays quiet in
|
|
183
|
+
* turn where an owner holds it.
|
|
168
184
|
*/
|
|
169
185
|
export function stay(mix, voice, at) {
|
|
170
186
|
voice.state = 'live';
|
|
@@ -173,10 +189,22 @@ export function stay(mix, voice, at) {
|
|
|
173
189
|
if (owner.state === 'done')
|
|
174
190
|
return;
|
|
175
191
|
const now = Number.isNaN(mix.now) ? Number.NEGATIVE_INFINITY : mix.now;
|
|
176
|
-
if (
|
|
192
|
+
if (plays(owner, now) || lingers(owner, at)) {
|
|
193
|
+
if (owner.fitting !== null && !voice.skipped)
|
|
194
|
+
refit(mix, owner, at);
|
|
195
|
+
}
|
|
196
|
+
else
|
|
197
|
+
release(mix, owner, at);
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Lets an owner holding no child still playing leave at `at`; under history one an owner holds
|
|
201
|
+
* stays with it instead, quiet while its children are.
|
|
202
|
+
*/
|
|
203
|
+
export function release(mix, owner, at) {
|
|
204
|
+
if (owner.owner !== null && owner.state === 'live' && mix.opts.history !== undefined)
|
|
205
|
+
beginFade(mix, owner, { over: 0, stays: true }, at);
|
|
206
|
+
else
|
|
177
207
|
retire(mix, owner, at);
|
|
178
|
-
else if (owner.fitting !== null && !voice.skipped)
|
|
179
|
-
refit(mix, owner, at);
|
|
180
208
|
}
|
|
181
209
|
/**
|
|
182
210
|
* Turns a fading voice around: it climbs back from where its fade out had got to, up the same
|
|
@@ -205,7 +233,12 @@ export function beginRise(mix, voice, opts) {
|
|
|
205
233
|
voice.out = null;
|
|
206
234
|
voice.outAt = Number.POSITIVE_INFINITY;
|
|
207
235
|
voice.state = waiting ? 'pending' : 'live';
|
|
236
|
+
if (waiting)
|
|
237
|
+
voice.begunSeq = Number.POSITIVE_INFINITY;
|
|
208
238
|
voice.back = over > 0 && from < 1 ? { from, at, over } : null;
|
|
239
|
+
// Its fade in stood still from where the fade out began, and climbs on from where it stood.
|
|
240
|
+
if (!out.rest && out.at < at)
|
|
241
|
+
voice.stalls = [...(voice.stalls ?? []), out.at, at];
|
|
209
242
|
if (waiting)
|
|
210
243
|
changed(mix, voice);
|
|
211
244
|
noted(mix, voice);
|
|
@@ -223,7 +256,7 @@ export function fadeAt(mix, voice, at, over) {
|
|
|
223
256
|
return;
|
|
224
257
|
const now = Number.isNaN(mix.now) ? startOf(voice) : mix.now;
|
|
225
258
|
if (at <= now) {
|
|
226
|
-
beginFade(mix, voice, { over }, Math.max(
|
|
259
|
+
beginFade(mix, voice, { over }, Math.max(startedAt(voice), at));
|
|
227
260
|
mix.stir();
|
|
228
261
|
return;
|
|
229
262
|
}
|
package/dist/fill.js
CHANGED
|
@@ -472,7 +472,8 @@ export function call(voice, chans, rec, slot) {
|
|
|
472
472
|
rec.delta = null;
|
|
473
473
|
rec.probed = this.now;
|
|
474
474
|
rec.seeks = voice.seeks;
|
|
475
|
-
|
|
475
|
+
// A role member is one at weight 0 too, unasked, and holds its role's place as on the general path.
|
|
476
|
+
return voice.spec.role !== undefined;
|
|
476
477
|
}
|
|
477
478
|
const kept = reading.kept;
|
|
478
479
|
host.ready(voice, subject, rec);
|
package/dist/fit.js
CHANGED
|
@@ -50,6 +50,14 @@ function least(lo, hi, ok) {
|
|
|
50
50
|
return lo;
|
|
51
51
|
if (!ok(hi))
|
|
52
52
|
return hi;
|
|
53
|
+
// An unbounded `hi`, as an `Infinity` hint gives, is brought to a finite one before halving.
|
|
54
|
+
if (hi === Number.POSITIVE_INFINITY) {
|
|
55
|
+
hi = Math.max(lo, 1) * 2;
|
|
56
|
+
while (!ok(hi)) {
|
|
57
|
+
lo = hi;
|
|
58
|
+
hi *= 2;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
53
61
|
for (let i = 0; i < 60; i++) {
|
|
54
62
|
const mid = (lo + hi) / 2;
|
|
55
63
|
if (ok(mid))
|
package/dist/gone.js
CHANGED
|
@@ -77,6 +77,7 @@ export function goneIndex(mix) {
|
|
|
77
77
|
/** Hands voices let go of from `gone` to the departed index, noting the latest that left. */
|
|
78
78
|
function forgot(mix, out) {
|
|
79
79
|
for (const v of out) {
|
|
80
|
+
mix.parters.delete(v);
|
|
80
81
|
mix.departed.add(mix, v);
|
|
81
82
|
if (v.doneAt > mix.transport.forgotTo)
|
|
82
83
|
mix.transport.forgotTo = v.doneAt;
|
package/dist/handle.d.ts
CHANGED
|
@@ -11,7 +11,7 @@ export interface HandleHost<I, O> {
|
|
|
11
11
|
* The voice's clock jumped: for an owner, so did every clock it holds. With `rebuild`, their
|
|
12
12
|
* state is made again for where they now are. How sure that leaves it.
|
|
13
13
|
*/
|
|
14
|
-
sought(voice: Voice<I, O>, rebuild: boolean): Doubt;
|
|
14
|
+
sought(voice: Voice<I, O>, rebuild: boolean, ahead: boolean): Doubt;
|
|
15
15
|
fade(voice: Voice<I, O>, opts: FadeOptions<I> | undefined): void;
|
|
16
16
|
rise(voice: Voice<I, O>, opts: {
|
|
17
17
|
over?: number;
|
package/dist/handle.js
CHANGED
|
@@ -115,14 +115,16 @@ export class VoiceHandle {
|
|
|
115
115
|
return 'exact';
|
|
116
116
|
voice.written = true;
|
|
117
117
|
// A pending voice's clock is anchored at its start, so it starts there on time.
|
|
118
|
+
let ahead = false;
|
|
118
119
|
if (voice.state !== 'pending') {
|
|
119
120
|
const u = this.#host.nowFor(voice);
|
|
120
121
|
leave(voice, u, true);
|
|
121
122
|
voice.rebase(u);
|
|
123
|
+
ahead = elapsed > voice.anchorElapsed;
|
|
122
124
|
}
|
|
123
125
|
voice.anchorElapsed = elapsed;
|
|
124
126
|
voice.seeks++;
|
|
125
|
-
const doubt = this.#host.sought(voice, opts?.state !== 'keep');
|
|
127
|
+
const doubt = this.#host.sought(voice, opts?.state !== 'keep', ahead);
|
|
126
128
|
this.#host.changed(voice);
|
|
127
129
|
if (this.#host.taping())
|
|
128
130
|
this.#host.record(this, 'seek', (h) => h.seek(elapsed, opts));
|
package/dist/held.d.ts
CHANGED
|
@@ -22,7 +22,8 @@ export declare function sinceOf<I, O>(this: Mixer<I, O>, voice: Voice<I, O>, del
|
|
|
22
22
|
/**
|
|
23
23
|
* The mix time a subject's fade in counts from: `since`, but no earlier than its voice starts,
|
|
24
24
|
* where a voice sought ahead of its start has a subject's delay run out before it; and from when
|
|
25
|
-
* the voice first showed, for one freezing before.
|
|
25
|
+
* the voice first showed, for one freezing before. Its start is where its owners' clocks first
|
|
26
|
+
* read it, as `since` is, not where they would by their controls now.
|
|
26
27
|
*/
|
|
27
28
|
export declare function shownOf<I, O>(this: Mixer<I, O>, voice: Voice<I, O>, since: number): number;
|
|
28
29
|
/** Lists a record whose origin is still ahead of `now`, for a later change to the clock to move. */
|
package/dist/held.js
CHANGED
|
@@ -2,8 +2,7 @@ import { changed } from './chain.js';
|
|
|
2
2
|
import { stepOf } from './clock.js';
|
|
3
3
|
import { delayOf } from './delay.js';
|
|
4
4
|
import { schedule } from './due.js';
|
|
5
|
-
import { originOf } from './origin.js';
|
|
6
|
-
import { startOf } from './place.js';
|
|
5
|
+
import { originOf, startedAt } from './origin.js';
|
|
7
6
|
import { record } from './record.js';
|
|
8
7
|
import { unreach, unreached } from './unreached.js';
|
|
9
8
|
/** The record a voice gives every subject it does not reach. */
|
|
@@ -118,13 +117,21 @@ export function sinceOf(voice, delay) {
|
|
|
118
117
|
/**
|
|
119
118
|
* The mix time a subject's fade in counts from: `since`, but no earlier than its voice starts,
|
|
120
119
|
* where a voice sought ahead of its start has a subject's delay run out before it; and from when
|
|
121
|
-
* the voice first showed, for one freezing before.
|
|
120
|
+
* the voice first showed, for one freezing before. Its start is where its owners' clocks first
|
|
121
|
+
* read it, as `since` is, not where they would by their controls now.
|
|
122
122
|
*/
|
|
123
123
|
export function shownOf(voice, since) {
|
|
124
|
-
const start =
|
|
124
|
+
const start = startedAt(voice);
|
|
125
125
|
const from = since < start ? start : since;
|
|
126
126
|
return voice.freezesBefore && voice.opened < from ? voice.opened : from;
|
|
127
127
|
}
|
|
128
|
+
/**
|
|
129
|
+
* Whether a listed record stays listed at `now`: its origin is ahead, or it is a motion voice's,
|
|
130
|
+
* unread since its origin, which `still` asks after since the motion has no run for it yet.
|
|
131
|
+
*/
|
|
132
|
+
function stillEarly(voice, held, now) {
|
|
133
|
+
return !(held.since <= now) || (voice.motion !== undefined && !(held.probed >= held.since));
|
|
134
|
+
}
|
|
128
135
|
/** Lists a record whose origin is still ahead of `now`, for a later change to the clock to move. */
|
|
129
136
|
export function waits(voice, held, now) {
|
|
130
137
|
if (held.since <= now)
|
|
@@ -135,7 +142,7 @@ export function waits(voice, held, now) {
|
|
|
135
142
|
if (early.length >= 64 && (early.length & (early.length - 1)) === 0) {
|
|
136
143
|
let kept = 0;
|
|
137
144
|
for (const h of early)
|
|
138
|
-
if (
|
|
145
|
+
if (stillEarly(voice, h, now))
|
|
139
146
|
early[kept++] = h;
|
|
140
147
|
early.length = kept;
|
|
141
148
|
}
|
|
@@ -147,6 +154,7 @@ export function waits(voice, held, now) {
|
|
|
147
154
|
*/
|
|
148
155
|
export function begin(mix, voice, now) {
|
|
149
156
|
voice.state = 'live';
|
|
157
|
+
voice.begunSeq = mix.transport.seq;
|
|
150
158
|
if (voice.anchorElapsed !== 0) {
|
|
151
159
|
voice.seeks++;
|
|
152
160
|
voice.rebuilds++;
|
|
@@ -178,7 +186,7 @@ export function reorigin(mix, voice, now) {
|
|
|
178
186
|
let kept = 0;
|
|
179
187
|
for (const held of early) {
|
|
180
188
|
origin(mix, voice, held);
|
|
181
|
-
if (
|
|
189
|
+
if (stillEarly(voice, held, now))
|
|
182
190
|
early[kept++] = held;
|
|
183
191
|
}
|
|
184
192
|
early.length = kept;
|
package/dist/hosts.js
CHANGED
|
@@ -216,7 +216,11 @@ function handleHost(mix) {
|
|
|
216
216
|
mix.lanes?.refill();
|
|
217
217
|
},
|
|
218
218
|
// A delta read before an owner's seek, or a hit booked, is not taken as standing after it.
|
|
219
|
-
sought: (voice, rebuild) => {
|
|
219
|
+
sought: (voice, rebuild, ahead) => {
|
|
220
|
+
if (ahead && voice.holding !== null && !mix.projecting) {
|
|
221
|
+
mix.leapt ??= new Set();
|
|
222
|
+
mix.leapt.add(voice);
|
|
223
|
+
}
|
|
220
224
|
if (rebuild)
|
|
221
225
|
voice.rebuilds++;
|
|
222
226
|
let doubt = seekDoubt(mix, voice, rebuild);
|
package/dist/lane.d.ts
CHANGED
|
@@ -185,6 +185,8 @@ export declare class Lane<I, O> implements Positions<I, O> {
|
|
|
185
185
|
* checked against every lane up to some epoch has met this one if this one's is no later.
|
|
186
186
|
*/
|
|
187
187
|
epoch: number;
|
|
188
|
+
/** The lanes' probe count when it was made: a probe no later read its subject elsewhere. */
|
|
189
|
+
made: number;
|
|
188
190
|
/**
|
|
189
191
|
* Per position, `STRIDE` numbers side by side, so one subject's are one read from memory: its
|
|
190
192
|
* delay, its `since`, its weight at the last fill, its weight at the last fill a probe read, a
|
package/dist/lane.js
CHANGED
|
@@ -37,6 +37,8 @@ export class Lane {
|
|
|
37
37
|
* checked against every lane up to some epoch has met this one if this one's is no later.
|
|
38
38
|
*/
|
|
39
39
|
epoch = 0;
|
|
40
|
+
/** The lanes' probe count when it was made: a probe no later read its subject elsewhere. */
|
|
41
|
+
made = 0;
|
|
40
42
|
/**
|
|
41
43
|
* Per position, `STRIDE` numbers side by side, so one subject's are one read from memory: its
|
|
42
44
|
* delay, its `since`, its weight at the last fill, its weight at the last fill a probe read, a
|
package/dist/lanes.js
CHANGED
|
@@ -268,7 +268,8 @@ export class Lanes {
|
|
|
268
268
|
}
|
|
269
269
|
reported(lane, slot) {
|
|
270
270
|
const o = slot * 6 /* Per.SLOT */;
|
|
271
|
-
|
|
271
|
+
const probe = this.per[o + 3 /* Per.LANE_PROBE */];
|
|
272
|
+
if (!(probe > this.per[o + 4 /* Per.GENERAL_PROBE */]) || !(probe > lane.made))
|
|
272
273
|
return undefined;
|
|
273
274
|
const p = lane.positionOf(slot);
|
|
274
275
|
if (p < 0)
|
package/dist/meet.js
CHANGED
|
@@ -86,6 +86,15 @@ export function meet(lanes, slot, subject, head, lane) {
|
|
|
86
86
|
}
|
|
87
87
|
if (!met)
|
|
88
88
|
return lane;
|
|
89
|
+
// Met newest first; folded, as the general path folds them, in voice order.
|
|
90
|
+
const ids = lanes.met;
|
|
91
|
+
for (let i = 1; i < lanes.metN; i++) {
|
|
92
|
+
const id = ids[i];
|
|
93
|
+
let j = i - 1;
|
|
94
|
+
for (; j >= 0 && ids[j] > id; j--)
|
|
95
|
+
ids[j + 1] = ids[j];
|
|
96
|
+
ids[j + 1] = id;
|
|
97
|
+
}
|
|
89
98
|
// The fill ran before the subject had these positions. Where every voice it just met folds
|
|
90
99
|
// after every other laned voice, the general path folds just those onto the lanes' values;
|
|
91
100
|
// otherwise it reads the general path all frame.
|
package/dist/mixer.d.ts
CHANGED
|
@@ -106,6 +106,8 @@ export declare class Mixer<I, O> implements Mix<I, O> {
|
|
|
106
106
|
retired: Voice<I, O>[];
|
|
107
107
|
/** Visit every voice each sync, as before the due queue; for tests that compare the two. */
|
|
108
108
|
walkAll: boolean;
|
|
109
|
+
/** Owners sought forward since the last frame, which passes what each seek went past in order. */
|
|
110
|
+
leapt: Set<Voice<I, O>> | null;
|
|
109
111
|
/** The last array `pull` read, by position, with each subject's chain head, to skip the lookup. */
|
|
110
112
|
pulled: I[];
|
|
111
113
|
pulledHeads: (Subject<unknown> | undefined)[];
|
package/dist/mixer.js
CHANGED
|
@@ -95,6 +95,8 @@ export class Mixer {
|
|
|
95
95
|
retired = [];
|
|
96
96
|
/** Visit every voice each sync, as before the due queue; for tests that compare the two. */
|
|
97
97
|
walkAll = false;
|
|
98
|
+
/** Owners sought forward since the last frame, which passes what each seek went past in order. */
|
|
99
|
+
leapt = null;
|
|
98
100
|
/** The last array `pull` read, by position, with each subject's chain head, to skip the lookup. */
|
|
99
101
|
pulled = [];
|
|
100
102
|
pulledHeads = [];
|
package/dist/motion.js
CHANGED
|
@@ -241,8 +241,8 @@ export function spring(writes, opts) {
|
|
|
241
241
|
const k = opts.stiffness ?? 170;
|
|
242
242
|
const c = opts.damping ?? 26;
|
|
243
243
|
const m = opts.mass ?? 1;
|
|
244
|
-
if (!(k > 0) || !(m > 0) || !(c >= 0))
|
|
245
|
-
throw new RangeError('blits: a spring takes a positive stiffness and mass, and damping of 0 or more');
|
|
244
|
+
if (!(k > 0 && k < Infinity) || !(m > 0 && m < Infinity) || !(c >= 0 && c < Infinity))
|
|
245
|
+
throw new RangeError('blits: a spring takes a finite positive stiffness and mass, and finite damping of 0 or more');
|
|
246
246
|
const settle = opts.settle ?? 1e-4;
|
|
247
247
|
const w0 = Math.sqrt(k / m);
|
|
248
248
|
const zeta = c / (2 * Math.sqrt(k * m));
|
|
@@ -273,11 +273,11 @@ export function spring(writes, opts) {
|
|
|
273
273
|
*/
|
|
274
274
|
export function glide(writes, opts) {
|
|
275
275
|
const ms = opts.ms ?? 325;
|
|
276
|
-
if (!(ms > 0))
|
|
277
|
-
throw new RangeError('blits: a glide takes a positive ms');
|
|
276
|
+
if (!(ms > 0 && ms < Infinity))
|
|
277
|
+
throw new RangeError('blits: a glide takes a finite positive ms');
|
|
278
278
|
const k = opts.stiffness ?? 170;
|
|
279
|
-
if (!(k > 0))
|
|
280
|
-
throw new RangeError('blits: a glide takes a positive stiffness');
|
|
279
|
+
if (!(k > 0 && k < Infinity))
|
|
280
|
+
throw new RangeError('blits: a glide takes a finite positive stiffness');
|
|
281
281
|
const tau = ms / 1000;
|
|
282
282
|
const settle = opts.settle ?? 1e-4;
|
|
283
283
|
const bounded = opts.bounds !== undefined;
|