@msb235/blits 0.11.0 → 0.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +110 -0
- package/dist/arcs.d.ts +40 -0
- package/dist/arcs.js +241 -0
- package/dist/chain.d.ts +6 -1
- package/dist/chain.js +33 -13
- package/dist/channels.d.ts +2 -0
- package/dist/channels.js +12 -1
- package/dist/clone.js +16 -4
- package/dist/cue.js +11 -2
- package/dist/departed.d.ts +5 -1
- package/dist/departed.js +71 -27
- package/dist/easing.d.ts +12 -1
- package/dist/easing.js +7 -1
- package/dist/everyone.d.ts +14 -1
- package/dist/everyone.js +27 -2
- package/dist/fade.js +3 -3
- package/dist/fill.js +16 -9
- package/dist/fit.d.ts +8 -1
- package/dist/fit.js +7 -3
- package/dist/fold.js +69 -13
- package/dist/gather.js +38 -7
- package/dist/handle.d.ts +2 -0
- package/dist/handle.js +8 -0
- package/dist/held.d.ts +5 -2
- package/dist/held.js +6 -3
- package/dist/history.d.ts +19 -2
- package/dist/history.js +149 -31
- package/dist/hosts.js +27 -11
- package/dist/index.d.ts +4 -1
- package/dist/index.js +2 -0
- package/dist/lanes.d.ts +23 -7
- package/dist/lanes.js +36 -2
- package/dist/lean.js +2 -2
- package/dist/marks.js +3 -0
- package/dist/mixer.d.ts +16 -1
- package/dist/mixer.js +60 -30
- package/dist/move.js +7 -2
- package/dist/numbers.d.ts +5 -1
- package/dist/numbers.js +4 -0
- package/dist/owner.js +6 -1
- package/dist/place.d.ts +8 -0
- package/dist/place.js +16 -8
- package/dist/project.js +3 -3
- package/dist/qualify.d.ts +10 -0
- package/dist/qualify.js +52 -1
- package/dist/reading.d.ts +2 -1
- package/dist/reading.js +2 -1
- package/dist/record.d.ts +6 -0
- package/dist/record.js +1 -0
- package/dist/restored.d.ts +67 -18
- package/dist/restored.js +159 -49
- package/dist/revive.d.ts +1 -0
- package/dist/revive.js +1 -0
- package/dist/roster.js +2 -1
- package/dist/seek.js +72 -18
- package/dist/shared.d.ts +1 -1
- package/dist/shared.js +1 -1
- package/dist/spans.d.ts +5 -0
- package/dist/spans.js +75 -5
- package/dist/store.d.ts +2 -0
- package/dist/store.js +4 -0
- package/dist/touch.js +3 -2
- package/dist/transport.js +6 -1
- package/dist/types/arc.d.ts +55 -0
- package/dist/types/arc.js +1 -0
- package/dist/types/mix.d.ts +24 -0
- package/dist/types/voice.d.ts +10 -1
- package/dist/types/watch.d.ts +126 -0
- package/dist/types/watch.js +1 -0
- package/dist/types.d.ts +2 -0
- package/dist/voice.d.ts +37 -6
- package/dist/voice.js +17 -0
- package/dist/watch.d.ts +28 -0
- package/dist/watch.js +467 -0
- package/dist/weigh.d.ts +13 -2
- package/dist/weigh.js +10 -3
- package/docs/schema.html +39 -3
- package/package.json +5 -4
package/dist/mixer.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { cueArc } from './arcs.js';
|
|
1
2
|
import { blended, blendOf, laneRead, shareKept } from './blend.js';
|
|
2
|
-
import { aims, chain, linkable, relink, unlink } from './chain.js';
|
|
3
|
-
import { lerpInto } from './channels.js';
|
|
3
|
+
import { aims, chain, headOf, linkable, relink, unlink } from './chain.js';
|
|
4
|
+
import { lerpInto, scalarOp } from './channels.js';
|
|
4
5
|
import { cue } from './cue.js';
|
|
5
6
|
import { DepartedIndex } from './departed.js';
|
|
6
7
|
import { beginFade, leaveVoice, parting } from './fade.js';
|
|
@@ -27,6 +28,7 @@ import { record, unplayable } from './tape.js';
|
|
|
27
28
|
import { touch } from './touch.js';
|
|
28
29
|
import { announce, Transport } from './transport.js';
|
|
29
30
|
import { none } from './voice.js';
|
|
31
|
+
import { gave, historyFor, register, unregister } from './watch.js';
|
|
30
32
|
import { base, capped, fadeOf, horizonFor, ownedBy, ownerBase, prime, weigh, weighOwned, } from './weigh.js';
|
|
31
33
|
/**
|
|
32
34
|
* What a mix takes where its options say nothing. Not exported from the package: the suite turns
|
|
@@ -142,6 +144,8 @@ export class Mixer {
|
|
|
142
144
|
*/
|
|
143
145
|
chains = new Store();
|
|
144
146
|
version = 0;
|
|
147
|
+
/** What `prime` reads its clock, voice time and pass from: see `Primed`. */
|
|
148
|
+
primed = new Float64Array(3 /* Primed.SIZE */);
|
|
145
149
|
steps = new Steps();
|
|
146
150
|
/** Per subject, the voices whose `subjects` name it, in voice order. */
|
|
147
151
|
named = new Store();
|
|
@@ -205,6 +209,8 @@ export class Mixer {
|
|
|
205
209
|
bounded;
|
|
206
210
|
/** By slot, a stock array channel's in-place `lerp`. */
|
|
207
211
|
lerpsInto;
|
|
212
|
+
/** By slot, a stock scalar channel's arithmetic, which a fold applies without calling the channel. */
|
|
213
|
+
scalars;
|
|
208
214
|
/** What a fold with a locus in play gathers into, one per depth of folds under way. */
|
|
209
215
|
locusScratch = [];
|
|
210
216
|
locusDepth = 0;
|
|
@@ -215,6 +221,8 @@ export class Mixer {
|
|
|
215
221
|
bookers = null;
|
|
216
222
|
/** The fit a span being cued takes, which `cue` hands its voice. */
|
|
217
223
|
fitting = null;
|
|
224
|
+
/** What an arc being cued keeps, which `cue` hands its voice. */
|
|
225
|
+
arcing = null;
|
|
218
226
|
/** Every owner still in the mix, null until one is cued. */
|
|
219
227
|
owners = null;
|
|
220
228
|
/** With a history store, the key each subject is paged under; null without one. */
|
|
@@ -231,6 +239,7 @@ export class Mixer {
|
|
|
231
239
|
this.channels = this.names.map((k) => kit[k]);
|
|
232
240
|
this.bounded = this.channels.flatMap((c, i) => (c.bounds ? [i] : []));
|
|
233
241
|
this.lerpsInto = this.channels.map((c) => lerpInto(c));
|
|
242
|
+
this.scalars = this.channels.map((c) => scalarOp(c));
|
|
234
243
|
this.names.forEach((k, i) => {
|
|
235
244
|
this.slotOf.set(k, i);
|
|
236
245
|
});
|
|
@@ -372,6 +381,9 @@ export class Mixer {
|
|
|
372
381
|
this.fitting = null;
|
|
373
382
|
}
|
|
374
383
|
}
|
|
384
|
+
arc(spec) {
|
|
385
|
+
return cueArc(this, spec);
|
|
386
|
+
}
|
|
375
387
|
sync(timestamp) {
|
|
376
388
|
this.own('sync').sync(timestamp);
|
|
377
389
|
}
|
|
@@ -496,6 +508,9 @@ export class Mixer {
|
|
|
496
508
|
const motion = voice.motion;
|
|
497
509
|
return motion !== undefined && voice.elapsedAt(now) >= voice.latest && motion.landed();
|
|
498
510
|
}
|
|
511
|
+
close() {
|
|
512
|
+
unregister(this);
|
|
513
|
+
}
|
|
499
514
|
mute(opts) {
|
|
500
515
|
for (const voice of this.cued) {
|
|
501
516
|
voice.written = true;
|
|
@@ -505,12 +520,14 @@ export class Mixer {
|
|
|
505
520
|
}
|
|
506
521
|
drop(subject) {
|
|
507
522
|
this.frame = nextFrame();
|
|
508
|
-
const head = this
|
|
523
|
+
const head = headOf(this, subject);
|
|
509
524
|
// Nothing that kept this head, such as `pull`'s remembered list, may take it as current again.
|
|
510
525
|
if (head !== undefined)
|
|
511
526
|
unlink(this, head);
|
|
512
|
-
|
|
513
|
-
|
|
527
|
+
// A number a seek carried over is released even where its chain is not linked since.
|
|
528
|
+
const slot = this.chains.get(subject)?.slot ?? -1;
|
|
529
|
+
if (slot >= 0 && this.lanes !== null)
|
|
530
|
+
this.lanes.release(slot);
|
|
514
531
|
// A read ahead's copy reads the live mix's poses, and so hides one where the mix deletes it.
|
|
515
532
|
if (this.projecting && !(this.pose instanceof Masked))
|
|
516
533
|
this.pose = new Masked(this.pose);
|
|
@@ -521,40 +538,24 @@ export class Mixer {
|
|
|
521
538
|
// A record of it is only in a voice over every subject, one naming it, or one gone; a ramp out
|
|
522
539
|
// of a voice that does not name it is in `parters`, and a patch's own state in `motions`.
|
|
523
540
|
const kept = releasing(this);
|
|
524
|
-
// A motion patch's state is the live mix's alone: a read ahead cannot start a subject afresh in it.
|
|
525
|
-
const release = (motion) => {
|
|
526
|
-
if (!this.projecting)
|
|
527
|
-
motion.release(subject, ...kept);
|
|
528
|
-
else if (motion.holds(subject))
|
|
529
|
-
unplayable('a drop of a subject a motion patch holds');
|
|
530
|
-
};
|
|
531
|
-
const forget = (voice) => {
|
|
532
|
-
leaveVoice(this, voice, subject);
|
|
533
|
-
voice.subjects.delete(subject);
|
|
534
|
-
if (voice.motion !== undefined)
|
|
535
|
-
release(voice.motion);
|
|
536
|
-
voice.parts?.delete(subject);
|
|
537
|
-
voice.parted?.delete(subject);
|
|
538
|
-
voice.blend?.of.reads.delete(subject);
|
|
539
|
-
};
|
|
540
541
|
for (const voice of this.general)
|
|
541
|
-
|
|
542
|
+
this.forgetIn(voice, subject, kept);
|
|
542
543
|
const named = this.named.get(subject);
|
|
543
544
|
if (named !== undefined)
|
|
544
545
|
for (const voice of [...named])
|
|
545
|
-
|
|
546
|
+
this.forgetIn(voice, subject, kept);
|
|
546
547
|
const gone = goneIndex(this);
|
|
547
548
|
for (const voice of gone.general)
|
|
548
|
-
|
|
549
|
+
this.forgetIn(voice, subject, kept);
|
|
549
550
|
const goneNaming = gone.named.get(subject);
|
|
550
551
|
if (goneNaming !== undefined)
|
|
551
552
|
for (const voice of goneNaming)
|
|
552
|
-
|
|
553
|
+
this.forgetIn(voice, subject, kept);
|
|
553
554
|
if (this.owners !== null)
|
|
554
555
|
for (const voice of this.owners)
|
|
555
|
-
|
|
556
|
+
this.forgetIn(voice, subject, kept);
|
|
556
557
|
for (const voice of this.parters) {
|
|
557
|
-
|
|
558
|
+
this.forgetIn(voice, subject, kept);
|
|
558
559
|
if (voice.parts?.size === 0)
|
|
559
560
|
voice.parts = null;
|
|
560
561
|
if (voice.parted?.size === 0)
|
|
@@ -565,10 +566,30 @@ export class Mixer {
|
|
|
565
566
|
const strays = this.strays.get(subject);
|
|
566
567
|
if (strays !== undefined)
|
|
567
568
|
for (const motion of strays)
|
|
568
|
-
|
|
569
|
+
this.releaseIn(motion, subject, kept);
|
|
569
570
|
this.strays.delete(subject);
|
|
570
571
|
for (const motion of this.strayAll)
|
|
571
|
-
|
|
572
|
+
this.releaseIn(motion, subject, kept);
|
|
573
|
+
this.recordDrop(subject);
|
|
574
|
+
}
|
|
575
|
+
/** Lets `voice` go of a dropped subject. Methods rather than closures, so a drop makes neither. */
|
|
576
|
+
forgetIn(voice, subject, kept) {
|
|
577
|
+
leaveVoice(this, voice, subject);
|
|
578
|
+
voice.subjects.delete(subject);
|
|
579
|
+
if (voice.motion !== undefined)
|
|
580
|
+
this.releaseIn(voice.motion, subject, kept);
|
|
581
|
+
voice.parts?.delete(subject);
|
|
582
|
+
voice.parted?.delete(subject);
|
|
583
|
+
voice.blend?.of.reads.delete(subject);
|
|
584
|
+
}
|
|
585
|
+
// A motion patch's state is the live mix's alone: a read ahead cannot start a subject afresh in it.
|
|
586
|
+
releaseIn(motion, subject, kept) {
|
|
587
|
+
if (!this.projecting)
|
|
588
|
+
motion.release(subject, kept[0], kept[1], kept[2]);
|
|
589
|
+
else if (motion.holds(subject))
|
|
590
|
+
unplayable('a drop of a subject a motion patch holds');
|
|
591
|
+
}
|
|
592
|
+
recordDrop(subject) {
|
|
572
593
|
record(this, 'drop', (stage) => stage.mix(this)?.drop(subject));
|
|
573
594
|
}
|
|
574
595
|
touch(subject) {
|
|
@@ -680,6 +701,15 @@ function named(transport, name) {
|
|
|
680
701
|
if (taken.some((m) => m.name === name))
|
|
681
702
|
throw new Error(`blits: a mix named ${name} is already on this transport, or a seek could bring it back`);
|
|
682
703
|
}
|
|
704
|
+
/** Makes a mix the host asked for, with the registry's history where it names none, and lists it there. */
|
|
705
|
+
export function made(kit, opts, lanes) {
|
|
706
|
+
const { history, added } = historyFor(opts);
|
|
707
|
+
const m = new Mixer(kit, added ? { ...opts, history } : opts, lanes);
|
|
708
|
+
if (added)
|
|
709
|
+
gave(m.transport);
|
|
710
|
+
register(m);
|
|
711
|
+
return m;
|
|
712
|
+
}
|
|
683
713
|
/**
|
|
684
714
|
* The one that ships: every form on the CPU, probed per subject on demand.
|
|
685
715
|
*
|
|
@@ -689,7 +719,7 @@ export const mixer = {
|
|
|
689
719
|
name: 'mixer',
|
|
690
720
|
runs: new Set(['fn', 'keys', 'motion']),
|
|
691
721
|
create(kit, opts) {
|
|
692
|
-
return
|
|
722
|
+
return made(kit, opts, Lanes);
|
|
693
723
|
},
|
|
694
724
|
};
|
|
695
725
|
/**
|
package/dist/move.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import { settleArcs } from './arcs.js';
|
|
1
2
|
import { changed } from './chain.js';
|
|
2
3
|
import { elapsedWith } from './clock.js';
|
|
3
4
|
import { byId, popDue, schedule } from './due.js';
|
|
4
|
-
import { detour } from './everyone.js';
|
|
5
|
+
import { detour, outgrow } from './everyone.js';
|
|
5
6
|
import { beginFade, part, retire } from './fade.js';
|
|
6
7
|
import { expireGone, keepGone } from './gone.js';
|
|
7
8
|
import { begin } from './held.js';
|
|
@@ -129,8 +130,12 @@ function moveTo(mix, now) {
|
|
|
129
130
|
if (!mix.projecting)
|
|
130
131
|
schedule(mix, voice);
|
|
131
132
|
}
|
|
132
|
-
if (mix.owners !== null)
|
|
133
|
+
if (mix.owners !== null) {
|
|
133
134
|
lapse(mix, now);
|
|
135
|
+
settleArcs(mix, now);
|
|
136
|
+
}
|
|
137
|
+
if (mix.sharers.length > 0 && !mix.projecting)
|
|
138
|
+
outgrow(mix);
|
|
134
139
|
mix.stirred = false;
|
|
135
140
|
if (mix.retired.length > 0)
|
|
136
141
|
prune(mix);
|
package/dist/numbers.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ export declare const absent: unique symbol;
|
|
|
6
6
|
* them, so numbering one never keeps it alive.
|
|
7
7
|
*/
|
|
8
8
|
export declare class Numbers<I> {
|
|
9
|
-
private
|
|
9
|
+
private owner;
|
|
10
10
|
private next;
|
|
11
11
|
/** Numbers released for reuse; null until one is, as most patches never release one. */
|
|
12
12
|
private free;
|
|
@@ -26,6 +26,10 @@ export declare class Numbers<I> {
|
|
|
26
26
|
constructor(owner: {
|
|
27
27
|
forget(slot: number): void;
|
|
28
28
|
});
|
|
29
|
+
/** Hands the numbers to a new owner, which hears of every release from now on. */
|
|
30
|
+
adopt(owner: {
|
|
31
|
+
forget(slot: number): void;
|
|
32
|
+
}): void;
|
|
29
33
|
/** One past the highest number handed out, so a loop over 0..size meets every live one. */
|
|
30
34
|
get size(): number;
|
|
31
35
|
take(subject: I): number;
|
package/dist/numbers.js
CHANGED
|
@@ -29,6 +29,10 @@ export class Numbers {
|
|
|
29
29
|
constructor(owner) {
|
|
30
30
|
this.owner = owner;
|
|
31
31
|
}
|
|
32
|
+
/** Hands the numbers to a new owner, which hears of every release from now on. */
|
|
33
|
+
adopt(owner) {
|
|
34
|
+
this.owner = owner;
|
|
35
|
+
}
|
|
32
36
|
/** One past the highest number handed out, so a loop over 0..size meets every live one. */
|
|
33
37
|
get size() {
|
|
34
38
|
return this.next;
|
package/dist/owner.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { arcShare } from './arcs.js';
|
|
1
2
|
import { backWith, clampWeight, elapsedWith, envelope, timeWith } from './clock.js';
|
|
2
3
|
/** What an owner is cued with in place of a patch: it writes nothing, so no fold ever meets it. */
|
|
3
4
|
export const ownerPatch = Object.freeze({
|
|
@@ -128,8 +129,12 @@ export function ownWeight(owner, now, reduced, base) {
|
|
|
128
129
|
/** What every owner above a voice multiplies into its weight. */
|
|
129
130
|
export function ownersWeight(voice, now, reduced, base) {
|
|
130
131
|
let w = 1;
|
|
131
|
-
|
|
132
|
+
let child = voice;
|
|
133
|
+
for (let o = voice.owner; o !== null; child = o, o = o.owner) {
|
|
132
134
|
w *= ownWeight(o, now, reduced, base);
|
|
135
|
+
if (o.arc !== null)
|
|
136
|
+
w *= arcShare(o, child, now, reduced);
|
|
137
|
+
}
|
|
133
138
|
return w;
|
|
134
139
|
}
|
|
135
140
|
/**
|
package/dist/place.d.ts
CHANGED
|
@@ -10,6 +10,8 @@ export declare function mixAt<I, O>(mix: Mixer<I, O>, u: number): number;
|
|
|
10
10
|
export declare function pin<I, O>(mix: Mixer<I, O>, voice: Voice<I, O>, u: number): void;
|
|
11
11
|
/** Puts each pending voice's start, given in host time, where the mix's rate now puts it. */
|
|
12
12
|
export declare function repin<I, O>(mix: Mixer<I, O>): void;
|
|
13
|
+
/** Moves a pending voice's start, its clock with it; false where it is already there. */
|
|
14
|
+
export declare function startAt<I, O>(mix: Mixer<I, O>, voice: Voice<I, O>, start: number): boolean;
|
|
13
15
|
/** Refuses a placement that names a mark twice on one side, or that waits on itself. */
|
|
14
16
|
export declare function checkPlacement<I, O>(mix: Mixer<I, O>, spec: VoiceSpec<I, O>, anchor: Placement, owner: Voice<I, O> | null): void;
|
|
15
17
|
/**
|
|
@@ -18,3 +20,9 @@ export declare function checkPlacement<I, O>(mix: Mixer<I, O>, spec: VoiceSpec<I
|
|
|
18
20
|
* last gave. Repeated so a chain of anchors settles in one sync.
|
|
19
21
|
*/
|
|
20
22
|
export declare function place<I, O>(mix: Mixer<I, O>): void;
|
|
23
|
+
/**
|
|
24
|
+
* The time an anchor answers on its voice's owner's clock, or undefined while its target has none.
|
|
25
|
+
* A time before the owner starts answers `before`: undefined, so a start waits, or -Infinity for a
|
|
26
|
+
* floor, which is no bound at all.
|
|
27
|
+
*/
|
|
28
|
+
export declare function resolve<I, O>(mix: Mixer<I, O>, a: number | NonNullable<Placement['start']>, self: Voice<I, O>, before?: number): number | undefined;
|
package/dist/place.js
CHANGED
|
@@ -5,6 +5,7 @@ import { noted } from './history.js';
|
|
|
5
5
|
import { markOf } from './marks.js';
|
|
6
6
|
import { mixTime, ownerReading } from './owner.js';
|
|
7
7
|
import { holders, scored, scoredGone, scoreVersion, unhold } from './scored.js';
|
|
8
|
+
import { refloor } from './spans.js';
|
|
8
9
|
import { none } from './voice.js';
|
|
9
10
|
import { warnOnce } from './warn.js';
|
|
10
11
|
/** What a voice's own owner's clock reads now: the mix clock for a voice no owner holds. */
|
|
@@ -35,7 +36,7 @@ export function repin(mix) {
|
|
|
35
36
|
}
|
|
36
37
|
}
|
|
37
38
|
/** Moves a pending voice's start, its clock with it; false where it is already there. */
|
|
38
|
-
function startAt(mix, voice, start) {
|
|
39
|
+
export function startAt(mix, voice, start) {
|
|
39
40
|
if (start === voice.start)
|
|
40
41
|
return false;
|
|
41
42
|
voice.start = start;
|
|
@@ -145,18 +146,23 @@ export function place(mix) {
|
|
|
145
146
|
}
|
|
146
147
|
}
|
|
147
148
|
if (!moved)
|
|
148
|
-
|
|
149
|
+
break;
|
|
149
150
|
}
|
|
151
|
+
refloor(mix);
|
|
150
152
|
}
|
|
151
|
-
/**
|
|
152
|
-
|
|
153
|
+
/**
|
|
154
|
+
* The time an anchor answers on its voice's owner's clock, or undefined while its target has none.
|
|
155
|
+
* A time before the owner starts answers `before`: undefined, so a start waits, or -Infinity for a
|
|
156
|
+
* floor, which is no bound at all.
|
|
157
|
+
*/
|
|
158
|
+
export function resolve(mix, a, self, before) {
|
|
153
159
|
const o = self.owner;
|
|
154
160
|
if (typeof a === 'number')
|
|
155
161
|
return o === null ? mixAt(mix, a - mix.offset) : a;
|
|
156
162
|
if ('all' in a) {
|
|
157
163
|
let latest = Number.NEGATIVE_INFINITY;
|
|
158
164
|
for (const m of a.all) {
|
|
159
|
-
const t = member(mix, m, self);
|
|
165
|
+
const t = member(mix, m, self, before);
|
|
160
166
|
if (t === undefined)
|
|
161
167
|
return undefined;
|
|
162
168
|
latest = Math.max(latest, t);
|
|
@@ -169,7 +175,7 @@ function resolve(mix, a, self) {
|
|
|
169
175
|
let earliest = Number.POSITIVE_INFINITY;
|
|
170
176
|
let unknown = false;
|
|
171
177
|
for (const m of a.any) {
|
|
172
|
-
const t = member(mix, m, self);
|
|
178
|
+
const t = member(mix, m, self, before);
|
|
173
179
|
if (t === undefined)
|
|
174
180
|
unknown = true;
|
|
175
181
|
else
|
|
@@ -205,14 +211,16 @@ function resolve(mix, a, self) {
|
|
|
205
211
|
if (o === null)
|
|
206
212
|
return t + by;
|
|
207
213
|
const local = ownerReading(o, t);
|
|
214
|
+
if (local === Number.NEGATIVE_INFINITY)
|
|
215
|
+
return before;
|
|
208
216
|
return Number.isFinite(local) ? local + by : undefined;
|
|
209
217
|
}
|
|
210
218
|
/**
|
|
211
219
|
* What one member of a join answers, or the time it last gave once its target has left, as a lone
|
|
212
220
|
* anchor keeps its voice where its target last put it.
|
|
213
221
|
*/
|
|
214
|
-
function member(mix, a, self) {
|
|
215
|
-
const t = resolve(mix, a, self);
|
|
222
|
+
function member(mix, a, self, before) {
|
|
223
|
+
const t = resolve(mix, a, self, before);
|
|
216
224
|
if (t === undefined)
|
|
217
225
|
return self.answers?.get(a);
|
|
218
226
|
if (!mix.projecting) {
|
package/dist/project.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ownBlends } from './blend.js';
|
|
2
|
-
import { heldAt } from './chain.js';
|
|
2
|
+
import { headOf, heldAt } from './chain.js';
|
|
3
3
|
import { stepOf } from './clock.js';
|
|
4
4
|
import { open, sighted, weightAt } from './everyone.js';
|
|
5
5
|
import { fadesAt } from './fade.js';
|
|
@@ -362,7 +362,7 @@ function recordOf(mix, voice, subject) {
|
|
|
362
362
|
// A voice sharing one record has that for every subject a chain has asked it about, pointed at
|
|
363
363
|
// the subject for the caller to copy from.
|
|
364
364
|
if (voice.sharing) {
|
|
365
|
-
const slot = mix
|
|
365
|
+
const slot = headOf(mix, subject)?.slot ?? -1;
|
|
366
366
|
if (!sighted(voice, slot))
|
|
367
367
|
return undefined;
|
|
368
368
|
const all = voice.everyone;
|
|
@@ -373,7 +373,7 @@ function recordOf(mix, voice, subject) {
|
|
|
373
373
|
const held = voice.subjects.get(subject);
|
|
374
374
|
if (held !== undefined || voice.unreachedBits === null)
|
|
375
375
|
return held;
|
|
376
|
-
return unreached(voice, mix
|
|
376
|
+
return unreached(voice, headOf(mix, subject)?.slot ?? -1)
|
|
377
377
|
? voice.unreached
|
|
378
378
|
: undefined;
|
|
379
379
|
}
|
package/dist/qualify.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Lanes } from './lanes.js';
|
|
2
|
+
import type { Channel } from './types.js';
|
|
2
3
|
import type { Voice } from './voice.js';
|
|
3
4
|
/** What qualifying needs of one voice: whether its patch and spec can run on a lane, and its channels. */
|
|
4
5
|
export interface Candidate {
|
|
@@ -16,6 +17,15 @@ export declare function qualify(numeric: readonly boolean[], voices: readonly Ca
|
|
|
16
17
|
voices: Set<number>;
|
|
17
18
|
};
|
|
18
19
|
export declare function requalify<I, O>(lanes: Lanes<I, O>, version: number): void;
|
|
20
|
+
/** Whether `keepCallsOff` applies; settable for bench/frame.mjs. */
|
|
21
|
+
export declare let callsOff: boolean;
|
|
22
|
+
export declare function setCallsOff(on: boolean): void;
|
|
23
|
+
/**
|
|
24
|
+
* A voice whose patch a lane would call, as a fn voice's (no tween, keys or motion), writing only
|
|
25
|
+
* arrays: a crowd writing a number reads 17% to 25% faster on lanes (bench/frame.mjs `named` under
|
|
26
|
+
* CALLS_OFF, msb-uai, 2026-10-21).
|
|
27
|
+
*/
|
|
28
|
+
export declare function calls<I, O>(v: Voice<I, O>, channels: readonly Channel<unknown>[]): boolean;
|
|
19
29
|
/**
|
|
20
30
|
* A voice starts playing on a lane or crowd: its epoch. Every subject meets a voice over all of
|
|
21
31
|
* them on its next probe; a voice naming its subjects marks only those.
|
package/dist/qualify.js
CHANGED
|
@@ -53,6 +53,8 @@ export function requalify(lanes, version) {
|
|
|
53
53
|
const numeric = host.channels.map((c) => numericOf(c) !== undefined);
|
|
54
54
|
// A voice writing nothing has no lane to fill, so it stays where its calls are made.
|
|
55
55
|
const fits = present.map((v) => v.slots.length > 0 && host.fits(v));
|
|
56
|
+
if (callsOff)
|
|
57
|
+
keepCallsOff(present, fits, host.channels);
|
|
56
58
|
const candidates = () => present.map((v, i) => ({ id: v.id, fits: fits[i], slots: v.slots }));
|
|
57
59
|
let { channels, voices } = qualify(numeric, candidates());
|
|
58
60
|
// A locus folds its members together, so they run on lanes all together or not at all.
|
|
@@ -193,6 +195,54 @@ export function requalify(lanes, version) {
|
|
|
193
195
|
}
|
|
194
196
|
regroup(lanes, members);
|
|
195
197
|
}
|
|
198
|
+
/** Whether `keepCallsOff` applies; settable for bench/frame.mjs. */
|
|
199
|
+
export let callsOff = true;
|
|
200
|
+
export function setCallsOff(on) {
|
|
201
|
+
callsOff = on;
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* A voice whose patch a lane would call, as a fn voice's (no tween, keys or motion), writing only
|
|
205
|
+
* arrays: a crowd writing a number reads 17% to 25% faster on lanes (bench/frame.mjs `named` under
|
|
206
|
+
* CALLS_OFF, msb-uai, 2026-10-21).
|
|
207
|
+
*/
|
|
208
|
+
export function calls(v, channels) {
|
|
209
|
+
return (v.built === null &&
|
|
210
|
+
v.motion === undefined &&
|
|
211
|
+
v.slots.every((s) => vector(channels[s])));
|
|
212
|
+
}
|
|
213
|
+
/** A channel folding arrays with a rest, not `last`: what the rule was timed on. */
|
|
214
|
+
function vector(channel) {
|
|
215
|
+
return Array.isArray(channel.rest) && numericOf(channel)?.op !== 'last';
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Leaves off lanes each voice calling its patch where no other fitting voice writes any of its
|
|
219
|
+
* channels, but a crowd's other voices: a lane calls such a patch once a subject as the general
|
|
220
|
+
* path does, and its fill walks the subjects a second time (bench/frame.mjs `fns`, 2.58 ms a frame
|
|
221
|
+
* on lanes against 1.07 off, studio, 2026-10-21). Two voices over many subjects on one channel
|
|
222
|
+
* fold faster on lanes (`tweenfn` with three voices).
|
|
223
|
+
*/
|
|
224
|
+
function keepCallsOff(present, fits, channels) {
|
|
225
|
+
const others = new Array(channels.length).fill(false);
|
|
226
|
+
const wide = new Array(channels.length).fill(false);
|
|
227
|
+
present.forEach((v, i) => {
|
|
228
|
+
if (!fits[i])
|
|
229
|
+
return;
|
|
230
|
+
const crowd = crowdable(v);
|
|
231
|
+
if (!calls(v, channels))
|
|
232
|
+
for (const s of v.slots)
|
|
233
|
+
others[s] = true;
|
|
234
|
+
else if (!crowd)
|
|
235
|
+
for (const s of v.slots) {
|
|
236
|
+
if (wide[s])
|
|
237
|
+
others[s] = true;
|
|
238
|
+
wide[s] = true;
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
present.forEach((v, i) => {
|
|
242
|
+
if (fits[i] && calls(v, channels) && !v.slots.some((s) => others[s]))
|
|
243
|
+
fits[i] = false;
|
|
244
|
+
});
|
|
245
|
+
}
|
|
196
246
|
/** Whether qualifying laned exactly the channels laned now. */
|
|
197
247
|
function sameChannels(lanes, channels) {
|
|
198
248
|
const by = lanes.bySlot;
|
|
@@ -273,7 +323,8 @@ export function retouch(lanes) {
|
|
|
273
323
|
return qualify();
|
|
274
324
|
const laned = v.slots.some((slot) => lanes.bySlot[slot] !== undefined);
|
|
275
325
|
const fits = v.slots.length > 0 && lanes.host.fits(v);
|
|
276
|
-
|
|
326
|
+
// As `keepCallsOff`: with none of its channels laned, no fitting voice but its like writes them.
|
|
327
|
+
if (!laned && (!fits || (callsOff && crowdable(v) && calls(v, lanes.host.channels)))) {
|
|
277
328
|
lanes.whole = false;
|
|
278
329
|
continue;
|
|
279
330
|
}
|
package/dist/reading.d.ts
CHANGED
|
@@ -7,7 +7,8 @@ export declare const reading: {
|
|
|
7
7
|
live: boolean;
|
|
8
8
|
/**
|
|
9
9
|
* The earliest voice time a read may still ask for; a patch keeping a history may drop what is
|
|
10
|
-
* older. Infinity where the mix keeps none, so only what plays now is kept.
|
|
10
|
+
* older. Infinity where the mix keeps none, so only what plays now is kept. Set before a motion
|
|
11
|
+
* voice's read alone, so another patch finds whatever the last one left.
|
|
11
12
|
*/
|
|
12
13
|
horizon: number;
|
|
13
14
|
/**
|
package/dist/reading.js
CHANGED
|
@@ -7,7 +7,8 @@ export const reading = {
|
|
|
7
7
|
live: true,
|
|
8
8
|
/**
|
|
9
9
|
* The earliest voice time a read may still ask for; a patch keeping a history may drop what is
|
|
10
|
-
* older. Infinity where the mix keeps none, so only what plays now is kept.
|
|
10
|
+
* older. Infinity where the mix keeps none, so only what plays now is kept. Set before a motion
|
|
11
|
+
* voice's read alone, so another patch finds whatever the last one left.
|
|
11
12
|
*/
|
|
12
13
|
horizon: Number.POSITIVE_INFINITY,
|
|
13
14
|
/**
|
package/dist/record.d.ts
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
import type { Subject } from './voice.js';
|
|
2
|
+
/** A record with every field written out, optional ones too, as every record in a mix is built. */
|
|
3
|
+
export type WholeRecord = {
|
|
4
|
+
[K in Field]: Subject<unknown>[K];
|
|
5
|
+
};
|
|
6
|
+
type Field = keyof Subject<unknown>;
|
|
2
7
|
/**
|
|
3
8
|
* A subject's record for `voice` holding every field one can come to hold, in one order, so every
|
|
4
9
|
* record in a mix shares one hidden class: a record built elsewhere, or given `base`, `snaps` or
|
|
@@ -7,3 +12,4 @@ import type { Subject } from './voice.js';
|
|
|
7
12
|
* does not inline would box.
|
|
8
13
|
*/
|
|
9
14
|
export declare function record(voice: object | null, reaches: boolean, state: unknown): Subject<unknown>;
|
|
15
|
+
export {};
|
package/dist/record.js
CHANGED
package/dist/restored.d.ts
CHANGED
|
@@ -2,43 +2,92 @@ import type { Leavings } from './leavings.js';
|
|
|
2
2
|
import { Store } from './store.js';
|
|
3
3
|
import type { Cut } from './transport.js';
|
|
4
4
|
import type { Subject } from './voice.js';
|
|
5
|
+
/**
|
|
6
|
+
* The seeks back after the one a record was last put back, written or deleted at, which every
|
|
7
|
+
* record stamped then shares. The next seek fills it; a later seek to as early a moment joins it
|
|
8
|
+
* to its own, so the root it reaches holds the earliest moment sought since. Only records and the
|
|
9
|
+
* stamps joined beneath it hold it, so it is collected with the last record that can read it.
|
|
10
|
+
*/
|
|
11
|
+
/** A moment sought, the end of frame `seq`. */
|
|
12
|
+
export interface Moment {
|
|
13
|
+
readonly t: number;
|
|
14
|
+
readonly seq: number;
|
|
15
|
+
}
|
|
16
|
+
export interface Stamp extends Moment {
|
|
17
|
+
/** On a root: the earliest moment sought since, and the frame that ended. */
|
|
18
|
+
t: number;
|
|
19
|
+
seq: number;
|
|
20
|
+
/** The stamp this one was joined to, null on a root. */
|
|
21
|
+
up: Stamp | null;
|
|
22
|
+
/** A bound on how deep the stamps joined beneath it go, so joining the lower keeps paths short. */
|
|
23
|
+
rank: number;
|
|
24
|
+
/** The records subjects left with after this seek's moment, and where it cut; null for none. */
|
|
25
|
+
left: Leavings<unknown> | null;
|
|
26
|
+
cut: Cut | null;
|
|
27
|
+
/** The store that made it; null on `gone`. */
|
|
28
|
+
readonly of: object | null;
|
|
29
|
+
}
|
|
5
30
|
/**
|
|
6
31
|
* A voice's records once a seek has gone back: each subject's is put back from the record it had,
|
|
7
32
|
* 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
|
|
9
|
-
*
|
|
10
|
-
* seek back cuts what came after it and a later seek ahead of that moment must not
|
|
33
|
+
* It is one store however many seeks there are: by subject it keeps the stamp of the seek its
|
|
34
|
+
* record was last put back at, and a record behind is put back to the earliest moment sought
|
|
35
|
+
* since, as a seek back cuts what came after it and a later seek ahead of that moment must not
|
|
36
|
+
* read past it.
|
|
11
37
|
*/
|
|
12
38
|
export declare class Restored<I> extends Store<I, Subject<unknown>> {
|
|
13
39
|
private readonly own;
|
|
14
40
|
private readonly now;
|
|
15
41
|
private readonly make;
|
|
16
|
-
/**
|
|
17
|
-
private
|
|
18
|
-
/**
|
|
42
|
+
/** What a record put back now is stamped with, filled by the next seek. */
|
|
43
|
+
private stamp;
|
|
44
|
+
/** What a record never stamped reads as: the first seek's. */
|
|
45
|
+
private readonly first;
|
|
46
|
+
/**
|
|
47
|
+
* By subject, the stamp its record was last put back, written or deleted at; none reads as
|
|
48
|
+
* `first`. A record that reaches its subject is its own, and holds it as `restored` instead,
|
|
49
|
+
* sparing a write here per subject a seek puts back; one that does not may stand for many.
|
|
50
|
+
*/
|
|
19
51
|
private readonly at;
|
|
20
52
|
/**
|
|
21
|
-
*
|
|
22
|
-
*
|
|
53
|
+
* The roots, each moment earlier than the next, held weakly: one no record can reach is never
|
|
54
|
+
* read again, and those are cleared out once the list doubles.
|
|
55
|
+
*/
|
|
56
|
+
private roots;
|
|
57
|
+
private kept;
|
|
58
|
+
/**
|
|
59
|
+
* Stamps made a batch ahead of their seeks: one made at its seek is young when every old record
|
|
60
|
+
* is written with it, and V8 notes each such write for its next scavenge.
|
|
23
61
|
*/
|
|
24
|
-
private
|
|
25
|
-
|
|
26
|
-
private futures;
|
|
62
|
+
private ready;
|
|
63
|
+
private aging;
|
|
27
64
|
/** The latest seek's moment. */
|
|
28
|
-
|
|
65
|
+
top: number;
|
|
29
66
|
/**
|
|
30
67
|
* `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 `
|
|
32
|
-
*
|
|
33
|
-
*
|
|
68
|
+
* that has not left since and is not reached has there, as a seek to `at` takes it. `make` puts
|
|
69
|
+
* a record back as it stood at `at`, undefined where it had none then; `mine` says `live` is the
|
|
70
|
+
* record this store held, which nothing else does.
|
|
34
71
|
*/
|
|
35
|
-
constructor(own: Store<I, Subject<unknown>>, now: (key: I, held: Subject<unknown> | undefined,
|
|
72
|
+
constructor(own: Store<I, Subject<unknown>>, now: (key: I, held: Subject<unknown> | undefined, at: Moment) => Subject<unknown> | undefined, make: (live: Subject<unknown>, at: Moment, mine: boolean) => Subject<unknown> | undefined);
|
|
36
73
|
/** A seek back to `t`: `left` has the records subjects left with after `cut`, null for none. */
|
|
37
74
|
back(t: number, cut: Cut, left: Leavings<I> | null): void;
|
|
38
|
-
/** The
|
|
39
|
-
private
|
|
75
|
+
/** The next seek's stamp, from a batch made a batch of seeks ago, twice as many each time to 64. */
|
|
76
|
+
private fresh;
|
|
77
|
+
/**
|
|
78
|
+
* Whether a reaching record stamped by this store is still its record for the key it was found
|
|
79
|
+
* under: every record let go is stamped `gone`, copies are stamped with nothing, and this turns
|
|
80
|
+
* false if a stamped record is set again, which could put it under a second key.
|
|
81
|
+
*/
|
|
82
|
+
private sure;
|
|
40
83
|
get(key: I): Subject<unknown> | undefined;
|
|
84
|
+
/** A chain's record for a subject, found again without a lookup where this store stamped it. */
|
|
85
|
+
find(key: I, guess: Subject<unknown>): Subject<unknown> | undefined;
|
|
86
|
+
/** `get`, given what `own` holds for `key`. */
|
|
87
|
+
private from;
|
|
41
88
|
set(key: I, value: Subject<unknown>): void;
|
|
42
89
|
delete(key: I): void;
|
|
90
|
+
/** Lets go of the record held for `key`, stamping it `gone`. */
|
|
91
|
+
private shed;
|
|
43
92
|
clear(): void;
|
|
44
93
|
}
|