@msb235/blits 0.12.0 → 0.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,19 @@ This package follows [semver](https://semver.org). Below 1.0.0, a breaking chang
4
4
  version and everything else the patch. Each release lists its changes as **Breaking**, **Added** and
5
5
  **Fixed**, and the release workflow refuses a tag with no section here.
6
6
 
7
+ ## 0.12.1
8
+
9
+ ### Added
10
+
11
+ - `mix.close()`: takes a mix off the list debuggers read, for a host tearing it down, where it would
12
+ otherwise stay listed until collected. It stops every debugger watching it, and frees a debugger's
13
+ hold unless another listed mix shares its transport.
14
+
15
+ ### Fixed
16
+
17
+ - An arc not yet started, cued late or under an owner yet to start, no longer shows its enter's
18
+ first frame from the moment it is cued.
19
+
7
20
  ## 0.12.0
8
21
 
9
22
  ### Added
package/dist/arcs.js CHANGED
@@ -68,8 +68,10 @@ function rampOf(s, e, end, blend, t) {
68
68
  */
69
69
  export function arcShare(arcVoice, child, now, reduced) {
70
70
  const arc = arcVoice.arc;
71
- const read = ownerReading(arcVoice, now);
72
- const t = read > 0 ? read : 0;
71
+ const t = ownerReading(arcVoice, now);
72
+ // Its parts freeze on their first frames, so an arc yet to start has to silence them itself.
73
+ if (!(t >= 0))
74
+ return 0;
73
75
  const segs = segments(arc, arcVoice.hold);
74
76
  const end = arc.enter + arcVoice.hold + arc.exit;
75
77
  const blend = reduced ? 0 : arc.blend;
package/dist/mixer.d.ts CHANGED
@@ -264,6 +264,7 @@ export declare class Mixer<I, O> implements Mix<I, O> {
264
264
  ramp(rate: number, over: number): void;
265
265
  /** Whether a voice will change no pose from `now` on, short of a change made to it. */
266
266
  still(voice: Voice<I, O>, now: number): boolean;
267
+ close(): void;
267
268
  mute(opts?: {
268
269
  over?: number;
269
270
  }): void;
package/dist/mixer.js CHANGED
@@ -28,7 +28,7 @@ import { record, unplayable } from './tape.js';
28
28
  import { touch } from './touch.js';
29
29
  import { announce, Transport } from './transport.js';
30
30
  import { none } from './voice.js';
31
- import { gave, historyFor, register } from './watch.js';
31
+ import { gave, historyFor, register, unregister } from './watch.js';
32
32
  import { base, capped, fadeOf, horizonFor, ownedBy, ownerBase, prime, weigh, weighOwned, } from './weigh.js';
33
33
  /**
34
34
  * What a mix takes where its options say nothing. Not exported from the package: the suite turns
@@ -508,6 +508,9 @@ export class Mixer {
508
508
  const motion = voice.motion;
509
509
  return motion !== undefined && voice.elapsedAt(now) >= voice.latest && motion.landed();
510
510
  }
511
+ close() {
512
+ unregister(this);
513
+ }
511
514
  mute(opts) {
512
515
  for (const voice of this.cued) {
513
516
  voice.written = true;
package/dist/shared.d.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  * The package version, for a fact whose shape follows the code too closely to version by hand:
3
3
  * copies of different releases keep apart. `test/copies.test.ts` holds it to `package.json`.
4
4
  */
5
- export declare const release = "0.12.0";
5
+ export declare const release = "0.12.1";
6
6
  /**
7
7
  * A WeakMap every copy of blits loaded in one realm shares, so a fact one copy records about a
8
8
  * patch or channel it made is read by another copy's mix. The name carries a version, bumped when
package/dist/shared.js CHANGED
@@ -2,7 +2,7 @@
2
2
  * The package version, for a fact whose shape follows the code too closely to version by hand:
3
3
  * copies of different releases keep apart. `test/copies.test.ts` holds it to `package.json`.
4
4
  */
5
- export const release = '0.12.0';
5
+ export const release = '0.12.1';
6
6
  /**
7
7
  * A WeakMap every copy of blits loaded in one realm shares, so a fact one copy records about a
8
8
  * patch or channel it made is read by another copy's mix. The name carries a version, bumped when
@@ -313,6 +313,12 @@ export interface Mix<I, O, H = unknown> {
313
313
  * A frame loop sleeping on `inert` subscribes here to wake.
314
314
  */
315
315
  onWake(fn: () => void): () => void;
316
+ /**
317
+ * Takes the mix off the list debuggers read (see `watched`), for a host done with it, where it
318
+ * would otherwise stay until collected. Stops every watcher, and frees a hold no other listed mix
319
+ * on its transport shares. It still plays if synced.
320
+ */
321
+ close(): void;
316
322
  /** Fades every voice out: over `over` when given, over each voice's own `fade.out` otherwise. */
317
323
  mute(opts?: {
318
324
  over?: number;
package/dist/watch.d.ts CHANGED
@@ -23,4 +23,6 @@ export declare function historyFor(opts: {
23
23
  export declare function gave(transport: object): void;
24
24
  /** Lists a mix the host made, under an entry this copy builds. */
25
25
  export declare function register(mix: Any): void;
26
+ /** Unlists a mix, telling every listener it is gone; one not listed is left alone. */
27
+ export declare function unregister(mix: Any): void;
26
28
  export {};
package/dist/watch.js CHANGED
@@ -100,10 +100,27 @@ export function register(mix) {
100
100
  };
101
101
  r.entries_.set(mix, entry);
102
102
  r.refs.set(id, new WeakRef(mix));
103
- r.gone.register(mix, id);
103
+ r.gone.register(mix, id, mix);
104
104
  for (const fn of [...r.fns])
105
105
  fn({ kind: 'add', entry });
106
106
  }
107
+ /** Unlists a mix, telling every listener it is gone; one not listed is left alone. */
108
+ export function unregister(mix) {
109
+ const r = registry();
110
+ const entry = r.entries_.get(mix);
111
+ if (entry === undefined || !r.refs.has(entry.id))
112
+ return;
113
+ r.refs.delete(entry.id);
114
+ r.gone.unregister(mix);
115
+ untap(mix);
116
+ const t = mix.transport;
117
+ const held = ctlOf(t).held;
118
+ const listed = (m) => r.refs.has(r.entries_.get(m)?.id ?? -1);
119
+ if (held !== null && !t.members.some(listed))
120
+ held.free();
121
+ for (const fn of [...r.fns])
122
+ fn({ kind: 'gone', id: entry.id });
123
+ }
107
124
  function labelOf(mix, subject) {
108
125
  const own = mix.opts.labelOf?.(subject);
109
126
  return own ?? guess(subject);
@@ -251,12 +268,19 @@ function stopper(ref, fn) {
251
268
  const own = m === undefined ? undefined : taps.get(m);
252
269
  if (m === undefined || own === undefined || !own.fns.delete(fn) || own.fns.size > 0)
253
270
  return;
254
- own.untap();
255
- taps.delete(m);
256
- ctlOf(m.transport).tapped--;
257
- fit(m.transport);
271
+ untap(m);
258
272
  };
259
273
  }
274
+ /** Puts back what watching `mix` swapped, whoever is still watching. */
275
+ function untap(mix) {
276
+ const own = taps.get(mix);
277
+ if (own === undefined)
278
+ return;
279
+ own.untap();
280
+ taps.delete(mix);
281
+ ctlOf(mix.transport).tapped--;
282
+ fit(mix.transport);
283
+ }
260
284
  /** The frame a read lands in, handing the last one over where the mix has moved on since. */
261
285
  function frameOf(mix) {
262
286
  const t = taps.get(mix);
@@ -375,6 +399,7 @@ function hold(t) {
375
399
  host = ts;
376
400
  at();
377
401
  },
402
+ free: () => handle.free(),
378
403
  };
379
404
  fit(t);
380
405
  t.woke();
@@ -382,7 +407,7 @@ function hold(t) {
382
407
  if (freed)
383
408
  throw new Error('blits: that hold was freed');
384
409
  };
385
- return {
410
+ const handle = {
386
411
  get mixes() {
387
412
  return [...t.members];
388
413
  },
@@ -438,4 +463,5 @@ function hold(t) {
438
463
  }
439
464
  },
440
465
  };
466
+ return handle;
441
467
  }
package/docs/schema.html CHANGED
@@ -857,7 +857,7 @@ phase.result; <span class="cm">// { budget, length, over, skipped, fell }</spa
857
857
  <pre><code>const phase = mix.span({ name: 'phase', order: 'queue', duration: 2000 });
858
858
  mix.cue({ patch: writeChange(change), owner: phase, rank: change.line,
859
859
  earliest: { of: { name: `orb ${change.id}`, score: 'orbs' }, mark: 'end' } });</code></pre></li>
860
- <li id="arcs"><b>Arcs.</b> <code>mix.arc(spec)</code> cues an owner holding an ordered chain of three parts that hand over on its clock: <code>enter</code>, then <code>active</code> for the <code>hold</code>, then <code>exit</code>. It is klieg's <code>fire(text, { enter, active, exit, hold, blendMs })</code> as a blits primitive, with the same part names so the two line up, and it replaces a host adding up starts by hand: the marquee's program is a span queueing arcs. Built 2026-10-22.
860
+ <li id="arcs"><b>Arcs.</b> <code>mix.arc(spec)</code> cues an owner holding an ordered chain of three parts that hand over on its clock: <code>enter</code>, then <code>active</code> for the <code>hold</code>, then <code>exit</code>. It is klieg's <code>fire(text, { enter, active, exit, hold, blendMs })</code> as a blits primitive, with the same part names so the two line up, and it replaces a host adding up starts by hand: bree's marquee runs its program as a span queueing arcs. Built 2026-10-22.
861
861
  <pre><code>const word = mix.arc({
862
862
  name: 'JACKPOT',
863
863
  enter: { patch: rise, stagger: byLetter, duration: 600 }, <span class="cm">// a voice's spec, or several layered</span>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@msb235/blits",
3
- "version": "0.12.0",
3
+ "version": "0.12.1",
4
4
  "description": "Concurrent effects: each on its own clock with its own state and weight, folded into one value per subject per frame",
5
5
  "keywords": [
6
6
  "animation",