@msb235/blits 0.14.0 → 0.15.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.
Files changed (187) hide show
  1. package/CHANGELOG.md +198 -0
  2. package/README.md +3 -3
  3. package/dist/ahead.d.ts +26 -0
  4. package/dist/ahead.js +264 -0
  5. package/dist/around.d.ts +11 -0
  6. package/dist/around.js +32 -0
  7. package/dist/bare.js +37 -14
  8. package/dist/blank.d.ts +10 -0
  9. package/dist/blank.js +26 -0
  10. package/dist/blend.d.ts +2 -1
  11. package/dist/blend.js +1 -6
  12. package/dist/bound.d.ts +70 -0
  13. package/dist/bound.js +351 -0
  14. package/dist/chain.d.ts +2 -1
  15. package/dist/chain.js +7 -5
  16. package/dist/changes.d.ts +24 -0
  17. package/dist/changes.js +189 -0
  18. package/dist/channels.d.ts +39 -13
  19. package/dist/channels.js +14 -5
  20. package/dist/color.d.ts +12 -10
  21. package/dist/color.js +25 -25
  22. package/dist/columns.d.ts +2 -1
  23. package/dist/columns.js +62 -38
  24. package/dist/contribution.d.ts +30 -0
  25. package/dist/contribution.js +374 -0
  26. package/dist/copyout.d.ts +10 -0
  27. package/dist/copyout.js +69 -24
  28. package/dist/crowd.d.ts +16 -6
  29. package/dist/crowd.js +20 -11
  30. package/dist/cue.d.ts +8 -1
  31. package/dist/cue.js +39 -13
  32. package/dist/cut.d.ts +8 -0
  33. package/dist/cut.js +4 -0
  34. package/dist/delay.d.ts +3 -0
  35. package/dist/delay.js +7 -0
  36. package/dist/drop.d.ts +9 -0
  37. package/dist/drop.js +84 -0
  38. package/dist/due.d.ts +24 -5
  39. package/dist/due.js +114 -34
  40. package/dist/easing.d.ts +6 -4
  41. package/dist/easing.js +7 -3
  42. package/dist/everyone.d.ts +8 -2
  43. package/dist/everyone.js +33 -3
  44. package/dist/fade.d.ts +2 -1
  45. package/dist/fade.js +2 -1
  46. package/dist/fill.d.ts +3 -2
  47. package/dist/fill.js +52 -20
  48. package/dist/fit.d.ts +44 -26
  49. package/dist/fit.js +10 -9
  50. package/dist/fold.d.ts +4 -37
  51. package/dist/fold.js +5 -513
  52. package/dist/gather.d.ts +12 -7
  53. package/dist/gather.js +179 -44
  54. package/dist/gone.js +2 -2
  55. package/dist/grid.d.ts +47 -0
  56. package/dist/grid.js +247 -0
  57. package/dist/handle.d.ts +2 -0
  58. package/dist/handle.js +14 -8
  59. package/dist/held.d.ts +2 -1
  60. package/dist/held.js +2 -1
  61. package/dist/history.d.ts +6 -5
  62. package/dist/history.js +7 -9
  63. package/dist/hosts.js +9 -15
  64. package/dist/index.d.ts +5 -4
  65. package/dist/keyfill.d.ts +1 -1
  66. package/dist/keyfill.js +5 -3
  67. package/dist/keyrows.d.ts +12 -0
  68. package/dist/keyrows.js +99 -12
  69. package/dist/lane.d.ts +38 -11
  70. package/dist/lane.js +46 -11
  71. package/dist/lanes.d.ts +30 -12
  72. package/dist/lanes.js +56 -36
  73. package/dist/leavings.d.ts +2 -2
  74. package/dist/leavings.js +1 -1
  75. package/dist/lives.d.ts +47 -0
  76. package/dist/lives.js +180 -0
  77. package/dist/meet.d.ts +2 -2
  78. package/dist/meet.js +95 -7
  79. package/dist/mixer.d.ts +56 -37
  80. package/dist/mixer.js +57 -171
  81. package/dist/motion.d.ts +14 -118
  82. package/dist/motion.js +16 -8
  83. package/dist/motions.d.ts +62 -155
  84. package/dist/motions.js +56 -370
  85. package/dist/moved.d.ts +1 -1
  86. package/dist/moved.js +16 -7
  87. package/dist/named.d.ts +6 -2
  88. package/dist/named.js +49 -3
  89. package/dist/numbers.d.ts +7 -0
  90. package/dist/numbers.js +33 -0
  91. package/dist/owed.d.ts +2 -0
  92. package/dist/owed.js +4 -0
  93. package/dist/pack.d.ts +2 -1
  94. package/dist/pack.js +2 -3
  95. package/dist/patch.d.ts +7 -3
  96. package/dist/patch.js +6 -2
  97. package/dist/places.d.ts +53 -0
  98. package/dist/places.js +171 -0
  99. package/dist/plot.d.ts +12 -0
  100. package/dist/plot.js +52 -0
  101. package/dist/project.d.ts +4 -4
  102. package/dist/project.js +9 -212
  103. package/dist/pull.js +22 -9
  104. package/dist/qualify.js +93 -31
  105. package/dist/reading.d.ts +6 -0
  106. package/dist/reading.js +5 -0
  107. package/dist/record.d.ts +5 -1
  108. package/dist/record.js +3 -2
  109. package/dist/relink.d.ts +1 -1
  110. package/dist/renumber.d.ts +10 -0
  111. package/dist/renumber.js +75 -0
  112. package/dist/restored.d.ts +2 -2
  113. package/dist/restored.js +1 -1
  114. package/dist/rests.d.ts +9 -0
  115. package/dist/rests.js +76 -0
  116. package/dist/revive.d.ts +3 -2
  117. package/dist/revive.js +2 -2
  118. package/dist/{locus.d.ts → role.d.ts} +10 -9
  119. package/dist/{locus.js → role.js} +3 -3
  120. package/dist/roles.d.ts +10 -0
  121. package/dist/roles.js +158 -0
  122. package/dist/roster.js +3 -3
  123. package/dist/rotation.d.ts +1 -1
  124. package/dist/rotation.js +1 -1
  125. package/dist/rows.js +28 -20
  126. package/dist/sample.d.ts +2 -2
  127. package/dist/sample.js +33 -7
  128. package/dist/seat.d.ts +17 -0
  129. package/dist/seat.js +25 -0
  130. package/dist/seek.js +5 -5
  131. package/dist/setting.d.ts +27 -0
  132. package/dist/setting.js +55 -0
  133. package/dist/shared.d.ts +1 -1
  134. package/dist/shared.js +1 -1
  135. package/dist/signals.d.ts +64 -32
  136. package/dist/signals.js +20 -18
  137. package/dist/stack.d.ts +5 -5
  138. package/dist/stack.js +5 -5
  139. package/dist/standing.d.ts +2 -1
  140. package/dist/standing.js +3 -2
  141. package/dist/store.d.ts +9 -0
  142. package/dist/store.js +24 -0
  143. package/dist/strays.d.ts +2 -1
  144. package/dist/strays.js +23 -15
  145. package/dist/stretch.d.ts +112 -0
  146. package/dist/stretch.js +9 -0
  147. package/dist/subject.d.ts +142 -0
  148. package/dist/subject.js +1 -0
  149. package/dist/tape.d.ts +9 -5
  150. package/dist/tape.js +5 -1
  151. package/dist/then.d.ts +9 -0
  152. package/dist/then.js +57 -0
  153. package/dist/ticker.d.ts +4 -4
  154. package/dist/trace.d.ts +4 -0
  155. package/dist/trace.js +255 -0
  156. package/dist/transport.d.ts +0 -8
  157. package/dist/transport.js +0 -4
  158. package/dist/turns.d.ts +4 -3
  159. package/dist/turns.js +4 -3
  160. package/dist/types/arc.d.ts +12 -11
  161. package/dist/types/channel.d.ts +12 -8
  162. package/dist/types/history.d.ts +35 -19
  163. package/dist/types/mix.d.ts +237 -163
  164. package/dist/types/motion.d.ts +151 -0
  165. package/dist/types/motion.js +1 -0
  166. package/dist/types/patch.d.ts +67 -34
  167. package/dist/types/place.d.ts +51 -36
  168. package/dist/types/trace.d.ts +51 -0
  169. package/dist/types/trace.js +1 -0
  170. package/dist/types/transport.d.ts +20 -18
  171. package/dist/types/voice.d.ts +144 -73
  172. package/dist/types/watch.d.ts +88 -40
  173. package/dist/types.d.ts +2 -0
  174. package/dist/unreached.d.ts +2 -0
  175. package/dist/unreached.js +7 -0
  176. package/dist/voice.d.ts +10 -143
  177. package/dist/voice.js +37 -82
  178. package/dist/warn.d.ts +2 -0
  179. package/dist/warn.js +1 -1
  180. package/dist/watch.d.ts +2 -2
  181. package/dist/watch.js +5 -3
  182. package/dist/wave.d.ts +4 -3
  183. package/dist/wave.js +3 -2
  184. package/dist/weigh.d.ts +7 -1
  185. package/dist/weigh.js +27 -0
  186. package/docs/schema.html +223 -34
  187. package/package.json +2 -2
package/dist/motions.js CHANGED
@@ -1,52 +1,20 @@
1
+ import { applied, change, commit, cut, dueTo, insert, peek, queue, rewind } from './changes.js';
1
2
  import { closed } from './closed.js';
3
+ import { made, pack, prune, reclaim, release, restretch, runAt, runThen, unpack, unrelease, } from './lives.js';
2
4
  import { absent, Numbers } from './numbers.js';
3
5
  import { reading } from './reading.js';
4
6
  import { Store } from './store.js';
7
+ import { numbers, } from './stretch.js';
5
8
  import { sloped } from './tweened.js';
6
- /**
7
- * A patch's law, `[form, settle, k1, k2, k3]`: which closed form its stretches follow, its `settle`,
8
- * and up to three constants that form reads. It heads the patch's `runs`, so `solve` finds them in
9
- * the buffer a sample already reads rather than in an object of their own.
10
- */
11
- const HEAD = 5;
12
- /** A run that shares nothing with the patch's own. */
13
- const runOf = (r) => ({
14
- flags: r.flags,
15
- stretches: r.stretches.map((seg) => ({ ...seg })),
16
- pending: r.pending?.map((c) => ({ ...c })),
17
- });
18
- /** `segs`, in time order, ahead of `list`: those older than its first, one per time. */
19
- function ahead(segs, list) {
20
- const first = list[0].at;
21
- const out = [];
22
- for (const seg of [...segs].sort((x, y) => x.at - y.at))
23
- if (seg.at < first && out[out.length - 1]?.at !== seg.at)
24
- out.push(seg);
25
- return out.length === 0 ? list : [...out, ...list];
26
- }
9
+ const HEAD = 5 /* Law.HEAD */;
27
10
  /** Where every patch's `sample` leaves a value on its way to the mix; none outlives its call. */
28
11
  let shared = { xs: new Float64Array(4), vs: new Float64Array(4) };
29
12
  /** What a patch's `xs` and `vs` are before its first subject sets its axes. */
30
13
  const unsized = new Float64Array(0);
31
- /** Where `applied` reads a stretch at the time a change lands; copied out before it returns. */
32
- let landing = { x: new Float64Array(4), v: new Float64Array(4) };
33
- /** Where `numbers` gathers what it copies, kept an array of doubles so every copy is one too. */
34
- const gathered = [0.5];
35
- /** `n` numbers of `from` from `at`, as an array of their own. */
36
- function numbers(from, at, n) {
37
- // `Array.from` walks a typed array by its iterator: about 500 B a call for three axes.
38
- for (let i = 0; i < n; i++)
39
- gathered[i] = from[at + i];
40
- return gathered.slice(0, n);
41
- }
42
- /**
43
- * A subject's flags: its value is a number; it has changes pending; it has older stretches; the
44
- * stretch it is playing was found landed, which holds until another replaces it.
45
- */
46
- const SCALAR = 1;
47
- const PENDING = 2;
48
- const OLDER = 4;
49
- const LANDED = 8;
14
+ const SCALAR = 1 /* RunFlag.SCALAR */;
15
+ const PENDING = 2 /* RunFlag.PENDING */;
16
+ const OLDER = 4 /* RunFlag.OLDER */;
17
+ const LANDED = 8 /* RunFlag.LANDED */;
50
18
  const wrongKind = (channel, holdsNumber) => new Error(`blits: channel ${channel} holds ${holdsNumber ? 'a number' : 'an array'}, and this motion patch gives a subject ${holdsNumber ? 'an array' : 'a number'}`);
51
19
  /**
52
20
  * A motion patch's subjects: the stretch each is playing, in one flat array by a number the patch
@@ -58,6 +26,7 @@ const wrongKind = (channel, holdsNumber) => new Error(`blits: channel ${channel}
58
26
  * 10k springs of one subject each, a buffer per field, a map lookup per sample and the law in an
59
27
  * object of its own took a call from 130 ns to 490.
60
28
  */
29
+ // biome-ignore lint/suspicious/noUnsafeDeclarationMerging: every member of `Methods` is installed below
61
30
  export class Motions {
62
31
  shape;
63
32
  numbers = new Numbers(this);
@@ -68,6 +37,7 @@ export class Motions {
68
37
  /** Axes per subject; -1 until the first subject sets it. */
69
38
  n = -1;
70
39
  cap = 0;
40
+ /** Numbers in each subject's run; a run starts at `base`. Written only here. */
71
41
  stride = 0;
72
42
  /**
73
43
  * The law, then each subject's run; empty until the first subject, the law read from `shape`.
@@ -124,6 +94,16 @@ export class Motions {
124
94
  const known = this.known(subject);
125
95
  if (known !== undefined)
126
96
  return known;
97
+ const seg = this.fresh(subject);
98
+ const s = this.take(subject);
99
+ this.runs[this.base(s) + 1] = this.freshFlags;
100
+ this.write(s, seg);
101
+ return s;
102
+ }
103
+ /** The flags of the run `fresh` last started. */
104
+ freshFlags = 0;
105
+ /** The stretch a subject met afresh starts with, its axes and the law set up for it, numbering no one. */
106
+ fresh(subject) {
127
107
  const x = this.shape.from(subject);
128
108
  const v = this.shape.velocity(subject, x);
129
109
  const to = this.shape.aim(x, v, null, subject, false);
@@ -136,10 +116,14 @@ export class Motions {
136
116
  throw wrongKind(this.channel, this.holdsNumber);
137
117
  if (this.n < 0)
138
118
  this.size(n);
139
- const s = this.take(subject);
140
- this.runs[this.base(s) + 1] = scalar ? SCALAR : 0;
141
- this.write(s, { at: 0, ms: this.shape.ms?.(subject) ?? 0, x0: x, v0: v, to });
142
- return s;
119
+ this.grow(1);
120
+ this.freshFlags = scalar ? SCALAR : 0;
121
+ return { at: 0, ms: this.shape.ms?.(subject) ?? 0, x0: x, v0: v, to };
122
+ }
123
+ /** The run `fresh` starts, as plain data. */
124
+ freshRun(subject) {
125
+ const seg = this.fresh(subject);
126
+ return { flags: this.freshFlags, stretches: [seg], pending: undefined };
143
127
  }
144
128
  /** Sets the axes every subject moves on, at the first. */
145
129
  size(n) {
@@ -175,120 +159,10 @@ export class Motions {
175
159
  * rewind before the release plays them on from there. Empty without history.
176
160
  */
177
161
  released = [];
178
- /**
179
- * Forgets a subject, so a later ask starts it afresh from `from`. Given the mix time `at` and frame
180
- * `seq`, under history, its run is kept for a rewind before then, until `reach` passes it.
181
- */
182
- release(subject, at, reach = Number.NEGATIVE_INFINITY, seq = Number.NaN) {
183
- const s = this.known(subject);
184
- if (s === undefined)
185
- return;
186
- if (at !== undefined) {
187
- const kept = [];
188
- for (const e of this.released)
189
- if (e.at >= reach)
190
- kept.push(e);
191
- else
192
- this.owner?.page(this.ownerId, 'released', e.subject, [
193
- { at: e.at, seq: e.seq, data: runOf(e) },
194
- ]);
195
- this.released = kept;
196
- this.released.push({ subject, at, seq, ...this.runAt(s) });
197
- }
198
- this.slots.delete(subject);
199
- this.numbers.release(s);
200
- }
201
- /** Puts back each subject released after frame `seq`, as it stood when released. */
202
- unrelease(seq) {
203
- const back = this.released.filter((e) => e.seq > seq).sort((a, b) => a.seq - b.seq);
204
- if (back.length === 0)
205
- return;
206
- this.released = this.released.filter((e) => e.seq <= seq);
207
- const done = new Set();
208
- for (const e of back) {
209
- if (done.has(e.subject))
210
- continue;
211
- done.add(e.subject);
212
- this.unpack(e.subject, e);
213
- }
214
- }
215
- /** Subject `s`'s run as it stands. */
216
- runAt(s) {
217
- const older = this.older?.[s];
218
- const pending = this.pending?.[s];
219
- return {
220
- flags: this.runs[this.base(s) + 1],
221
- stretches: older === undefined ? [this.latest(s)] : [...older, this.latest(s)],
222
- pending: pending === undefined ? undefined : [...pending],
223
- };
224
- }
225
162
  /** Whether the patch holds a run for `subject`. */
226
163
  holds(subject) {
227
164
  return this.known(subject) !== undefined;
228
165
  }
229
- /** A subject's run as plain data, for a voice leaving memory; undefined where it has none. */
230
- pack(subject) {
231
- const s = this.known(subject);
232
- return s === undefined ? undefined : runOf(this.runAt(s));
233
- }
234
- /** Puts a subject's run back as `pack` or a release left it, replacing any it has now. */
235
- unpack(subject, run) {
236
- const now = this.known(subject);
237
- if (now !== undefined) {
238
- this.slots.delete(subject);
239
- this.numbers.release(now);
240
- }
241
- if (this.n < 0)
242
- this.size(run.stretches[0].x0.length);
243
- const s = this.take(subject);
244
- this.runs[this.base(s) + 1] = run.flags & ~(OLDER | PENDING);
245
- const latest = run.stretches[run.stretches.length - 1];
246
- this.write(s, latest);
247
- if (run.stretches.length > 1) {
248
- if (this.older === null)
249
- this.older = [undefined];
250
- this.older[s] = run.stretches.slice(0, -1);
251
- this.flag(s, OLDER, true);
252
- }
253
- if (run.pending !== undefined) {
254
- if (this.pending === null)
255
- this.pending = [undefined];
256
- this.pending[s] = run.pending;
257
- this.flag(s, PENDING, true);
258
- }
259
- }
260
- /** A released run a history store gave back, for a rewind to before its release. */
261
- reclaim(subject, at, seq, run) {
262
- if (this.released.some((e) => e.seq === seq && Object.is(e.subject, subject)))
263
- return;
264
- this.released.push({ subject, at, seq, ...run });
265
- }
266
- /**
267
- * Stretches a history store gave back, each by the frame it was in force by: each life of the
268
- * subject, ended by a release, takes those from its own, ahead of the earliest it still keeps.
269
- */
270
- restretch(subject, back) {
271
- const lives = this.released
272
- .filter((e) => Object.is(e.subject, subject))
273
- .sort((x, y) => x.seq - y.seq);
274
- let from = Number.NEGATIVE_INFINITY;
275
- const within = (to) => back.filter((r) => r.seq > from && r.seq <= to).map((r) => r.data);
276
- for (const e of lives) {
277
- e.stretches = ahead(within(e.seq), e.stretches);
278
- from = e.seq;
279
- }
280
- const s = this.known(subject);
281
- if (s === undefined)
282
- return;
283
- const older = this.older?.[s] ?? [];
284
- const list = ahead(within(Number.POSITIVE_INFINITY), [...older, this.latest(s)]);
285
- if (list.length === older.length + 1)
286
- return;
287
- if (this.older === null)
288
- this.older = [undefined];
289
- this.older[s] = list.slice(0, -1);
290
- this.flag(s, OLDER, true);
291
- }
292
166
  /**
293
167
  * Told, with `watchId`, whenever a subject's stretch or what is pending for it changes, so a mix
294
168
  * keeping a copy of it knows to read it again. Set by the mix, cleared when its voice retires. An
@@ -339,7 +213,11 @@ export class Motions {
339
213
  }
340
214
  /** The value `at` hands the mix: the first axis for a number, a fresh array otherwise. */
341
215
  value(s, xs) {
342
- if ((this.runs[this.base(s) + 1] & SCALAR) !== 0)
216
+ return this.valueOf(this.runs[this.base(s) + 1], xs);
217
+ }
218
+ /** The value `at` hands the mix for a run with `flags`. */
219
+ valueOf(flags, xs) {
220
+ if ((flags & SCALAR) !== 0)
343
221
  return xs[0];
344
222
  const n = this.n;
345
223
  const out = new Array(n);
@@ -464,80 +342,6 @@ export class Motions {
464
342
  }
465
343
  return { x, v, s };
466
344
  }
467
- /**
468
- * Queues a retarget, set or push, untimed at the latest frame, for `subject`'s next read past its time.
469
- * The queue keeps the changes with a time in time order, ahead of any still waiting for one.
470
- */
471
- change(subject, c) {
472
- const back = this.owner === null ? Number.NaN : this.owner.revive(this.ownerId, subject);
473
- const s = this.slot(subject);
474
- this.check(c.to, this.n);
475
- this.check(c.x, this.n);
476
- this.check(c.v, this.n);
477
- c.made = this.owner === null ? Number.NaN : this.owner.now();
478
- c.seq = this.owner === null ? Number.NaN : this.owner.seq();
479
- if (c.at === undefined) {
480
- const at = this.frame(subject);
481
- if (!Number.isNaN(at))
482
- c.at = at;
483
- else if (!Number.isNaN(back))
484
- c.at = back;
485
- }
486
- const list = this.pending?.[s];
487
- if (list === undefined) {
488
- if (this.pending === null)
489
- this.pending = [undefined];
490
- this.pending[s] = [c];
491
- this.flag(s, PENDING, true);
492
- }
493
- else if (c.at === undefined)
494
- list.push(c);
495
- else
496
- this.queue(list, c, c.at);
497
- reading.moved++;
498
- const made = { at: c.at, to: c.to, x: c.x, v: c.v };
499
- this.owner?.changed(this.ownerId, this, subject, made);
500
- }
501
- /** Files a change with a time after every one due by then, ahead of those still waiting for one. */
502
- queue(list, c, at) {
503
- let i = list.length;
504
- while (i > 0) {
505
- const before = list[i - 1].at;
506
- if (before !== undefined && before <= at)
507
- break;
508
- i--;
509
- }
510
- list.splice(i, 0, c);
511
- }
512
- /** Every change due by `t` applied to a copy of the stretch playing then, committing nothing. */
513
- peek(s, t, xo, vo) {
514
- let seg = this.playing(s, t);
515
- const list = this.dueTo(s);
516
- if (list !== undefined)
517
- for (const change of list) {
518
- const a = change.at;
519
- if (a === undefined || !(a <= t))
520
- break;
521
- if (a >= seg.at)
522
- seg = this.applied(s, seg, change, a);
523
- }
524
- this.evaluateSegment(seg, t, xo, vo);
525
- }
526
- /** Subject `s`'s changes waiting, with those a read ahead's tape made filed among them by time. */
527
- dueTo(s) {
528
- const own = this.pending?.[s];
529
- const ahead = reading.ahead?.get(this);
530
- if (ahead === undefined)
531
- return own;
532
- const subject = this.numbers.subject(s);
533
- const made = subject === absent ? undefined : ahead.get(subject);
534
- if (made === undefined)
535
- return own;
536
- if (own === undefined)
537
- return made;
538
- // The patch's own were made first, and a stable sort keeps them first at one time.
539
- return [...own.filter((c) => c.at !== undefined), ...made].sort((a, b) => a.at - b.at);
540
- }
541
345
  check(a, n) {
542
346
  if (a !== undefined && a.length !== n)
543
347
  throw new Error('blits: a motion patch moves every subject on the same number of axes');
@@ -557,54 +361,6 @@ export class Motions {
557
361
  if (this.causes !== null)
558
362
  this.causes[s] = undefined;
559
363
  }
560
- /**
561
- * Takes back every change made after frame `seq`: each subject plays from its earliest stretch
562
- * kept, with the changes made by then queued to be applied again.
563
- */
564
- rewind(seq) {
565
- this.unrelease(seq);
566
- for (let s = 0; s < this.numbers.size; s++)
567
- if (this.numbers.subject(s) !== absent)
568
- this.cut(s, seq);
569
- }
570
- cut(s, seq) {
571
- const older = this.older?.[s];
572
- const pending = this.pending?.[s];
573
- const current = this.latest(s);
574
- const stretches = older === undefined ? [current] : [...older, current];
575
- const kept = [];
576
- let cut = false;
577
- for (const c of [...stretches.slice(1).map((seg) => seg.change), ...(pending ?? [])])
578
- if (c === undefined || c.seq === undefined || !(c.seq > seq)) {
579
- if (c !== undefined)
580
- kept.push(c);
581
- }
582
- else
583
- cut = true;
584
- if (!cut)
585
- return;
586
- if (older !== undefined) {
587
- this.older[s] = undefined;
588
- this.flag(s, OLDER, false);
589
- }
590
- this.write(s, stretches[0]);
591
- // Applied again in the order they take effect; one never stamped waits for the next read.
592
- const timed = kept
593
- .filter((c) => c.at !== undefined)
594
- .sort((a, b) => a.at - b.at);
595
- const list = [...timed, ...kept.filter((c) => c.at === undefined)];
596
- if (list.length === 0) {
597
- if (pending !== undefined) {
598
- this.pending[s] = undefined;
599
- this.flag(s, PENDING, false);
600
- }
601
- return;
602
- }
603
- if (this.pending === null)
604
- this.pending = [undefined];
605
- this.pending[s] = list;
606
- this.flag(s, PENDING, true);
607
- }
608
364
  /** Where subject `s`'s run starts in `runs`. */
609
365
  base(s) {
610
366
  return HEAD + s * this.stride;
@@ -666,99 +422,6 @@ export class Motions {
666
422
  }
667
423
  return list[lo];
668
424
  }
669
- applied(s, seg, change, at) {
670
- const n = this.n;
671
- if (landing.x.length < n)
672
- landing = { x: new Float64Array(n), v: new Float64Array(n) };
673
- const { x, v } = landing;
674
- this.evaluateSegment(seg, at, x, v);
675
- const xs = change.x ?? numbers(x, 0, n);
676
- const vs = change.v ?? numbers(v, 0, n);
677
- const subject = this.numbers.subject(s);
678
- const known = subject === absent ? undefined : subject;
679
- const put = change.x !== undefined && change.v === undefined;
680
- const to = this.shape.aim(xs, vs, change.to ?? seg.to, known, put);
681
- const ms = subject === absent ? seg.ms : (this.shape.ms?.(known) ?? 0);
682
- return { at, ms, x0: xs, v0: vs, to, change };
683
- }
684
- commit(s, t) {
685
- const list = this.pending[s];
686
- // The first read of a subject no frame had met: those waiting for a time take this one.
687
- if (list[list.length - 1].at === undefined) {
688
- let i = list.length;
689
- while (i > 0 && list[i - 1].at === undefined)
690
- i--;
691
- for (const c of list.splice(i)) {
692
- c.at = t;
693
- this.queue(list, c, t);
694
- }
695
- }
696
- while (list.length > 0) {
697
- const change = list[0];
698
- const a = change.at;
699
- if (a > t)
700
- break;
701
- list.shift();
702
- this.insert(s, this.applied(s, this.playing(s, a), change, a));
703
- }
704
- if (list.length === 0) {
705
- this.pending[s] = undefined;
706
- this.flag(s, PENDING, false);
707
- }
708
- }
709
- insert(s, seg) {
710
- let list = this.older?.[s];
711
- if (list === undefined) {
712
- list = [];
713
- if (this.older === null)
714
- this.older = [undefined];
715
- this.older[s] = list;
716
- this.flag(s, OLDER, true);
717
- }
718
- if (seg.at >= this.runs[this.base(s)]) {
719
- list.push(this.latest(s));
720
- this.write(s, seg);
721
- return;
722
- }
723
- let i = list.length;
724
- while (i > 0 && list[i - 1].at > seg.at)
725
- i--;
726
- list.splice(i, 0, seg);
727
- }
728
- /**
729
- * The mix time and frame a stretch was in force by, which a store keys it under: when the change
730
- * that ended it was made, which falls in the same life of the subject.
731
- */
732
- made(c) {
733
- const made = c?.made;
734
- if (made === undefined || Number.isNaN(made))
735
- return { at: this.owner?.now() ?? Number.NaN, seq: this.owner?.seq() ?? Number.NaN };
736
- return { at: made, seq: c?.seq ?? Number.NaN };
737
- }
738
- /** Lets go of stretches older than the one in force at `reading.horizon`. */
739
- prune(s) {
740
- const list = this.older?.[s];
741
- if (list === undefined)
742
- return;
743
- let n = 0;
744
- while (n < list.length &&
745
- (n + 1 < list.length ? list[n + 1].at : this.runs[this.base(s)]) <=
746
- reading.horizon)
747
- n++;
748
- if (n === 0)
749
- return;
750
- const out = list.splice(0, n);
751
- const subject = this.numbers.subject(s);
752
- if (this.owner !== null && subject !== absent)
753
- this.owner.page(this.ownerId, 'stretch', subject, out.map((seg, i) => {
754
- const next = out[i + 1] ?? list[0];
755
- return { ...this.made(next === undefined ? this.causes?.[s] : next.change), data: seg };
756
- }));
757
- if (list.length === 0) {
758
- this.older[s] = undefined;
759
- this.flag(s, OLDER, false);
760
- }
761
- }
762
425
  evaluateSegment(seg, t, xo, vo) {
763
426
  this.evaluate(seg.at, seg.ms, seg.x0, 0, seg.v0, 0, seg.to, 0, t, xo, vo);
764
427
  }
@@ -766,3 +429,26 @@ export class Motions {
766
429
  return closed(this.runs, this.shape.ease, this.n, at, ms, x0, x, v0, v, to, g, t, xo, vo);
767
430
  }
768
431
  }
432
+ const methods = {
433
+ release,
434
+ unrelease,
435
+ runAt,
436
+ pack,
437
+ unpack,
438
+ reclaim,
439
+ runThen,
440
+ restretch,
441
+ made,
442
+ prune,
443
+ change,
444
+ queue,
445
+ peek,
446
+ dueTo,
447
+ rewind,
448
+ cut,
449
+ applied,
450
+ commit,
451
+ insert,
452
+ };
453
+ for (const [name, value] of Object.entries(methods))
454
+ Object.defineProperty(Motions.prototype, name, { value, writable: true, configurable: true });
package/dist/moved.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { Motions } from './motions.js';
2
- import type { Subject } from './voice.js';
2
+ import type { Subject } from './subject.js';
3
3
  /**
4
4
  * A motion voice's delta for a subject on the general path: what its patch's `at` gives, written
5
5
  * into the record's last delta and the array that holds, where `at` makes an object and an array a
package/dist/moved.js CHANGED
@@ -1,4 +1,6 @@
1
1
  import { doubles } from './channels.js';
2
+ import { reading } from './reading.js';
3
+ import { sampleThen } from './then.js';
2
4
  /**
3
5
  * A motion voice's delta for a subject on the general path: what its patch's `at` gives, written
4
6
  * into the record's last delta and the array that holds, where `at` makes an object and an array a
@@ -6,16 +8,23 @@ import { doubles } from './channels.js';
6
8
  * `setting` has the voice time to sample at.
7
9
  */
8
10
  export function moved(run, key, subject, setting, rec) {
9
- let s = rec.run;
10
- if (rec.freed !== run.freed) {
11
- s = run.slot(subject);
12
- rec.run = s;
13
- rec.freed = run.freed;
11
+ let scalar;
12
+ const back = reading.back;
13
+ if (back !== null)
14
+ scalar = (sampleThen(run, subject, setting.elapsed, back) & 1 /* RunFlag.SCALAR */) !== 0;
15
+ else {
16
+ let s = rec.run;
17
+ if (rec.freed !== run.freed) {
18
+ s = run.slot(subject);
19
+ rec.run = s;
20
+ rec.freed = run.freed;
21
+ }
22
+ run.sample(s, setting.elapsed, run.xs, run.vs);
23
+ scalar = run.scalar(s);
14
24
  }
15
25
  const xs = run.xs;
16
- run.sample(s, setting.elapsed, xs, run.vs);
17
26
  const delta = rec.delta ?? {};
18
- if (run.scalar(s)) {
27
+ if (scalar) {
19
28
  delta[key] = xs[0];
20
29
  return delta;
21
30
  }
package/dist/named.d.ts CHANGED
@@ -5,8 +5,12 @@
5
5
  export declare class Named<I> {
6
6
  readonly list: readonly I[];
7
7
  private readonly set;
8
- constructor(subjects: Iterable<I>);
8
+ /** Each subject's `stagger` answer, asked at cue; null for a voice with no stagger. */
9
+ readonly delays: Map<I, number> | null;
10
+ /** The longest of `delays`, 0 at least, and the least, 0 at most: a voice's bounds at cue. */
11
+ readonly latest: number;
12
+ readonly soonest: number;
13
+ constructor(subjects: Iterable<I>, stagger?: (subject: I) => number);
9
14
  get size(): number;
10
15
  has(subject: I): boolean;
11
- [Symbol.iterator](): Iterator<I>;
12
16
  }
package/dist/named.js CHANGED
@@ -7,7 +7,21 @@ const FEW = 8;
7
7
  export class Named {
8
8
  list;
9
9
  set;
10
- constructor(subjects) {
10
+ /** Each subject's `stagger` answer, asked at cue; null for a voice with no stagger. */
11
+ delays = null;
12
+ /** The longest of `delays`, 0 at least, and the least, 0 at most: a voice's bounds at cue. */
13
+ latest = 0;
14
+ soonest = 0;
15
+ constructor(subjects, stagger) {
16
+ // An array of distinct subjects, as nearly every voice names, is copied without iterating it.
17
+ if (Array.isArray(subjects) && distinct(subjects)) {
18
+ this.list = subjects.slice();
19
+ this.set = null;
20
+ if (stagger)
21
+ this.delays = delaysOf(this.list, stagger);
22
+ [this.latest, this.soonest] = boundsOf(this.delays);
23
+ return;
24
+ }
11
25
  const list = [];
12
26
  let set = null;
13
27
  for (const s of subjects) {
@@ -26,6 +40,9 @@ export class Named {
26
40
  // Exactly as long as it is: a list grown by push keeps room for 17.
27
41
  this.list = list.slice();
28
42
  this.set = set;
43
+ if (stagger)
44
+ this.delays = delaysOf(this.list, stagger);
45
+ [this.latest, this.soonest] = boundsOf(this.delays);
29
46
  }
30
47
  get size() {
31
48
  return this.list.length;
@@ -33,7 +50,36 @@ export class Named {
33
50
  has(subject) {
34
51
  return this.set === null ? this.list.includes(subject) : this.set.has(subject);
35
52
  }
36
- [Symbol.iterator]() {
37
- return this.list[Symbol.iterator]();
53
+ }
54
+ function delaysOf(list, stagger) {
55
+ const delays = new Map();
56
+ for (const s of list)
57
+ delays.set(s, stagger(s));
58
+ return delays;
59
+ }
60
+ function boundsOf(delays) {
61
+ let latest = 0;
62
+ let soonest = 0;
63
+ for (const delay of delays?.values() ?? []) {
64
+ if (delay > latest)
65
+ latest = delay;
66
+ if (delay < soonest)
67
+ soonest = delay;
68
+ }
69
+ return [latest, soonest];
70
+ }
71
+ /** Whether a short array names no subject twice, as `includes` compares; a long one says no. */
72
+ function distinct(subjects) {
73
+ const n = subjects.length;
74
+ if (n > FEW)
75
+ return false;
76
+ for (let i = 1; i < n; i++) {
77
+ const a = subjects[i];
78
+ for (let j = 0; j < i; j++) {
79
+ const b = subjects[j];
80
+ if (a === b || (Number.isNaN(a) && Number.isNaN(b)))
81
+ return false;
82
+ }
38
83
  }
84
+ return true;
39
85
  }
package/dist/numbers.d.ts CHANGED
@@ -33,6 +33,13 @@ export declare class Numbers<I> {
33
33
  /** One past the highest number handed out, so a loop over 0..size meets every live one. */
34
34
  get size(): number;
35
35
  take(subject: I): number;
36
+ /** Whether `slot` is a number in use, its subject perhaps collected and not yet released. */
37
+ has(slot: number): boolean;
36
38
  subject(slot: number): I | typeof absent;
39
+ /**
40
+ * Hands the subject numbered `from` the free number `to`, and frees `from` without telling the
41
+ * owner, whose stores the caller moves; false, changing nothing, where either does not hold.
42
+ */
43
+ move(from: number, to: number): boolean;
37
44
  release(slot: number): void;
38
45
  }