pixi-effects 0.26.1 → 0.26.2

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 (37) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +6 -6
  3. package/ai/chat-template.html +3 -3
  4. package/dist/Composition-H4QA24MV.js +7 -0
  5. package/dist/{Composition-Z2VY3KWZ.js.map → Composition-H4QA24MV.js.map} +1 -1
  6. package/dist/Composition-HM4YT5L7.cjs +16 -0
  7. package/dist/{Composition-THQPG4E5.cjs.map → Composition-HM4YT5L7.cjs.map} +1 -1
  8. package/dist/{chunk-H3RHEH5W.cjs → chunk-555URQHQ.cjs} +59 -59
  9. package/dist/{chunk-H3RHEH5W.cjs.map → chunk-555URQHQ.cjs.map} +1 -1
  10. package/dist/{chunk-YEVAUQ6Y.js → chunk-GBUM7X2S.js} +3 -3
  11. package/dist/{chunk-YEVAUQ6Y.js.map → chunk-GBUM7X2S.js.map} +1 -1
  12. package/dist/{chunk-EABL3UKJ.cjs → chunk-WPXDRSGN.cjs} +162 -9
  13. package/dist/chunk-WPXDRSGN.cjs.map +1 -0
  14. package/dist/{chunk-JHJNEIDI.js → chunk-X3HB4DSR.js} +162 -9
  15. package/dist/chunk-X3HB4DSR.js.map +1 -0
  16. package/dist/index.cjs +29 -29
  17. package/dist/index.js +5 -5
  18. package/dist/loader.css +1 -1
  19. package/dist/{music-AF4VTFAF.js → music-3O5MDQXY.js} +52 -6
  20. package/dist/music-3O5MDQXY.js.map +1 -0
  21. package/dist/{music-KB7YZLHP.cjs → music-4O367SPR.cjs} +52 -6
  22. package/dist/music-4O367SPR.cjs.map +1 -0
  23. package/dist/three.cjs +5 -5
  24. package/dist/three.js +1 -1
  25. package/docs/dsl.md +2 -2
  26. package/llms-full.txt +5 -4
  27. package/package.json +1 -1
  28. package/skills/pixi-effects/SKILL.md +1 -1
  29. package/skills/pixi-effects/reference/cheatsheet.md +1 -1
  30. package/skills/pixi-effects/reference/pitfalls.md +1 -0
  31. package/skills/pixi-effects/template.html +6 -6
  32. package/dist/Composition-THQPG4E5.cjs +0 -16
  33. package/dist/Composition-Z2VY3KWZ.js +0 -7
  34. package/dist/chunk-EABL3UKJ.cjs.map +0 -1
  35. package/dist/chunk-JHJNEIDI.js.map +0 -1
  36. package/dist/music-AF4VTFAF.js.map +0 -1
  37. package/dist/music-KB7YZLHP.cjs.map +0 -1
@@ -2965,7 +2965,7 @@ var AudioSequence = class extends Sequence {
2965
2965
  const raw = spec;
2966
2966
  if (raw.asset !== void 0) console.warn(`pixi-effects: ${who}: has both asset and music \u2014 the music plays; remove one`);
2967
2967
  if (raw.sfx !== void 0) console.warn(`pixi-effects: ${who}: has both sfx and music \u2014 the music plays; remove one`);
2968
- const { resolveMusic, renderMusic, musicLength, musicKey } = await import('./music-AF4VTFAF.js');
2968
+ const { resolveMusic, renderMusic, musicLength, musicKey } = await import('./music-3O5MDQXY.js');
2969
2969
  const music = resolveMusic(spec.music, who);
2970
2970
  if (!music) return;
2971
2971
  const natural = musicLength(music);
@@ -3042,19 +3042,41 @@ var AudioSequence = class extends Sequence {
3042
3042
  };
3043
3043
 
3044
3044
  // src/core/FrameCache.ts
3045
+ var BUDGET_BYTES = 96 * 1024 * 1024;
3046
+ var MIN_HELD = 3;
3047
+ var JUMP_SECONDS = 1;
3048
+ var EPS = 1e-6;
3049
+ function reportDecodeError(err, time) {
3050
+ const isTransient = err instanceof DOMException && (err.name === "EncodingError" || err.name === "NotSupportedError");
3051
+ if (isTransient) console.debug("pixi-effects: transient video decode failure at", time, "s \u2014", err.name);
3052
+ else console.warn("pixi-effects: video frame decode failed at", time, "s \u2014", err);
3053
+ }
3045
3054
  var FrameCache = class {
3046
3055
  sink;
3047
3056
  capacity;
3048
3057
  cache = /* @__PURE__ */ new Map();
3049
3058
  _pending = /* @__PURE__ */ new Map();
3059
+ // Sequential mode (the sink has `samples()`): one decoder pass that runs forward as the movie asks for frame after frame.
3060
+ _frames = [];
3061
+ _cur = null;
3062
+ _iter = null;
3063
+ _next = null;
3064
+ _held = 0;
3065
+ _lock = Promise.resolve();
3066
+ _lastTime = null;
3050
3067
  constructor(sink, options = {}) {
3051
3068
  this.sink = sink;
3052
3069
  this.capacity = options.capacity ?? 30;
3053
3070
  }
3071
+ /** How many decoded frames are held right now (the sequential reader's own list, plus the exact-time cache of the old path). */
3072
+ get heldFrames() {
3073
+ return this._frames.length + this.cache.size;
3074
+ }
3054
3075
  _key(time) {
3055
3076
  return Math.round(time * 1e6);
3056
3077
  }
3057
3078
  async getFrameAt(time) {
3079
+ if (this.sink.samples) return this._enqueue(() => this._sequentialAt(time));
3058
3080
  const key = this._key(time);
3059
3081
  if (this.cache.has(key)) {
3060
3082
  const v = this.cache.get(key);
@@ -3092,15 +3114,138 @@ var FrameCache = class {
3092
3114
  sample.close?.();
3093
3115
  return frame;
3094
3116
  } catch (err) {
3095
- const isTransient = err instanceof DOMException && (err.name === "EncodingError" || err.name === "NotSupportedError");
3096
- if (isTransient) {
3097
- console.debug("pixi-effects: transient video decode failure at", time, "s \u2014", err.name);
3098
- } else {
3099
- console.warn("pixi-effects: video frame decode failed at", time, "s \u2014", err);
3117
+ reportDecodeError(err, time);
3118
+ return null;
3119
+ }
3120
+ }
3121
+ // ── sequential mode ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────
3122
+ /** Requests are answered one at a time, in the order they arrive: one pass cannot serve two places at once. */
3123
+ _enqueue(fn) {
3124
+ const run = this._lock.then(fn, fn);
3125
+ this._lock = run.then(() => void 0, () => void 0);
3126
+ return run;
3127
+ }
3128
+ _covering(time) {
3129
+ for (const e of this._frames) {
3130
+ if (e.end === null ? e.req !== void 0 && Math.abs(e.req - time) < EPS : e.ts <= time + EPS && time < e.end - EPS) {
3131
+ this._frames.splice(this._frames.indexOf(e), 1);
3132
+ this._frames.push(e);
3133
+ return e;
3100
3134
  }
3135
+ }
3136
+ return null;
3137
+ }
3138
+ async _sequentialAt(time) {
3139
+ try {
3140
+ const last = this._lastTime;
3141
+ this._lastTime = time;
3142
+ const hit = this._covering(time);
3143
+ if (hit) return hit.frame;
3144
+ if (last === null || Math.abs(time - last) > JUMP_SECONDS) return await this._single(time);
3145
+ const cur = this._cur;
3146
+ if (this._iter && cur && time >= cur.ts && time - cur.ts <= JUMP_SECONDS) return await this._advance(time);
3147
+ const backwards = !!cur && time < cur.ts;
3148
+ return await this._restart(time, backwards ? Math.max(0, time - this._held / 30) : time);
3149
+ } catch (err) {
3150
+ reportDecodeError(err, time);
3151
+ await this._closeIter();
3101
3152
  return null;
3102
3153
  }
3103
3154
  }
3155
+ /** One frame by itself (a seek): getSample, kept so that asking again for the same time costs nothing. The running pass, if any, is closed: it no longer follows the playhead. */
3156
+ async _single(time) {
3157
+ await this._closeIter();
3158
+ this._cur = null;
3159
+ const sample = await this.sink.getSample(time);
3160
+ if (!sample) return null;
3161
+ const frame = sample.toVideoFrame();
3162
+ sample.close?.();
3163
+ this._keep({ ts: sample.timestamp, end: null, frame, req: time }, frame);
3164
+ return frame;
3165
+ }
3166
+ /** Start a pass at `time` (a seek and the frames of one group of pictures), keeping what is still held for a step back. */
3167
+ async _restart(time, from = time) {
3168
+ await this._closeIter();
3169
+ this._cur = null;
3170
+ const iter = this.sink.samples(from);
3171
+ this._iter = iter;
3172
+ const first = await iter.next();
3173
+ if (first.done) {
3174
+ await this._closeIter();
3175
+ return null;
3176
+ }
3177
+ if (first.value.timestamp > time + EPS) {
3178
+ first.value.close?.();
3179
+ await this._closeIter();
3180
+ return null;
3181
+ }
3182
+ this._take(first.value);
3183
+ return this._advance(time);
3184
+ }
3185
+ /** Run the pass forward until the frame that covers `time` is the current one; the next frame is read ahead (its start is where this one ends). */
3186
+ async _advance(time) {
3187
+ const cur = () => this._cur;
3188
+ for (; ; ) {
3189
+ if (!this._next) {
3190
+ let r;
3191
+ try {
3192
+ r = await this._iter.next();
3193
+ } catch (err) {
3194
+ reportDecodeError(err, time);
3195
+ await this._closeIter();
3196
+ const prev = this._frames.length > 1 ? this._frames[this._frames.length - 2] : null;
3197
+ const spacing = prev ? cur().ts - prev.ts : 1 / 24;
3198
+ return time < cur().ts + spacing - EPS ? cur().frame : null;
3199
+ }
3200
+ if (r.done) {
3201
+ cur().end = Infinity;
3202
+ this._iter = null;
3203
+ break;
3204
+ }
3205
+ this._next = r.value;
3206
+ }
3207
+ cur().end = this._next.timestamp;
3208
+ if (this._next.timestamp <= time + EPS) {
3209
+ const s = this._next;
3210
+ this._next = null;
3211
+ this._take(s);
3212
+ } else break;
3213
+ }
3214
+ return cur().frame;
3215
+ }
3216
+ _take(sample) {
3217
+ const frame = sample.toVideoFrame();
3218
+ sample.close?.();
3219
+ const entry = { ts: sample.timestamp, end: null, frame };
3220
+ this._keep(entry, frame);
3221
+ this._cur = entry;
3222
+ }
3223
+ /** Hold a frame (the first one sets how many fit: a byte budget, not a count) and close the oldest beyond that; the frame the pass stands on is never closed. */
3224
+ _keep(entry, frame) {
3225
+ if (this._held === 0) {
3226
+ const bytes = frame.allocationSize?.() ?? frame.displayWidth * frame.displayHeight * 1.5;
3227
+ this._held = Math.max(MIN_HELD, Math.min(this.capacity, Math.floor(BUDGET_BYTES / Math.max(1, bytes))));
3228
+ }
3229
+ this._frames.push(entry);
3230
+ while (this._frames.length > this._held) {
3231
+ const oldest = this._frames.find((e) => e !== this._cur && e !== entry);
3232
+ if (!oldest) break;
3233
+ this._frames.splice(this._frames.indexOf(oldest), 1);
3234
+ oldest.frame.close?.();
3235
+ }
3236
+ }
3237
+ async _closeIter() {
3238
+ const iter = this._iter;
3239
+ this._iter = null;
3240
+ this._next?.close?.();
3241
+ this._next = null;
3242
+ if (iter) {
3243
+ try {
3244
+ await iter.return?.(void 0);
3245
+ } catch {
3246
+ }
3247
+ }
3248
+ }
3104
3249
  _evictIfNeeded() {
3105
3250
  while (this.cache.size > this.capacity) {
3106
3251
  const oldestKey = this.cache.keys().next().value;
@@ -3114,6 +3259,14 @@ var FrameCache = class {
3114
3259
  for (const f of this.cache.values()) f?.close?.();
3115
3260
  this.cache.clear();
3116
3261
  this._pending.clear();
3262
+ for (const e of this._frames) e.frame.close?.();
3263
+ this._frames = [];
3264
+ this._cur = null;
3265
+ this._next?.close?.();
3266
+ this._next = null;
3267
+ const iter = this._iter;
3268
+ this._iter = null;
3269
+ void iter?.return?.(void 0);
3117
3270
  }
3118
3271
  };
3119
3272
 
@@ -4344,7 +4497,7 @@ function resolveDim(v, scope) {
4344
4497
  var _CompositionSequence = null;
4345
4498
  async function getCompositionSequence() {
4346
4499
  if (!_CompositionSequence) {
4347
- _CompositionSequence = (await import('./Composition-Z2VY3KWZ.js')).CompositionSequence;
4500
+ _CompositionSequence = (await import('./Composition-H4QA24MV.js')).CompositionSequence;
4348
4501
  }
4349
4502
  return _CompositionSequence;
4350
4503
  }
@@ -4387,5 +4540,5 @@ async function buildSequenceTree(specs, parent, root) {
4387
4540
  }
4388
4541
 
4389
4542
  export { CameraSequence, DEFAULT_FOV, DEG2 as DEG, FALLOFFS, LIGHT_KINDS, LIGHT_PROPS, LightSequence, MANY_BLURRED, MAX_BLUR, MAX_CASTERS, MAX_LIGHTS, MIN_BLUR, NEAR, NullSequence, SHADOW_TAPS, Sequence, bindClock, blurRadius, buildScope, buildSequenceTree, cameraBasis, checkEase, clockTable, contentLength, ensureFilterLibrary, evaluateExpr, flattenSvgPath, focusProblems, homeCamera, homeDistance, layerInitialZ, layerToWorld, lightLevel, noise, packLights, projectLayer, projectPoint, rand, random, registerSequenceType, remapOf, resolveAt, timeRemapOf };
4390
- //# sourceMappingURL=chunk-JHJNEIDI.js.map
4391
- //# sourceMappingURL=chunk-JHJNEIDI.js.map
4543
+ //# sourceMappingURL=chunk-X3HB4DSR.js.map
4544
+ //# sourceMappingURL=chunk-X3HB4DSR.js.map