@msb235/blits 0.7.1 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +180 -0
- package/README.md +22 -4
- package/dist/book.js +4 -4
- package/dist/chain.d.ts +1 -1
- package/dist/chain.js +57 -54
- package/dist/channels.d.ts +26 -5
- package/dist/channels.js +12 -8
- package/dist/clock.d.ts +8 -1
- package/dist/clock.js +14 -3
- package/dist/clone.d.ts +3 -0
- package/dist/clone.js +16 -1
- package/dist/closed.d.ts +1 -1
- package/dist/closed.js +3 -2
- package/dist/color.d.ts +0 -11
- package/dist/color.js +4 -24
- package/dist/columns.js +0 -1
- package/dist/copyout.d.ts +6 -0
- package/dist/copyout.js +38 -0
- package/dist/crowd.d.ts +7 -5
- package/dist/crowd.js +32 -18
- package/dist/cue.d.ts +3 -1
- package/dist/cue.js +31 -20
- package/dist/departed.d.ts +34 -0
- package/dist/departed.js +73 -0
- package/dist/easing.d.ts +5 -0
- package/dist/easing.js +31 -2
- package/dist/everyone.d.ts +56 -0
- package/dist/everyone.js +221 -0
- package/dist/fade.d.ts +8 -1
- package/dist/fade.js +34 -6
- package/dist/fill.d.ts +22 -13
- package/dist/fill.js +104 -41
- package/dist/fit.js +2 -1
- package/dist/fold.d.ts +37 -0
- package/dist/fold.js +213 -28
- package/dist/gather.d.ts +15 -9
- package/dist/gather.js +60 -25
- package/dist/gone.js +10 -0
- package/dist/handle.d.ts +16 -5
- package/dist/handle.js +56 -6
- package/dist/held.d.ts +8 -5
- package/dist/held.js +56 -58
- package/dist/history.d.ts +17 -13
- package/dist/history.js +47 -28
- package/dist/hosts.js +55 -8
- package/dist/index.d.ts +9 -4
- package/dist/index.js +5 -2
- package/dist/keyfill.d.ts +7 -0
- package/dist/keyfill.js +124 -0
- package/dist/keyrows.d.ts +1 -0
- package/dist/keyrows.js +1 -1
- package/dist/lane.d.ts +32 -2
- package/dist/lane.js +5 -0
- package/dist/lanes.d.ts +32 -8
- package/dist/lanes.js +69 -37
- package/dist/leavings.d.ts +26 -0
- package/dist/leavings.js +70 -0
- package/dist/meet.js +14 -8
- package/dist/mixer.d.ts +39 -7
- package/dist/mixer.js +50 -10
- package/dist/motion.d.ts +2 -0
- package/dist/motion.js +4 -1
- package/dist/motions.d.ts +22 -15
- package/dist/motions.js +40 -34
- package/dist/move.js +23 -16
- package/dist/moved.d.ts +11 -0
- package/dist/moved.js +29 -0
- package/dist/origin.d.ts +19 -0
- package/dist/origin.js +68 -0
- package/dist/owed.d.ts +4 -4
- package/dist/owed.js +8 -8
- package/dist/owner.d.ts +1 -1
- package/dist/owner.js +2 -2
- package/dist/pace.d.ts +13 -5
- package/dist/pace.js +21 -8
- package/dist/pack.d.ts +3 -1
- package/dist/pack.js +15 -24
- package/dist/paging.d.ts +8 -2
- package/dist/paging.js +44 -4
- package/dist/patch.d.ts +14 -9
- package/dist/patch.js +83 -21
- package/dist/place.js +18 -3
- package/dist/project.d.ts +12 -1
- package/dist/project.js +101 -83
- package/dist/pull.d.ts +2 -0
- package/dist/pull.js +8 -0
- package/dist/qualify.js +10 -2
- package/dist/reading.d.ts +5 -0
- package/dist/reading.js +5 -0
- package/dist/record.d.ts +9 -0
- package/dist/record.js +42 -0
- package/dist/relink.d.ts +11 -5
- package/dist/relink.js +7 -5
- package/dist/restored.d.ts +41 -0
- package/dist/restored.js +91 -0
- package/dist/revive.d.ts +10 -3
- package/dist/revive.js +31 -23
- package/dist/roster.d.ts +11 -0
- package/dist/roster.js +46 -0
- package/dist/rotation.d.ts +29 -0
- package/dist/rotation.js +91 -0
- package/dist/rows.d.ts +5 -5
- package/dist/rows.js +5 -5
- package/dist/sample.d.ts +3 -2
- package/dist/sample.js +32 -15
- package/dist/seek.js +97 -114
- package/dist/shared.d.ts +6 -0
- package/dist/shared.js +15 -0
- package/dist/signals.d.ts +24 -3
- package/dist/signals.js +50 -11
- package/dist/spans.js +7 -0
- package/dist/stack.d.ts +11 -0
- package/dist/stack.js +47 -0
- package/dist/standing.d.ts +10 -0
- package/dist/standing.js +79 -0
- package/dist/store.d.ts +3 -3
- package/dist/store.js +3 -3
- package/dist/strays.js +3 -0
- package/dist/tape.d.ts +8 -5
- package/dist/tape.js +21 -13
- package/dist/testing.d.ts +35 -0
- package/dist/testing.js +116 -0
- package/dist/touch.d.ts +6 -0
- package/dist/touch.js +28 -0
- package/dist/transport.d.ts +49 -6
- package/dist/transport.js +89 -19
- package/dist/turns.d.ts +18 -0
- package/dist/turns.js +72 -0
- package/dist/tweened.js +1 -1
- package/dist/types/channel.d.ts +46 -0
- package/dist/types/channel.js +1 -0
- package/dist/types/history.d.ts +139 -0
- package/dist/types/history.js +1 -0
- package/dist/types/mix.d.ts +336 -0
- package/dist/types/mix.js +1 -0
- package/dist/types/patch.d.ts +187 -0
- package/dist/types/patch.js +1 -0
- package/dist/types/place.d.ts +157 -0
- package/dist/types/place.js +1 -0
- package/dist/types/transport.d.ts +71 -0
- package/dist/types/transport.js +1 -0
- package/dist/types/voice.d.ts +323 -0
- package/dist/types/voice.js +1 -0
- package/dist/types.d.ts +7 -1162
- package/dist/unpage.d.ts +2 -2
- package/dist/unpage.js +53 -25
- package/dist/voice.d.ts +90 -22
- package/dist/voice.js +105 -43
- package/dist/wave.d.ts +42 -0
- package/dist/wave.js +62 -0
- package/dist/weigh.js +1 -1
- package/docs/schema.html +42 -36
- package/package.json +8 -3
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type Lane } from './lane.js';
|
|
2
|
+
import type { Lanes } from './lanes.js';
|
|
3
|
+
/**
|
|
4
|
+
* A keys voice's fill outside a locus, its weight a number: what `one` does for each subject, in
|
|
5
|
+
* one loop, so no weight or time crosses a call as a boxed double per subject.
|
|
6
|
+
*/
|
|
7
|
+
export declare function runKeys<I, O>(lanes: Lanes<I, O>, lane: Lane<I, O>): void;
|
package/dist/keyfill.js
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { foldNumber } from './channels.js';
|
|
2
|
+
import { clampWeight, frozenTime, phaseAt, same, weighed } from './clock.js';
|
|
3
|
+
import { shortWay } from './turns.js';
|
|
4
|
+
/**
|
|
5
|
+
* A keys voice's fill outside a locus, its weight a number: what `one` does for each subject, in
|
|
6
|
+
* one loop, so no weight or time crosses a call as a boxed double per subject.
|
|
7
|
+
*/
|
|
8
|
+
export function runKeys(lanes, lane) {
|
|
9
|
+
const voice = lane.voice;
|
|
10
|
+
const data = lane.data;
|
|
11
|
+
const per = lanes.per;
|
|
12
|
+
const list = lane.list;
|
|
13
|
+
const records = lane.records;
|
|
14
|
+
const chans = lane.chans;
|
|
15
|
+
const read = lane.values;
|
|
16
|
+
const n = chans.length;
|
|
17
|
+
const fills = lanes.fills;
|
|
18
|
+
const lately = lanes.lately;
|
|
19
|
+
const elapsedNow = voice.elapsedAt(lanes.now);
|
|
20
|
+
const duration = voice.duration;
|
|
21
|
+
const passes = voice.passes;
|
|
22
|
+
const before = voice.freezesBefore;
|
|
23
|
+
const after = voice.freezesAfter;
|
|
24
|
+
const span = voice.span;
|
|
25
|
+
const flat = lane.flat;
|
|
26
|
+
const parts = voice.parts !== null;
|
|
27
|
+
const whole = clampWeight(voice.weight);
|
|
28
|
+
let width = 0;
|
|
29
|
+
for (let i = 0; i < n; i++)
|
|
30
|
+
width += chans[i].axes;
|
|
31
|
+
if (lane.nums.length < width)
|
|
32
|
+
lane.nums = new Float64Array(width);
|
|
33
|
+
if (lane.kinds.length < n)
|
|
34
|
+
lane.kinds = new Uint8Array(n);
|
|
35
|
+
const nums = lane.nums;
|
|
36
|
+
const kinds = lane.kinds;
|
|
37
|
+
for (let p = 0; p < list.length; p++) {
|
|
38
|
+
const slot = list[p];
|
|
39
|
+
const o = p * 8 /* Row.STRIDE */;
|
|
40
|
+
if (per[slot * 6 /* Per.SLOT */ + 5 /* Per.LANE_FILL */] === fills - 1)
|
|
41
|
+
data[o + 3 /* Row.PROBED */] = data[o + 2 /* Row.WEIGHT */];
|
|
42
|
+
if (lately >= 0 && lanes.unread(slot))
|
|
43
|
+
continue;
|
|
44
|
+
const elapsed = frozenTime(elapsedNow - data[o + 0 /* Row.DELAY */], before, after, span);
|
|
45
|
+
if (!(elapsed >= 0)) {
|
|
46
|
+
data[o + 2 /* Row.WEIGHT */] = 0;
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
if (!flat) {
|
|
50
|
+
const since = data[o + 1 /* Row.SINCE */];
|
|
51
|
+
if (!lane.weighed || !same(since, lane.weighedSince)) {
|
|
52
|
+
lane.fade = lanes.host.envelope(voice, since);
|
|
53
|
+
lane.weighed = true;
|
|
54
|
+
lane.weighedSince = since;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
const w = flat && !parts ? whole : weighed(voice.weight, lane.fade, lanes.parting(voice, slot));
|
|
58
|
+
data[o + 2 /* Row.WEIGHT */] = w;
|
|
59
|
+
if (!lane.read || !same(elapsed, lane.readAt)) {
|
|
60
|
+
lanes.readKeyed(voice, phaseAt(elapsed, duration, passes), lane.delta, lane.scratch);
|
|
61
|
+
lane.read = true;
|
|
62
|
+
lane.readAt = elapsed;
|
|
63
|
+
lanes.gather(lane, lane.delta);
|
|
64
|
+
flatten(chans, read, nums, kinds);
|
|
65
|
+
}
|
|
66
|
+
if (!(w > 0))
|
|
67
|
+
continue;
|
|
68
|
+
let k = 0;
|
|
69
|
+
for (let i = 0; i < n; i++) {
|
|
70
|
+
const ch = chans[i];
|
|
71
|
+
const axes = ch.axes;
|
|
72
|
+
const kind = kinds[i];
|
|
73
|
+
if (kind === 1 /* Kind.NUMBERS */) {
|
|
74
|
+
const values = ch.values;
|
|
75
|
+
const base = slot * axes;
|
|
76
|
+
for (let a = 0; a < axes; a++)
|
|
77
|
+
values[base + a] = foldNumber(ch.op, values[base + a], nums[k + a], w);
|
|
78
|
+
}
|
|
79
|
+
else if (kind === 2 /* Kind.GATED */) {
|
|
80
|
+
lanes.folding = lane;
|
|
81
|
+
lanes.rec = records[p] ?? null;
|
|
82
|
+
lanes.arg[0 /* Arg.WEIGHT */] = w;
|
|
83
|
+
lanes.foldInto(ch, slot, read[i]);
|
|
84
|
+
}
|
|
85
|
+
k += axes;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* A keys read's values as the numbers `foldInto` would fold, an array read past its end as the rest:
|
|
91
|
+
* read here once per read, since reading a number out of a delta's array for each subject boxes it.
|
|
92
|
+
*/
|
|
93
|
+
function flatten(chans, read, nums, kinds) {
|
|
94
|
+
let k = 0;
|
|
95
|
+
for (let i = 0; i < chans.length; i++) {
|
|
96
|
+
const ch = chans[i];
|
|
97
|
+
const value = read[i];
|
|
98
|
+
if (value === undefined)
|
|
99
|
+
kinds[i] = 0 /* Kind.ABSENT */;
|
|
100
|
+
else if (!ch.plain)
|
|
101
|
+
kinds[i] = unplain(ch, value, nums, k);
|
|
102
|
+
else {
|
|
103
|
+
kinds[i] = 1 /* Kind.NUMBERS */;
|
|
104
|
+
if (ch.scalar)
|
|
105
|
+
nums[k] = value;
|
|
106
|
+
else {
|
|
107
|
+
const arr = Array.isArray(value) || ArrayBuffer.isView(value) ? value : null;
|
|
108
|
+
for (let a = 0; a < ch.axes; a++)
|
|
109
|
+
nums[k + a] = arr === null ? ch.rest : (arr[a] ?? ch.rest);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
k += ch.axes;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* `flatten` for a channel that is not plain. An angle's number is taken the short way round here,
|
|
117
|
+
* and then folds as the sum it is; a rest-less channel and a quat fold through `foldInto`.
|
|
118
|
+
*/
|
|
119
|
+
function unplain(ch, value, nums, k) {
|
|
120
|
+
if (!(ch.turn > 0))
|
|
121
|
+
return 2 /* Kind.GATED */;
|
|
122
|
+
nums[k] = shortWay(value, ch.turn);
|
|
123
|
+
return 1 /* Kind.NUMBERS */;
|
|
124
|
+
}
|
package/dist/keyrows.d.ts
CHANGED
package/dist/keyrows.js
CHANGED
|
@@ -23,7 +23,7 @@ export class KeyRows {
|
|
|
23
23
|
for (let i = 0; i < built.tracks.length; i++) {
|
|
24
24
|
const track = built.tracks[i];
|
|
25
25
|
const ch = chans[i];
|
|
26
|
-
if (track === undefined || ch === undefined)
|
|
26
|
+
if (track === undefined || ch === undefined || !ch.plain)
|
|
27
27
|
return -1;
|
|
28
28
|
for (const pt of track.all)
|
|
29
29
|
if (!flat(pt.value, ch))
|
package/dist/lane.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Numeric } from './channels.js';
|
|
2
2
|
import type { Motions } from './motions.js';
|
|
3
3
|
import type { Scratch } from './patch.js';
|
|
4
|
+
import type { Channel } from './types.js';
|
|
4
5
|
import type { Subject, Voice } from './voice.js';
|
|
5
6
|
/** One laned channel: its arithmetic, and every numbered subject's folded value, `axes` per subject. */
|
|
6
7
|
export interface Laned {
|
|
@@ -8,6 +9,12 @@ export interface Laned {
|
|
|
8
9
|
/** Its value is a number, its rest a number; otherwise an array, which `vec(1)` is too. */
|
|
9
10
|
scalar: boolean;
|
|
10
11
|
op: Numeric['op'];
|
|
12
|
+
/** Its values fold as its `op` alone: not through a band as `'last'` does, and not a rotation. */
|
|
13
|
+
plain: boolean;
|
|
14
|
+
/** An angle's full turn, and 0 for any other channel. */
|
|
15
|
+
turn: number;
|
|
16
|
+
/** The channel, whose own `fold` and `lerp` an `'own'` lane calls. */
|
|
17
|
+
channel: Channel<unknown>;
|
|
11
18
|
rest: number;
|
|
12
19
|
axes: number;
|
|
13
20
|
values: Float64Array;
|
|
@@ -56,6 +63,24 @@ export declare const enum Begin {
|
|
|
56
63
|
GENERAL = 0,
|
|
57
64
|
READY = 1
|
|
58
65
|
}
|
|
66
|
+
/**
|
|
67
|
+
* Where each number a fill hands from one of its methods to the next sits in `Lanes.arg`. Passed as
|
|
68
|
+
* an argument, a double is boxed wherever V8 does not inline the call: 16 bytes a number a subject
|
|
69
|
+
* (bench/allocs.mjs, 2026-10-09). The caller writes them just before the call, and the callee reads
|
|
70
|
+
* them before it calls anything else.
|
|
71
|
+
*/
|
|
72
|
+
export declare const enum Arg {
|
|
73
|
+
/** The weight to fold at. */
|
|
74
|
+
WEIGHT = 0,
|
|
75
|
+
/** The subject's voice time, its freezes applied. */
|
|
76
|
+
ELAPSED = 1,
|
|
77
|
+
PHASE = 2,
|
|
78
|
+
PASS = 3,
|
|
79
|
+
DELAY = 4,
|
|
80
|
+
/** The voice's own fade, for `signalled`. */
|
|
81
|
+
FADE = 5,
|
|
82
|
+
SIZE = 6
|
|
83
|
+
}
|
|
59
84
|
/** Where each of a subject's `SLOT` numbers in `Lanes.per` sits. */
|
|
60
85
|
export declare const enum Per {
|
|
61
86
|
SLOT = 6,
|
|
@@ -98,8 +123,8 @@ export interface Paced {
|
|
|
98
123
|
data: Float64Array;
|
|
99
124
|
}
|
|
100
125
|
/**
|
|
101
|
-
* Where a motion voice's per-subject numbers live, by position: a lane of one voice, or
|
|
102
|
-
* of single-subject
|
|
126
|
+
* Where a motion voice's per-subject numbers live, by position: a lane of one voice, or a crowd
|
|
127
|
+
* of single-subject voices, whose positions each belong to a voice of their own.
|
|
103
128
|
*/
|
|
104
129
|
export interface Positions<I, O> {
|
|
105
130
|
/** `STRIDE` numbers per position. */
|
|
@@ -164,6 +189,9 @@ export declare class Lane<I, O> implements Positions<I, O> {
|
|
|
164
189
|
chans: Laned[];
|
|
165
190
|
/** The value read for each of `chans`, for the subject being folded. */
|
|
166
191
|
values: unknown[];
|
|
192
|
+
/** A keys read's `values` as numbers, each channel's axes side by side, and how each folds. */
|
|
193
|
+
nums: Float64Array<ArrayBuffer>;
|
|
194
|
+
kinds: Uint8Array<ArrayBuffer>;
|
|
167
195
|
/**
|
|
168
196
|
* Within one fill, the last elapsed placed and the last `since` weighed, with what they gave, so
|
|
169
197
|
* subjects sharing a delay or a start share the arithmetic: the same inputs give the same bits.
|
|
@@ -179,6 +207,8 @@ export declare class Lane<I, O> implements Positions<I, O> {
|
|
|
179
207
|
fade: number;
|
|
180
208
|
/** Whether the voice has no fade in or out this fill, so its envelope is 1 for every subject. */
|
|
181
209
|
flat: boolean;
|
|
210
|
+
/** The voice's clock at this fill, set before `one` runs its subjects. */
|
|
211
|
+
elapsedNow: number;
|
|
182
212
|
/** A motion patch's state, which the lane samples in place of calling the patch. */
|
|
183
213
|
readonly motion: Motions<I> | undefined;
|
|
184
214
|
/** By the motion patch's number for a subject, its position plus one, 0 where it has none. */
|
package/dist/lane.js
CHANGED
|
@@ -52,6 +52,9 @@ export class Lane {
|
|
|
52
52
|
chans = [];
|
|
53
53
|
/** The value read for each of `chans`, for the subject being folded. */
|
|
54
54
|
values = [];
|
|
55
|
+
/** A keys read's `values` as numbers, each channel's axes side by side, and how each folds. */
|
|
56
|
+
nums = new Float64Array(0);
|
|
57
|
+
kinds = new Uint8Array(0);
|
|
55
58
|
/**
|
|
56
59
|
* Within one fill, the last elapsed placed and the last `since` weighed, with what they gave, so
|
|
57
60
|
* subjects sharing a delay or a start share the arithmetic: the same inputs give the same bits.
|
|
@@ -67,6 +70,8 @@ export class Lane {
|
|
|
67
70
|
fade = 1;
|
|
68
71
|
/** Whether the voice has no fade in or out this fill, so its envelope is 1 for every subject. */
|
|
69
72
|
flat = false;
|
|
73
|
+
/** The voice's clock at this fill, set before `one` runs its subjects. */
|
|
74
|
+
elapsedNow = 0;
|
|
70
75
|
/** A motion patch's state, which the lane samples in place of calling the patch. */
|
|
71
76
|
motion;
|
|
72
77
|
/** By the motion patch's number for a subject, its position plus one, 0 where it has none. */
|
package/dist/lanes.d.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import type { Column } from './columns.js';
|
|
2
|
+
import { copy } from './copyout.js';
|
|
2
3
|
import { type Crowd, row } from './crowd.js';
|
|
3
|
-
import { call, foldKeys, one, parting, readKeyed, reset, run, signalled, subjectAt } from './fill.js';
|
|
4
|
-
import { fold, foldDelta, foldInto, foldRun, gate, gather } from './gather.js';
|
|
4
|
+
import { call, foldKeys, one, parting, readKeyed, reset, run, runEveryone, signalled, subjectAt } from './fill.js';
|
|
5
|
+
import { fold, foldDelta, foldInto, foldRun, gate, gather, unplain } from './gather.js';
|
|
5
6
|
import { type Lane, type Laned, type Locus } from './lane.js';
|
|
6
7
|
import type { Watcher } from './motions.js';
|
|
7
|
-
import {
|
|
8
|
+
import { absent, Numbers } from './numbers.js';
|
|
8
9
|
import { Owed } from './owed.js';
|
|
9
10
|
import type { Channel } from './types.js';
|
|
10
11
|
import type { Subject, Voice } from './voice.js';
|
|
@@ -61,7 +62,7 @@ export declare class Lanes<I, O> implements Watcher {
|
|
|
61
62
|
/** The lanes over every subject that have started playing, by epoch. */
|
|
62
63
|
dense: Lane<I, O>[];
|
|
63
64
|
readonly byId: Map<number, Lane<I, O>>;
|
|
64
|
-
/** The crowds of single-subject
|
|
65
|
+
/** The crowds of single-subject voices, one per list of channels, and which holds each voice. */
|
|
65
66
|
crowds: Crowd<I, O>[];
|
|
66
67
|
readonly crowdOf: Map<number, Crowd<I, O>>;
|
|
67
68
|
/** Whether two crowds share a channel, so their rows must interleave in voice order. */
|
|
@@ -71,13 +72,20 @@ export declare class Lanes<I, O> implements Watcher {
|
|
|
71
72
|
/** The lane and record `one` is folding, whose band state a rest-less channel reads. */
|
|
72
73
|
folding: Lane<I, O> | null;
|
|
73
74
|
rec: Subject<unknown> | null;
|
|
75
|
+
/** The numbers a fill's methods hand each other: see `Arg`. */
|
|
76
|
+
readonly arg: Float64Array<ArrayBuffer>;
|
|
74
77
|
/** While a fill gathers a locus: that locus and the member gathering, where folds go instead. */
|
|
75
78
|
into: Locus<I, O> | null;
|
|
76
79
|
intoId: number;
|
|
77
80
|
readonly lawsByKey: Map<string, Float64Array<ArrayBufferLike>>;
|
|
78
81
|
lastLaw: Float64Array | null;
|
|
79
|
-
/**
|
|
82
|
+
/**
|
|
83
|
+
* The lanes the last `meet` gave a position, the first `metN` of `met`, and whether it placed a
|
|
84
|
+
* crowd row. Counted, not cut to length: an array emptied by its length takes new room at its
|
|
85
|
+
* next push, about 150 B a subject met (the `swap` row, bench/allocs.mjs, 2026-10-09).
|
|
86
|
+
*/
|
|
80
87
|
readonly met: number[];
|
|
88
|
+
metN: number;
|
|
81
89
|
metCrowd: boolean;
|
|
82
90
|
/** By subject number, the laned voices its probes fold on the general path this fill. */
|
|
83
91
|
readonly owed: Owed;
|
|
@@ -137,8 +145,9 @@ export declare class Lanes<I, O> implements Watcher {
|
|
|
137
145
|
keeps: boolean;
|
|
138
146
|
now: number;
|
|
139
147
|
filledAt: number;
|
|
140
|
-
/** `reading.moved` at the last fill
|
|
148
|
+
/** `reading.moved` and `reading.inputs` at the last fill: see `current`. */
|
|
141
149
|
moved: number;
|
|
150
|
+
inputs: number;
|
|
142
151
|
/** The `now` of the latest probe, and the probe count before its first, to tell a probe this frame. */
|
|
143
152
|
frameAt: number;
|
|
144
153
|
frameProbes: number;
|
|
@@ -191,6 +200,13 @@ export declare class Lanes<I, O> implements Watcher {
|
|
|
191
200
|
prepare(slot: number, subject: I, version: number, head: Subject<unknown> | null): boolean;
|
|
192
201
|
/** Whether this frame's lanes are filled and nothing since asks `begin` to look again. */
|
|
193
202
|
filled(now: number, version: number): boolean;
|
|
203
|
+
/**
|
|
204
|
+
* Whether the last fill still stands: everything a fill reads that can change between two probes,
|
|
205
|
+
* each stamped by `fillAll`. The clock; the mix's version, which voices coming, going, starting
|
|
206
|
+
* and stopping move; a motion's retargets and pushes; and a `level` set. Whatever else can change
|
|
207
|
+
* between probes keeps its voice off lanes in `fits`.
|
|
208
|
+
*/
|
|
209
|
+
current(now: number, version: number): boolean;
|
|
194
210
|
/**
|
|
195
211
|
* What a probe does before its subject, once a frame's lanes are settled: qualifies and fills as
|
|
196
212
|
* needed. Says whether the subject takes the general path or the lanes are filled.
|
|
@@ -215,8 +231,6 @@ export declare class Lanes<I, O> implements Watcher {
|
|
|
215
231
|
* would write: each number within 1e-9 of its rest.
|
|
216
232
|
*/
|
|
217
233
|
rests(slot: number): boolean;
|
|
218
|
-
/** Writes a subject's laned values into a pose, each array channel into a new array. */
|
|
219
|
-
copy(slot: number, pose: Record<string, unknown>): void;
|
|
220
234
|
forget(slot: number): void;
|
|
221
235
|
grow(size: number): void;
|
|
222
236
|
/**
|
|
@@ -224,16 +238,25 @@ export declare class Lanes<I, O> implements Watcher {
|
|
|
224
238
|
* next fill, and a lane keeps the sample the old one gave.
|
|
225
239
|
*/
|
|
226
240
|
stretchChanged(id: number, s: number): void;
|
|
241
|
+
/**
|
|
242
|
+
* A voice that kept one record for every subject keeps one each from now on: each of its lane's
|
|
243
|
+
* positions takes the record `of` gives its subject.
|
|
244
|
+
*/
|
|
245
|
+
rerecord(id: number, of: (subject: I, slot: number) => Subject<unknown>): void;
|
|
246
|
+
/** A voice's clock moved the `shown` of records it had already placed: copies them again. */
|
|
247
|
+
reshown(id: number): void;
|
|
227
248
|
/** Something on a voice changed: a crowd copies it again at the next fill. */
|
|
228
249
|
voiceChanged(id: number): void;
|
|
229
250
|
}
|
|
230
251
|
declare const methods: {
|
|
252
|
+
copy: typeof copy;
|
|
231
253
|
row: typeof row;
|
|
232
254
|
signalled: typeof signalled;
|
|
233
255
|
parting: typeof parting;
|
|
234
256
|
subjectAt: typeof subjectAt;
|
|
235
257
|
reset: typeof reset;
|
|
236
258
|
run: typeof run;
|
|
259
|
+
runEveryone: typeof runEveryone;
|
|
237
260
|
one: typeof one;
|
|
238
261
|
foldKeys: typeof foldKeys;
|
|
239
262
|
readKeyed: typeof readKeyed;
|
|
@@ -244,6 +267,7 @@ declare const methods: {
|
|
|
244
267
|
foldRun: typeof foldRun;
|
|
245
268
|
foldInto: typeof foldInto;
|
|
246
269
|
gate: typeof gate;
|
|
270
|
+
unplain: typeof unplain;
|
|
247
271
|
};
|
|
248
272
|
type Methods = typeof methods;
|
|
249
273
|
export interface Lanes<I, O> extends Methods {
|
package/dist/lanes.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import { copy } from './copyout.js';
|
|
1
2
|
import { row } from './crowd.js';
|
|
2
|
-
import { call, fillAll, foldKeys, one, parting, readKeyed, reset, run, signalled, subjectAt, } from './fill.js';
|
|
3
|
-
import { fold, foldDelta, foldInto, foldRun, gate, gather } from './gather.js';
|
|
3
|
+
import { call, fillAll, foldKeys, one, parting, readKeyed, reset, run, runEveryone, signalled, subjectAt, } from './fill.js';
|
|
4
|
+
import { fold, foldDelta, foldInto, foldRun, gate, gather, unplain } from './gather.js';
|
|
4
5
|
import { meet, reach } from './meet.js';
|
|
5
|
-
import { Numbers } from './numbers.js';
|
|
6
|
+
import { absent, Numbers } from './numbers.js';
|
|
6
7
|
import { Owed } from './owed.js';
|
|
7
8
|
import { requalify, retouch } from './qualify.js';
|
|
8
9
|
import { reading } from './reading.js';
|
|
@@ -19,7 +20,7 @@ export class Lanes {
|
|
|
19
20
|
/** The lanes over every subject that have started playing, by epoch. */
|
|
20
21
|
dense = [];
|
|
21
22
|
byId = new Map();
|
|
22
|
-
/** The crowds of single-subject
|
|
23
|
+
/** The crowds of single-subject voices, one per list of channels, and which holds each voice. */
|
|
23
24
|
crowds = [];
|
|
24
25
|
crowdOf = new Map();
|
|
25
26
|
/** Whether two crowds share a channel, so their rows must interleave in voice order. */
|
|
@@ -29,13 +30,20 @@ export class Lanes {
|
|
|
29
30
|
/** The lane and record `one` is folding, whose band state a rest-less channel reads. */
|
|
30
31
|
folding = null;
|
|
31
32
|
rec = null;
|
|
33
|
+
/** The numbers a fill's methods hand each other: see `Arg`. */
|
|
34
|
+
arg = new Float64Array(6 /* Arg.SIZE */);
|
|
32
35
|
/** While a fill gathers a locus: that locus and the member gathering, where folds go instead. */
|
|
33
36
|
into = null;
|
|
34
37
|
intoId = 0;
|
|
35
38
|
lawsByKey = new Map();
|
|
36
39
|
lastLaw = null;
|
|
37
|
-
/**
|
|
40
|
+
/**
|
|
41
|
+
* The lanes the last `meet` gave a position, the first `metN` of `met`, and whether it placed a
|
|
42
|
+
* crowd row. Counted, not cut to length: an array emptied by its length takes new room at its
|
|
43
|
+
* next push, about 150 B a subject met (the `swap` row, bench/allocs.mjs, 2026-10-09).
|
|
44
|
+
*/
|
|
38
45
|
met = [];
|
|
46
|
+
metN = 0;
|
|
39
47
|
metCrowd = false;
|
|
40
48
|
/** By subject number, the laned voices its probes fold on the general path this fill. */
|
|
41
49
|
owed = new Owed();
|
|
@@ -95,8 +103,9 @@ export class Lanes {
|
|
|
95
103
|
keeps = false;
|
|
96
104
|
now = Number.NaN;
|
|
97
105
|
filledAt = Number.NaN;
|
|
98
|
-
/** `reading.moved` at the last fill
|
|
106
|
+
/** `reading.moved` and `reading.inputs` at the last fill: see `current`. */
|
|
99
107
|
moved = 0;
|
|
108
|
+
inputs = 0;
|
|
100
109
|
/** The `now` of the latest probe, and the probe count before its first, to tell a probe this frame. */
|
|
101
110
|
frameAt = Number.NaN;
|
|
102
111
|
frameProbes = 0;
|
|
@@ -233,13 +242,23 @@ export class Lanes {
|
|
|
233
242
|
}
|
|
234
243
|
/** Whether this frame's lanes are filled and nothing since asks `begin` to look again. */
|
|
235
244
|
filled(now, version) {
|
|
245
|
+
return (this.frameAt === now &&
|
|
246
|
+
this.qualifiedVersion === version &&
|
|
247
|
+
this.touched.length === 0 &&
|
|
248
|
+
!this.filling &&
|
|
249
|
+
this.current(now, version));
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Whether the last fill still stands: everything a fill reads that can change between two probes,
|
|
253
|
+
* each stamped by `fillAll`. The clock; the mix's version, which voices coming, going, starting
|
|
254
|
+
* and stopping move; a motion's retargets and pushes; and a `level` set. Whatever else can change
|
|
255
|
+
* between probes keeps its voice off lanes in `fits`.
|
|
256
|
+
*/
|
|
257
|
+
current(now, version) {
|
|
236
258
|
return (this.filledAt === now &&
|
|
237
|
-
this.frameAt === now &&
|
|
238
259
|
this.filledVersion === version &&
|
|
239
|
-
this.qualifiedVersion === version &&
|
|
240
260
|
this.moved === reading.moved &&
|
|
241
|
-
this.
|
|
242
|
-
!this.filling);
|
|
261
|
+
this.inputs === reading.inputs);
|
|
243
262
|
}
|
|
244
263
|
/**
|
|
245
264
|
* What a probe does before its subject, once a frame's lanes are settled: qualifies and fills as
|
|
@@ -257,8 +276,9 @@ export class Lanes {
|
|
|
257
276
|
}
|
|
258
277
|
if (this.filling)
|
|
259
278
|
return 0 /* Begin.GENERAL */;
|
|
260
|
-
//
|
|
261
|
-
// waiting, each of which touches it,
|
|
279
|
+
// Past a qualify that stands, the mix's version moves when a voice over every subject comes,
|
|
280
|
+
// goes, starts or stops waiting, each of which touches it, or stops sharing one record, which
|
|
281
|
+
// moves no voice on or off a lane: so with voices touched it is a set of touches.
|
|
262
282
|
if (this.qualifiedVersion !== version) {
|
|
263
283
|
if (!Number.isNaN(this.qualifiedVersion) && this.touched.length > 0 && retouch(this))
|
|
264
284
|
this.qualifiedVersion = version;
|
|
@@ -269,7 +289,7 @@ export class Lanes {
|
|
|
269
289
|
requalify(this, version);
|
|
270
290
|
if (this.laned.length === 0)
|
|
271
291
|
return 0 /* Begin.GENERAL */;
|
|
272
|
-
if (this.
|
|
292
|
+
if (!this.current(now, version)) {
|
|
273
293
|
fillAll(this, now, version);
|
|
274
294
|
// A patch call in that fill made kept state, which took its voice off its lane: fill without it.
|
|
275
295
|
if (this.qualifiedVersion !== version) {
|
|
@@ -349,30 +369,6 @@ export class Lanes {
|
|
|
349
369
|
}
|
|
350
370
|
return true;
|
|
351
371
|
}
|
|
352
|
-
/** Writes a subject's laned values into a pose, each array channel into a new array. */
|
|
353
|
-
copy(slot, pose) {
|
|
354
|
-
const laned = this.laned;
|
|
355
|
-
for (let c = 0; c < laned.length; c++) {
|
|
356
|
-
const ch = laned[c];
|
|
357
|
-
const axes = ch.axes;
|
|
358
|
-
// Never `delete`: it drops a reused out object into dictionary mode for good.
|
|
359
|
-
if (ch.op === 'last' && Number.isNaN(ch.values[slot * axes])) {
|
|
360
|
-
if (pose[ch.name] !== undefined)
|
|
361
|
-
pose[ch.name] = undefined;
|
|
362
|
-
continue;
|
|
363
|
-
}
|
|
364
|
-
if (ch.scalar) {
|
|
365
|
-
pose[ch.name] = ch.values[slot];
|
|
366
|
-
continue;
|
|
367
|
-
}
|
|
368
|
-
// Made as the general path makes it, a copy of rest, so poses from either path share a shape.
|
|
369
|
-
const arr = [...ch.start];
|
|
370
|
-
const base = slot * axes;
|
|
371
|
-
for (let a = 0; a < axes; a++)
|
|
372
|
-
arr[a] = ch.values[base + a];
|
|
373
|
-
pose[ch.name] = arr;
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
372
|
forget(slot) {
|
|
377
373
|
this.host.forgot(slot);
|
|
378
374
|
this.live--;
|
|
@@ -417,6 +413,39 @@ export class Lanes {
|
|
|
417
413
|
if (q >= 0)
|
|
418
414
|
lane.fix(q);
|
|
419
415
|
}
|
|
416
|
+
/**
|
|
417
|
+
* A voice that kept one record for every subject keeps one each from now on: each of its lane's
|
|
418
|
+
* positions takes the record `of` gives its subject.
|
|
419
|
+
*/
|
|
420
|
+
rerecord(id, of) {
|
|
421
|
+
const lane = this.byId.get(id);
|
|
422
|
+
if (lane === undefined)
|
|
423
|
+
return;
|
|
424
|
+
for (let p = 0; p < lane.list.length; p++) {
|
|
425
|
+
const slot = lane.list[p];
|
|
426
|
+
const subject = this.subjectAt(slot);
|
|
427
|
+
if (subject !== absent)
|
|
428
|
+
lane.records[p] = of(subject, slot);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
/** A voice's clock moved the `shown` of records it had already placed: copies them again. */
|
|
432
|
+
reshown(id) {
|
|
433
|
+
const c = this.crowdOf.get(id);
|
|
434
|
+
const p = c?.rowOf.get(id);
|
|
435
|
+
if (c !== undefined && p !== undefined) {
|
|
436
|
+
const held = c.records[p];
|
|
437
|
+
if (held !== undefined)
|
|
438
|
+
c.data[p * 8 /* Row.STRIDE */ + 1 /* Row.SINCE */] = held.shown;
|
|
439
|
+
}
|
|
440
|
+
const lane = this.byId.get(id);
|
|
441
|
+
if (lane === undefined)
|
|
442
|
+
return;
|
|
443
|
+
for (let q = 0; q < lane.list.length; q++) {
|
|
444
|
+
const held = lane.records[q];
|
|
445
|
+
if (held !== undefined)
|
|
446
|
+
lane.data[q * 8 /* Row.STRIDE */ + 1 /* Row.SINCE */] = held.shown;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
420
449
|
/** Something on a voice changed: a crowd copies it again at the next fill. */
|
|
421
450
|
voiceChanged(id) {
|
|
422
451
|
const c = this.crowdOf.get(id);
|
|
@@ -430,12 +459,14 @@ export class Lanes {
|
|
|
430
459
|
// A fill's per-subject path is methods, installed here from the modules that own them: as
|
|
431
460
|
// functions called directly they made fills 5-8% slower (bench/ab.sh on teitou, 2026-10-05).
|
|
432
461
|
const methods = {
|
|
462
|
+
copy,
|
|
433
463
|
row,
|
|
434
464
|
signalled,
|
|
435
465
|
parting,
|
|
436
466
|
subjectAt,
|
|
437
467
|
reset,
|
|
438
468
|
run,
|
|
469
|
+
runEveryone,
|
|
439
470
|
one,
|
|
440
471
|
foldKeys,
|
|
441
472
|
readKeyed,
|
|
@@ -446,6 +477,7 @@ const methods = {
|
|
|
446
477
|
foldRun,
|
|
447
478
|
foldInto,
|
|
448
479
|
gate,
|
|
480
|
+
unplain,
|
|
449
481
|
};
|
|
450
482
|
for (const [name, value] of Object.entries(methods))
|
|
451
483
|
Object.defineProperty(Lanes.prototype, name, { value, writable: true, configurable: true });
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type Cut } from './transport.js';
|
|
2
|
+
import type { Left, Subject } from './voice.js';
|
|
3
|
+
/**
|
|
4
|
+
* Under history, the records subjects left a voice with, while a seek or a read back may reach
|
|
5
|
+
* them: in the order they left, the oldest let go of from the front, and each subject's found
|
|
6
|
+
* without looking through the others'.
|
|
7
|
+
*/
|
|
8
|
+
export declare class Leavings<I> {
|
|
9
|
+
private list;
|
|
10
|
+
/** How many at the front of `list` have been let go of, cleared out once they are half of it. */
|
|
11
|
+
private head;
|
|
12
|
+
private readonly by;
|
|
13
|
+
static from<I>(entries: readonly Left<I>[]): Leavings<I> | null;
|
|
14
|
+
push(e: Left<I>): void;
|
|
15
|
+
/**
|
|
16
|
+
* Lets go of every entry that left before `reach`, handing each to `out` first. They are at the
|
|
17
|
+
* front: entries are in the order they left, and mix time does not run back along it.
|
|
18
|
+
*/
|
|
19
|
+
expire(reach: number, out: ((e: Left<I>) => void) | null): void;
|
|
20
|
+
/** The record `subject` had at `cut` and left with after it, if it left after `cut`. */
|
|
21
|
+
at(subject: I, cut: Cut): Subject<unknown> | undefined;
|
|
22
|
+
/** Each time `subject` left, oldest first. */
|
|
23
|
+
of(subject: I): readonly Left<I>[];
|
|
24
|
+
/** Every entry, oldest first. */
|
|
25
|
+
all(): readonly Left<I>[];
|
|
26
|
+
}
|
package/dist/leavings.js
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { Store } from './store.js';
|
|
2
|
+
import { within } from './transport.js';
|
|
3
|
+
/**
|
|
4
|
+
* Under history, the records subjects left a voice with, while a seek or a read back may reach
|
|
5
|
+
* them: in the order they left, the oldest let go of from the front, and each subject's found
|
|
6
|
+
* without looking through the others'.
|
|
7
|
+
*/
|
|
8
|
+
export class Leavings {
|
|
9
|
+
list = [];
|
|
10
|
+
/** How many at the front of `list` have been let go of, cleared out once they are half of it. */
|
|
11
|
+
head = 0;
|
|
12
|
+
by = new Store();
|
|
13
|
+
static from(entries) {
|
|
14
|
+
if (entries.length === 0)
|
|
15
|
+
return null;
|
|
16
|
+
const out = new Leavings();
|
|
17
|
+
for (const e of entries)
|
|
18
|
+
out.push(e);
|
|
19
|
+
return out;
|
|
20
|
+
}
|
|
21
|
+
push(e) {
|
|
22
|
+
this.list.push(e);
|
|
23
|
+
const own = this.by.get(e.subject);
|
|
24
|
+
if (own === undefined)
|
|
25
|
+
this.by.set(e.subject, [e]);
|
|
26
|
+
else
|
|
27
|
+
own.push(e);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Lets go of every entry that left before `reach`, handing each to `out` first. They are at the
|
|
31
|
+
* front: entries are in the order they left, and mix time does not run back along it.
|
|
32
|
+
*/
|
|
33
|
+
expire(reach, out) {
|
|
34
|
+
const list = this.list;
|
|
35
|
+
let head = this.head;
|
|
36
|
+
while (head < list.length && !(list[head].at >= reach)) {
|
|
37
|
+
const e = list[head++];
|
|
38
|
+
if (out !== null)
|
|
39
|
+
out(e);
|
|
40
|
+
const own = this.by.get(e.subject);
|
|
41
|
+
if (own.length === 1)
|
|
42
|
+
this.by.delete(e.subject);
|
|
43
|
+
else
|
|
44
|
+
own.shift();
|
|
45
|
+
}
|
|
46
|
+
this.head = head;
|
|
47
|
+
if (head >= 32 && head * 2 >= list.length) {
|
|
48
|
+
this.list = list.slice(head);
|
|
49
|
+
this.head = 0;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/** The record `subject` had at `cut` and left with after it, if it left after `cut`. */
|
|
53
|
+
at(subject, cut) {
|
|
54
|
+
const own = this.by.get(subject);
|
|
55
|
+
if (own === undefined)
|
|
56
|
+
return undefined;
|
|
57
|
+
for (const e of own)
|
|
58
|
+
if (!within(cut, e.seq, e.sync))
|
|
59
|
+
return e.held;
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
62
|
+
/** Each time `subject` left, oldest first. */
|
|
63
|
+
of(subject) {
|
|
64
|
+
return this.by.get(subject) ?? [];
|
|
65
|
+
}
|
|
66
|
+
/** Every entry, oldest first. */
|
|
67
|
+
all() {
|
|
68
|
+
return this.head === 0 ? this.list : this.list.slice(this.head);
|
|
69
|
+
}
|
|
70
|
+
}
|