@msb235/blits 0.10.0 → 0.12.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.
Files changed (126) hide show
  1. package/CHANGELOG.md +248 -0
  2. package/README.md +15 -4
  3. package/dist/arcs.d.ts +40 -0
  4. package/dist/arcs.js +239 -0
  5. package/dist/blend.js +1 -5
  6. package/dist/book.d.ts +2 -0
  7. package/dist/book.js +27 -3
  8. package/dist/chain.d.ts +17 -1
  9. package/dist/chain.js +57 -15
  10. package/dist/channels.d.ts +2 -0
  11. package/dist/channels.js +12 -1
  12. package/dist/clock.d.ts +14 -0
  13. package/dist/clock.js +9 -1
  14. package/dist/clone.js +16 -4
  15. package/dist/columns.d.ts +4 -5
  16. package/dist/columns.js +11 -5
  17. package/dist/crowd.d.ts +1 -1
  18. package/dist/crowd.js +4 -1
  19. package/dist/cue.js +63 -7
  20. package/dist/departed.d.ts +8 -3
  21. package/dist/departed.js +76 -29
  22. package/dist/due.d.ts +4 -0
  23. package/dist/due.js +16 -3
  24. package/dist/easing.d.ts +12 -1
  25. package/dist/easing.js +7 -1
  26. package/dist/everyone.d.ts +14 -1
  27. package/dist/everyone.js +27 -2
  28. package/dist/fade.d.ts +12 -5
  29. package/dist/fade.js +57 -19
  30. package/dist/fill.js +16 -9
  31. package/dist/fit.d.ts +8 -1
  32. package/dist/fit.js +7 -3
  33. package/dist/fold.d.ts +1 -1
  34. package/dist/fold.js +112 -9
  35. package/dist/frames.d.ts +2 -0
  36. package/dist/frames.js +17 -0
  37. package/dist/gather.js +38 -7
  38. package/dist/handle.d.ts +7 -2
  39. package/dist/handle.js +28 -12
  40. package/dist/held.d.ts +13 -2
  41. package/dist/held.js +43 -9
  42. package/dist/history.d.ts +25 -2
  43. package/dist/history.js +163 -35
  44. package/dist/hosts.js +45 -17
  45. package/dist/index.d.ts +5 -2
  46. package/dist/index.js +3 -1
  47. package/dist/keyfill.js +3 -1
  48. package/dist/lane.d.ts +1 -1
  49. package/dist/lane.js +3 -1
  50. package/dist/lanes.d.ts +31 -7
  51. package/dist/lanes.js +57 -6
  52. package/dist/lean.d.ts +15 -0
  53. package/dist/lean.js +23 -0
  54. package/dist/leavings.d.ts +6 -1
  55. package/dist/leavings.js +16 -2
  56. package/dist/marks.d.ts +7 -3
  57. package/dist/marks.js +105 -27
  58. package/dist/meet.js +1 -0
  59. package/dist/mixer.d.ts +35 -6
  60. package/dist/mixer.js +113 -45
  61. package/dist/motion.d.ts +4 -1
  62. package/dist/motion.js +16 -2
  63. package/dist/motions.d.ts +4 -0
  64. package/dist/motions.js +45 -11
  65. package/dist/move.js +35 -12
  66. package/dist/numbers.d.ts +5 -1
  67. package/dist/numbers.js +4 -0
  68. package/dist/owner.d.ts +18 -2
  69. package/dist/owner.js +44 -3
  70. package/dist/pack.d.ts +1 -0
  71. package/dist/pack.js +3 -0
  72. package/dist/place.d.ts +8 -0
  73. package/dist/place.js +16 -8
  74. package/dist/placed.d.ts +19 -0
  75. package/dist/placed.js +54 -0
  76. package/dist/project.d.ts +5 -2
  77. package/dist/project.js +238 -34
  78. package/dist/pull.js +37 -8
  79. package/dist/qualify.d.ts +10 -0
  80. package/dist/qualify.js +60 -3
  81. package/dist/reading.d.ts +8 -1
  82. package/dist/reading.js +8 -1
  83. package/dist/record.d.ts +6 -0
  84. package/dist/record.js +2 -0
  85. package/dist/restored.d.ts +69 -17
  86. package/dist/restored.js +163 -50
  87. package/dist/revive.d.ts +6 -3
  88. package/dist/revive.js +11 -3
  89. package/dist/roster.js +2 -1
  90. package/dist/rows.js +15 -5
  91. package/dist/seek.d.ts +7 -2
  92. package/dist/seek.js +152 -43
  93. package/dist/shared.d.ts +5 -0
  94. package/dist/shared.js +5 -0
  95. package/dist/signals.d.ts +10 -0
  96. package/dist/signals.js +17 -0
  97. package/dist/spans.d.ts +5 -0
  98. package/dist/spans.js +75 -5
  99. package/dist/store.d.ts +13 -0
  100. package/dist/store.js +23 -0
  101. package/dist/strays.js +50 -2
  102. package/dist/tape.d.ts +29 -4
  103. package/dist/tape.js +49 -11
  104. package/dist/touch.js +3 -2
  105. package/dist/transport.d.ts +11 -2
  106. package/dist/transport.js +26 -10
  107. package/dist/types/arc.d.ts +55 -0
  108. package/dist/types/arc.js +1 -0
  109. package/dist/types/mix.d.ts +57 -14
  110. package/dist/types/patch.d.ts +5 -1
  111. package/dist/types/place.d.ts +11 -4
  112. package/dist/types/transport.d.ts +12 -3
  113. package/dist/types/voice.d.ts +72 -5
  114. package/dist/types/watch.d.ts +126 -0
  115. package/dist/types/watch.js +1 -0
  116. package/dist/types.d.ts +2 -0
  117. package/dist/unpage.d.ts +6 -1
  118. package/dist/unpage.js +34 -2
  119. package/dist/voice.d.ts +93 -16
  120. package/dist/voice.js +60 -3
  121. package/dist/watch.d.ts +26 -0
  122. package/dist/watch.js +441 -0
  123. package/dist/weigh.d.ts +13 -2
  124. package/dist/weigh.js +10 -3
  125. package/docs/schema.html +106 -38
  126. package/package.json +10 -4
package/dist/mixer.js CHANGED
@@ -1,13 +1,14 @@
1
+ import { cueArc } from './arcs.js';
1
2
  import { blended, blendOf, laneRead, shareKept } from './blend.js';
2
- import { aims, chain, linkable, relink } 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
- import { beginFade, leftWith, parting } from './fade.js';
7
+ import { beginFade, leaveVoice, parting } from './fade.js';
7
8
  import { apply, baseFor, clamp, contribution, fold, foldDetour, folded, foldLoci, foldSharers, foldWith, isRest, keyed, linked, near, owedBy, passes, read, shared, slopeFor, tick, } from './fold.js';
8
9
  import { GoneIndex, goneIndex } from './gone.js';
9
10
  import { held, shownOf, sinceOf, unreachedOf } from './held.js';
10
- import { leave, releasing } from './history.js';
11
+ import { releasing } from './history.js';
11
12
  import { book, laneHost } from './hosts.js';
12
13
  import { Lanes } from './lanes.js';
13
14
  import { foldLocus } from './locus.js';
@@ -22,11 +23,12 @@ import { Outs } from './revive.js';
22
23
  import { ScoreIndex } from './scored.js';
23
24
  import { seek } from './seek.js';
24
25
  import { Fitting, refit } from './spans.js';
25
- import { Store } from './store.js';
26
- import { record } from './tape.js';
26
+ import { Masked, Store } from './store.js';
27
+ 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 } 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
@@ -37,6 +39,7 @@ export const defaults = { lanes: true };
37
39
  export class Mixer {
38
40
  kit;
39
41
  opts;
42
+ lanesOf;
40
43
  cued = [];
41
44
  /** Under `history`, voices that have left but that a read back may still reach. */
42
45
  gone = [];
@@ -87,10 +90,15 @@ export class Mixer {
87
90
  /** The last array `pull` read, by position, with each subject's chain head, to skip the lookup. */
88
91
  pulled = [];
89
92
  pulledHeads = [];
90
- /** Each remembered head's lane slot, and the `version` and `relinks` they were all current at. */
93
+ /**
94
+ * Each remembered head's lane slot, -1 at a position with none, and the `version` and `relinks`
95
+ * they were all current at.
96
+ */
91
97
  pulledSlots = new Int32Array(0);
92
98
  pulledVersion = Number.NaN;
93
99
  pulledRelinks = -1;
100
+ /** What `pull` knows of each channel it has written, by name; none until the first pull. */
101
+ pullColumns = null;
94
102
  /** Counts chains made stale one subject at a time, which `version` does not. */
95
103
  relinks = 0;
96
104
  /** Read once a sync, so a `reduce` function is not called per voice per subject. */
@@ -136,6 +144,8 @@ export class Mixer {
136
144
  */
137
145
  chains = new Store();
138
146
  version = 0;
147
+ /** What `prime` reads its clock, voice time and pass from: see `Primed`. */
148
+ primed = new Float64Array(3 /* Primed.SIZE */);
139
149
  steps = new Steps();
140
150
  /** Per subject, the voices whose `subjects` name it, in voice order. */
141
151
  named = new Store();
@@ -199,6 +209,8 @@ export class Mixer {
199
209
  bounded;
200
210
  /** By slot, a stock array channel's in-place `lerp`. */
201
211
  lerpsInto;
212
+ /** By slot, a stock scalar channel's arithmetic, which a fold applies without calling the channel. */
213
+ scalars;
202
214
  /** What a fold with a locus in play gathers into, one per depth of folds under way. */
203
215
  locusScratch = [];
204
216
  locusDepth = 0;
@@ -209,23 +221,32 @@ export class Mixer {
209
221
  bookers = null;
210
222
  /** The fit a span being cued takes, which `cue` hands its voice. */
211
223
  fitting = null;
224
+ /** What an arc being cued keeps, which `cue` hands its voice. */
225
+ arcing = null;
212
226
  /** Every owner still in the mix, null until one is cued. */
213
227
  owners = null;
214
228
  /** With a history store, the key each subject is paged under; null without one. */
215
229
  keys = null;
216
230
  /** With a history store, the voices it paged out whose handles are still held; null without one. */
217
231
  outs = null;
218
- constructor(kit, opts) {
232
+ constructor(kit, opts,
233
+ /** The lanes an entry was built with; null in one that leaves them out. */
234
+ lanesOf) {
219
235
  this.kit = kit;
220
236
  this.opts = opts;
237
+ this.lanesOf = lanesOf;
221
238
  this.names = Object.keys(kit);
222
239
  this.channels = this.names.map((k) => kit[k]);
223
240
  this.bounded = this.channels.flatMap((c, i) => (c.bounds ? [i] : []));
224
241
  this.lerpsInto = this.channels.map((c) => lerpInto(c));
242
+ this.scalars = this.channels.map((c) => scalarOp(c));
225
243
  this.names.forEach((k, i) => {
226
244
  this.slotOf.set(k, i);
227
245
  });
228
- this.lanes = (opts.lanes ?? defaults.lanes) ? new Lanes(laneHost(this)) : null;
246
+ if (lanesOf === null && opts.lanes === true)
247
+ throw new Error("blits: '@msb235/blits/lean' has no lanes; take mix from '@msb235/blits'");
248
+ this.lanes =
249
+ lanesOf !== null && (opts.lanes ?? defaults.lanes) ? new lanesOf(laneHost(this)) : null;
229
250
  this.name = opts.name;
230
251
  const shared = opts.transport;
231
252
  if (shared !== undefined) {
@@ -290,6 +311,9 @@ export class Mixer {
290
311
  get timestamp() {
291
312
  return this.transport.last;
292
313
  }
314
+ get seq() {
315
+ return this.transport.seq;
316
+ }
293
317
  get last() {
294
318
  return this.transport.last;
295
319
  }
@@ -308,16 +332,31 @@ export class Mixer {
308
332
  }
309
333
  cue(spec) {
310
334
  this.attached();
311
- const engine = this.opts.engine ?? mixer;
312
- if (!engine.runs.has(spec.patch.form))
335
+ const engine = this.opts.engine;
336
+ if (engine !== undefined && !engine.runs.has(spec.patch.form))
313
337
  throw new Error(`blits: engine ${engine.name} does not run ${spec.patch.form} patches`);
314
338
  return cue(this, spec);
315
339
  }
316
- blend(patches, by, spec = {}) {
340
+ blend(members, by, spec = {}) {
317
341
  const locus = `blend:${this.nextId}`;
318
- const of = blendOf(by, patches.length - 1);
319
- return patches.map((patch, i) => {
320
- const handle = this.cue({ ...spec, patch, weight: by, locus });
342
+ const of = blendOf(by, members.length - 1);
343
+ return members.map((member, i) => {
344
+ const own = {
345
+ ...spec,
346
+ patch: member,
347
+ weight: by,
348
+ locus,
349
+ };
350
+ if ('patch' in member) {
351
+ own.patch = member.patch;
352
+ if (member.name !== undefined)
353
+ own.name = member.name;
354
+ if (member.tags !== undefined)
355
+ own.tags = member.tags;
356
+ if (member.hits !== undefined)
357
+ own.hits = member.hits;
358
+ }
359
+ const handle = this.cue(own);
321
360
  const voice = this.cued[this.cued.length - 1];
322
361
  voice.blend = { of, i };
323
362
  of.members.push(voice);
@@ -330,6 +369,8 @@ export class Mixer {
330
369
  return this.cue({ ...spec, patch: ownerPatch });
331
370
  }
332
371
  span(spec) {
372
+ if ((spec.rate ?? 1) < 0)
373
+ throw new Error('blits: a span lays out its children forward from its start, so it is not cued backward');
333
374
  this.fitting = new Fitting(spec);
334
375
  try {
335
376
  const handle = this.owns(spec);
@@ -340,6 +381,9 @@ export class Mixer {
340
381
  this.fitting = null;
341
382
  }
342
383
  }
384
+ arc(spec) {
385
+ return cueArc(this, spec);
386
+ }
343
387
  sync(timestamp) {
344
388
  this.own('sync').sync(timestamp);
345
389
  }
@@ -359,16 +403,16 @@ export class Mixer {
359
403
  this.looked = true;
360
404
  pull(this, subjects, into);
361
405
  }
362
- seek(time) {
406
+ seek(to) {
363
407
  if (this.projecting)
364
408
  throw new Error('blits: a projection does not seek');
365
- seek(this.own('seek'), time);
409
+ seek(this.own('seek'), to);
366
410
  }
367
411
  prepare(time) {
368
412
  return this.own('prepare').prepare(time);
369
413
  }
370
- project(time) {
371
- return projectAll(this.transport, time).of(this);
414
+ project(at) {
415
+ return projectAll(this.transport, at).of(this);
372
416
  }
373
417
  announce(name, opts = {}) {
374
418
  this.attached();
@@ -469,18 +513,21 @@ export class Mixer {
469
513
  voice.written = true;
470
514
  beginFade(this, voice, { over: opts?.over });
471
515
  }
472
- record(this, 'mute', () => this.mute(opts));
516
+ record(this, 'mute', (stage) => stage.mix(this)?.mute(opts));
473
517
  }
474
518
  drop(subject) {
475
519
  this.frame = nextFrame();
476
- const head = this.chains.get(subject);
520
+ const head = headOf(this, subject);
477
521
  // Nothing that kept this head, such as `pull`'s remembered list, may take it as current again.
478
- if (head !== undefined) {
479
- head.version = Number.NaN;
480
- this.relinks++;
481
- }
482
- if (head !== undefined && head.slot >= 0 && this.lanes !== null)
483
- this.lanes.release(head.slot);
522
+ if (head !== undefined)
523
+ unlink(this, head);
524
+ // A number a seek carried over is released even where its chain is not linked since.
525
+ const slot = this.chains.get(subject)?.slot ?? -1;
526
+ if (slot >= 0 && this.lanes !== null)
527
+ this.lanes.release(slot);
528
+ // A read ahead's copy reads the live mix's poses, and so hides one where the mix deletes it.
529
+ if (this.projecting && !(this.pose instanceof Masked))
530
+ this.pose = new Masked(this.pose);
484
531
  this.pose.delete(subject);
485
532
  this.restStamps?.delete(subject);
486
533
  this.chains.delete(subject);
@@ -488,32 +535,24 @@ export class Mixer {
488
535
  // A record of it is only in a voice over every subject, one naming it, or one gone; a ramp out
489
536
  // of a voice that does not name it is in `parters`, and a patch's own state in `motions`.
490
537
  const kept = releasing(this);
491
- const forget = (voice) => {
492
- leave(this, voice, subject, leftWith(this, voice, subject));
493
- voice.subjects.delete(subject);
494
- voice.motion?.release(subject, ...kept);
495
- voice.parts?.delete(subject);
496
- voice.parted?.delete(subject);
497
- voice.blend?.of.reads.delete(subject);
498
- };
499
538
  for (const voice of this.general)
500
- forget(voice);
539
+ this.forgetIn(voice, subject, kept);
501
540
  const named = this.named.get(subject);
502
541
  if (named !== undefined)
503
542
  for (const voice of [...named])
504
- forget(voice);
543
+ this.forgetIn(voice, subject, kept);
505
544
  const gone = goneIndex(this);
506
545
  for (const voice of gone.general)
507
- forget(voice);
546
+ this.forgetIn(voice, subject, kept);
508
547
  const goneNaming = gone.named.get(subject);
509
548
  if (goneNaming !== undefined)
510
549
  for (const voice of goneNaming)
511
- forget(voice);
550
+ this.forgetIn(voice, subject, kept);
512
551
  if (this.owners !== null)
513
552
  for (const voice of this.owners)
514
- forget(voice);
553
+ this.forgetIn(voice, subject, kept);
515
554
  for (const voice of this.parters) {
516
- forget(voice);
555
+ this.forgetIn(voice, subject, kept);
517
556
  if (voice.parts?.size === 0)
518
557
  voice.parts = null;
519
558
  if (voice.parted?.size === 0)
@@ -524,11 +563,31 @@ export class Mixer {
524
563
  const strays = this.strays.get(subject);
525
564
  if (strays !== undefined)
526
565
  for (const motion of strays)
527
- motion.release(subject, ...kept);
566
+ this.releaseIn(motion, subject, kept);
528
567
  this.strays.delete(subject);
529
568
  for (const motion of this.strayAll)
530
- motion.release(subject, ...kept);
531
- record(this, 'drop', () => this.drop(subject));
569
+ this.releaseIn(motion, subject, kept);
570
+ this.recordDrop(subject);
571
+ }
572
+ /** Lets `voice` go of a dropped subject. Methods rather than closures, so a drop makes neither. */
573
+ forgetIn(voice, subject, kept) {
574
+ leaveVoice(this, voice, subject);
575
+ voice.subjects.delete(subject);
576
+ if (voice.motion !== undefined)
577
+ this.releaseIn(voice.motion, subject, kept);
578
+ voice.parts?.delete(subject);
579
+ voice.parted?.delete(subject);
580
+ voice.blend?.of.reads.delete(subject);
581
+ }
582
+ // A motion patch's state is the live mix's alone: a read ahead cannot start a subject afresh in it.
583
+ releaseIn(motion, subject, kept) {
584
+ if (!this.projecting)
585
+ motion.release(subject, kept[0], kept[1], kept[2]);
586
+ else if (motion.holds(subject))
587
+ unplayable('a drop of a subject a motion patch holds');
588
+ }
589
+ recordDrop(subject) {
590
+ record(this, 'drop', (stage) => stage.mix(this)?.drop(subject));
532
591
  }
533
592
  touch(subject) {
534
593
  this.attached();
@@ -639,6 +698,15 @@ function named(transport, name) {
639
698
  if (taken.some((m) => m.name === name))
640
699
  throw new Error(`blits: a mix named ${name} is already on this transport, or a seek could bring it back`);
641
700
  }
701
+ /** Makes a mix the host asked for, with the registry's history where it names none, and lists it there. */
702
+ export function made(kit, opts, lanes) {
703
+ const { history, added } = historyFor(opts);
704
+ const m = new Mixer(kit, added ? { ...opts, history } : opts, lanes);
705
+ if (added)
706
+ gave(m.transport);
707
+ register(m);
708
+ return m;
709
+ }
642
710
  /**
643
711
  * The one that ships: every form on the CPU, probed per subject on demand.
644
712
  *
@@ -648,7 +716,7 @@ export const mixer = {
648
716
  name: 'mixer',
649
717
  runs: new Set(['fn', 'keys', 'motion']),
650
718
  create(kit, opts) {
651
- return new Mixer(kit, opts);
719
+ return made(kit, opts, Lanes);
652
720
  },
653
721
  };
654
722
  /**
package/dist/motion.d.ts CHANGED
@@ -45,7 +45,10 @@ export type Moving<I, O, V extends Value> = Patch<I, O, void> & {
45
45
  */
46
46
  push(subject: I, velocity: V, at?: number): void;
47
47
  };
48
- /** The state behind a `'motion'` patch, for an engine that reads it directly. */
48
+ /**
49
+ * The state behind a `'motion'` patch, for an engine that reads it directly; a patch another copy
50
+ * of the same release made counts.
51
+ */
49
52
  export declare function motionOf<I>(p: object): Motions<I> | undefined;
50
53
  interface Common<I, V extends Value> {
51
54
  /** Units per second each subject starts moving at. Default 0. */
package/dist/motion.js CHANGED
@@ -1,11 +1,17 @@
1
1
  import { COAST, CRITICAL, EASED, OVER, UNDER } from './closed.js';
2
2
  import { curve } from './easing.js';
3
3
  import { Motions } from './motions.js';
4
+ import { release } from './shared.js';
4
5
  const axes = (v) => (typeof v === 'number' ? [v] : [...v]);
5
6
  /** Zero on every axis `x` has: a motion's velocity where its options give none. */
6
7
  const still = (x) => new Array(x.length).fill(0);
7
8
  const per = (p, subject) => typeof p === 'function' ? p(subject) : p;
8
9
  const scalarOf = (p) => typeof p === 'function' ? undefined : typeof p === 'number';
10
+ /**
11
+ * What marks a motion patch to every copy of blits of this release loaded in one realm, so a
12
+ * spring another copy made runs on this copy's lanes; it gives the patch's state.
13
+ */
14
+ const MOTIONS = Symbol.for(`@msb235/blits/motions@${release}`);
9
15
  /** One `writes` array per channel, shared by every motion patch that writes it. */
10
16
  const writesOf = new Map();
11
17
  /**
@@ -29,6 +35,9 @@ class MotionPatch {
29
35
  get motion() {
30
36
  return this.motions.spec();
31
37
  }
38
+ get [MOTIONS]() {
39
+ return this.motions;
40
+ }
32
41
  at(_phase, subject, setting) {
33
42
  const state = this.motions;
34
43
  const s = state.slot(subject);
@@ -58,9 +67,14 @@ class GlidePatch extends MotionPatch {
58
67
  this.motions.change(subject, { at, v: axes(velocity) });
59
68
  }
60
69
  }
61
- /** The state behind a `'motion'` patch, for an engine that reads it directly. */
70
+ /**
71
+ * The state behind a `'motion'` patch, for an engine that reads it directly; a patch another copy
72
+ * of the same release made counts.
73
+ */
62
74
  export function motionOf(p) {
63
- return p instanceof MotionPatch ? p.motions : undefined;
75
+ return p instanceof MotionPatch
76
+ ? p.motions
77
+ : p[MOTIONS];
64
78
  }
65
79
  /**
66
80
  * The shapes of the stock motions, each one object over the options it reads, copied out of them so
package/dist/motions.d.ts CHANGED
@@ -167,6 +167,8 @@ export declare class Motions<I> {
167
167
  private unrelease;
168
168
  /** Subject `s`'s run as it stands. */
169
169
  private runAt;
170
+ /** Whether the patch holds a run for `subject`. */
171
+ holds(subject: I): boolean;
170
172
  /** A subject's run as plain data, for a voice leaving memory; undefined where it has none. */
171
173
  pack(subject: I): Run | undefined;
172
174
  /** Puts a subject's run back as `pack` or a release left it, replacing any it has now. */
@@ -244,6 +246,8 @@ export declare class Motions<I> {
244
246
  private queue;
245
247
  /** Every change due by `t` applied to a copy of the stretch playing then, committing nothing. */
246
248
  private peek;
249
+ /** Subject `s`'s changes waiting, with those a read ahead's tape made filed among them by time. */
250
+ private dueTo;
247
251
  private check;
248
252
  private flag;
249
253
  forget(s: number): void;
package/dist/motions.js CHANGED
@@ -28,6 +28,17 @@ function ahead(segs, list) {
28
28
  let shared = { xs: new Float64Array(4), vs: new Float64Array(4) };
29
29
  /** What a patch's `xs` and `vs` are before its first subject sets its axes. */
30
30
  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
+ }
31
42
  /**
32
43
  * A subject's flags: its value is a number; it has changes pending; it has older stretches; the
33
44
  * stretch it is playing was found landed, which holds until another replaces it.
@@ -211,6 +222,10 @@ export class Motions {
211
222
  pending: pending === undefined ? undefined : [...pending],
212
223
  };
213
224
  }
225
+ /** Whether the patch holds a run for `subject`. */
226
+ holds(subject) {
227
+ return this.known(subject) !== undefined;
228
+ }
214
229
  /** A subject's run as plain data, for a voice leaving memory; undefined where it has none. */
215
230
  pack(subject) {
216
231
  const s = this.known(subject);
@@ -496,7 +511,7 @@ export class Motions {
496
511
  /** Every change due by `t` applied to a copy of the stretch playing then, committing nothing. */
497
512
  peek(s, t, xo, vo) {
498
513
  let seg = this.playing(s, t);
499
- const list = this.pending?.[s];
514
+ const list = this.dueTo(s);
500
515
  if (list !== undefined)
501
516
  for (const change of list) {
502
517
  const a = change.at;
@@ -507,6 +522,21 @@ export class Motions {
507
522
  }
508
523
  this.evaluateSegment(seg, t, xo, vo);
509
524
  }
525
+ /** Subject `s`'s changes waiting, with those a read ahead's tape made filed among them by time. */
526
+ dueTo(s) {
527
+ const own = this.pending?.[s];
528
+ const ahead = reading.ahead?.get(this);
529
+ if (ahead === undefined)
530
+ return own;
531
+ const subject = this.numbers.subject(s);
532
+ const made = subject === absent ? undefined : ahead.get(subject);
533
+ if (made === undefined)
534
+ return own;
535
+ if (own === undefined)
536
+ return made;
537
+ // The patch's own were made first, and a stable sort keeps them first at one time.
538
+ return [...own.filter((c) => c.at !== undefined), ...made].sort((a, b) => a.at - b.at);
539
+ }
510
540
  check(a, n) {
511
541
  if (a !== undefined && a.length !== n)
512
542
  throw new Error('blits: a motion patch moves every subject on the same number of axes');
@@ -611,9 +641,9 @@ export class Motions {
611
641
  return {
612
642
  at: this.runs[this.base(s)],
613
643
  ms: this.runs[this.base(s) + 2],
614
- x0: Array.from(this.runs.subarray(x, x + n)),
615
- v0: Array.from(this.runs.subarray(x + n, x + 2 * n)),
616
- to: Array.from(this.runs.subarray(x + 2 * n, x + 3 * n)),
644
+ x0: numbers(this.runs, x, n),
645
+ v0: numbers(this.runs, x + n, n),
646
+ to: numbers(this.runs, x + 2 * n, n),
617
647
  change: this.causes?.[s],
618
648
  };
619
649
  }
@@ -636,11 +666,13 @@ export class Motions {
636
666
  return list[lo];
637
667
  }
638
668
  applied(s, seg, change, at) {
639
- const x = new Float64Array(this.n);
640
- const v = new Float64Array(this.n);
669
+ const n = this.n;
670
+ if (landing.x.length < n)
671
+ landing = { x: new Float64Array(n), v: new Float64Array(n) };
672
+ const { x, v } = landing;
641
673
  this.evaluateSegment(seg, at, x, v);
642
- const xs = Array.from(x);
643
- const vs = change.v ?? Array.from(v);
674
+ const xs = numbers(x, 0, n);
675
+ const vs = change.v ?? numbers(v, 0, n);
644
676
  const subject = this.numbers.subject(s);
645
677
  const known = subject === absent ? undefined : subject;
646
678
  const to = this.shape.aim(xs, vs, change.to ?? seg.to, known);
@@ -695,8 +727,7 @@ export class Motions {
695
727
  * The mix time and frame a stretch was in force by, which a store keys it under: when the change
696
728
  * that ended it was made, which falls in the same life of the subject.
697
729
  */
698
- made(next) {
699
- const c = next.change;
730
+ made(c) {
700
731
  const made = c?.made;
701
732
  if (made === undefined || Number.isNaN(made))
702
733
  return { at: this.owner?.now() ?? Number.NaN, seq: this.owner?.seq() ?? Number.NaN };
@@ -717,7 +748,10 @@ export class Motions {
717
748
  const out = list.splice(0, n);
718
749
  const subject = this.numbers.subject(s);
719
750
  if (this.owner !== null && subject !== absent)
720
- this.owner.page(this.ownerId, 'stretch', subject, out.map((seg, i) => ({ ...this.made(out[i + 1] ?? list[0] ?? this.latest(s)), data: seg })));
751
+ this.owner.page(this.ownerId, 'stretch', subject, out.map((seg, i) => {
752
+ const next = out[i + 1] ?? list[0];
753
+ return { ...this.made(next === undefined ? this.causes?.[s] : next.change), data: seg };
754
+ }));
721
755
  if (list.length === 0) {
722
756
  this.older[s] = undefined;
723
757
  this.flag(s, OLDER, false);
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
- import { popDue, schedule } from './due.js';
4
- import { detour } from './everyone.js';
4
+ import { byId, popDue, schedule } from './due.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';
@@ -66,7 +67,13 @@ function moveTo(mix, now) {
66
67
  (voice.owner === null ? now : ownerReading(voice.owner, now)) >= voice.start)
67
68
  begin(mix, voice, now);
68
69
  // After it starts, so a fade due by now on a voice that started since the last frame begins.
69
- if ((voice.state === 'live' || voice.state === 'frozen') && voice.outAt <= now)
70
+ // Not one set for after its passes run out, where a move across both would begin it first.
71
+ if ((voice.state === 'live' || voice.state === 'frozen') &&
72
+ voice.outAt <= now &&
73
+ !(voice.state === 'live' &&
74
+ !voice.freezesAfter &&
75
+ !voice.freezesBefore &&
76
+ voice.endsAt() < voice.outAt))
70
77
  beginFade(mix, voice, { over: Number.isNaN(voice.outOver) ? undefined : voice.outOver }, Math.max(startOf(voice), voice.outAt));
71
78
  if (voice.state !== 'pending') {
72
79
  if (voice.turns)
@@ -123,8 +130,12 @@ function moveTo(mix, now) {
123
130
  if (!mix.projecting)
124
131
  schedule(mix, voice);
125
132
  }
126
- if (mix.owners !== null)
133
+ if (mix.owners !== null) {
127
134
  lapse(mix, now);
135
+ settleArcs(mix, now);
136
+ }
137
+ if (mix.sharers.length > 0 && !mix.projecting)
138
+ outgrow(mix);
128
139
  mix.stirred = false;
129
140
  if (mix.retired.length > 0)
130
141
  prune(mix);
@@ -155,11 +166,16 @@ function turn(mix, voice, now) {
155
166
  voice.anchorNow = at;
156
167
  noted(mix, voice);
157
168
  }
158
- /** Past this many voices retiring in one frame, one pass over the list beats a search for each. */
169
+ /**
170
+ * Past this many voices retiring in one frame, and more than one in `SPARSE` of the list, one pass
171
+ * over the list beats a search for each.
172
+ */
159
173
  const SEARCHED = 8;
174
+ const SPARSE = 32;
160
175
  /**
161
- * Takes the voices retired since the last prune out of the list, in list order. A few are found by
162
- * their id, which the list is in order of, so the voices still in it are not read.
176
+ * Takes the voices retired since the last prune out of the list, in list order. A few, or a small
177
+ * share of a long list, are found by their id, which the list is in order of, so the voices still
178
+ * in it are not read.
163
179
  */
164
180
  function prune(mix) {
165
181
  const retired = mix.retired;
@@ -167,9 +183,8 @@ function prune(mix) {
167
183
  const voices = mix.cued;
168
184
  let at = null;
169
185
  // A projection's list holds copies of voices gone before it, which only a pass takes out.
170
- if (retired.length <= SEARCHED && !mix.projecting) {
171
- if (retired.length > 1)
172
- retired.sort((a, b) => a.id - b.id);
186
+ if ((retired.length <= SEARCHED || retired.length * SPARSE <= voices.length) && !mix.projecting) {
187
+ byId(retired);
173
188
  at = [];
174
189
  for (let k = 0; k < retired.length; k++) {
175
190
  const voice = retired[k];
@@ -185,8 +200,16 @@ function prune(mix) {
185
200
  if (at !== null) {
186
201
  for (const voice of retired)
187
202
  pruned = leave(mix, voice) || pruned;
188
- for (let k = at.length - 1; k >= 0; k--)
189
- voices.splice(at[k], 1);
203
+ // Closed up in one pass from the first to go: a splice for each moves the tail once a voice.
204
+ let kept = at[0];
205
+ let k = 0;
206
+ for (let i = kept; i < voices.length; i++) {
207
+ if (at[k] === i)
208
+ k++;
209
+ else
210
+ voices[kept++] = voices[i];
211
+ }
212
+ voices.length = kept;
190
213
  }
191
214
  else {
192
215
  let kept = 0;
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 readonly owner;
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.d.ts CHANGED
@@ -9,6 +9,19 @@ export declare class Holding<V> {
9
9
  /** How many it has held, and how many of those finished their passes. */
10
10
  kids: number;
11
11
  played: number;
12
+ /**
13
+ * When the children that left coast, as pairs of the frame one left in and the latest coast so
14
+ * far, mix time: a pair only where the latest grew, so a seek back cuts it to a prefix.
15
+ */
16
+ coasts: number[];
17
+ /** The frame the first child to leave before its passes ended left in; Infinity for none. */
18
+ cutSeq: number;
19
+ /** The latest coast of a child that left; -Infinity for none. */
20
+ get coast(): number;
21
+ /** A child left in frame `seq`, coasting at `coast`, or before its passes ended for undefined. */
22
+ left(coast: number | undefined, seq: number): void;
23
+ /** Forgets the children that left after frame `seq`, for a seek or read back to it. */
24
+ back(seq: number): void;
12
25
  }
13
26
  /**
14
27
  * What an owner's clock reads at mix time `now`, through every owner above it: -Infinity before it
@@ -41,5 +54,8 @@ export declare function heldByInput<I, O>(voice: Voice<I, O>): boolean;
41
54
  export declare function ownWeight<I, O>(owner: Voice<I, O>, now: number, reduced: boolean, base: (owner: Voice<I, O>) => number): number;
42
55
  /** What every owner above a voice multiplies into its weight. */
43
56
  export declare function ownersWeight<I, O>(voice: Voice<I, O>, now: number, reduced: boolean, base: (owner: Voice<I, O>) => number): number;
44
- /** Points a projection's copies at each other's owners, each owner holding only copies. */
45
- export declare function relink<I, O>(copies: readonly Voice<I, O>[]): void;
57
+ /**
58
+ * Points a projection's copies at each other's owners, each owner holding only copies; for a read
59
+ * back to frame `seq`, as its children that had left by then left it.
60
+ */
61
+ export declare function relink<I, O>(copies: readonly Voice<I, O>[], seq?: number): void;