@glissade/scene 0.6.1 → 0.7.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/dist/index.d.ts +87 -50
- package/dist/index.js +229 -86
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -139,6 +139,92 @@ interface DrawOnEachOptions extends DrawOnOptions {
|
|
|
139
139
|
* whiteboard sequence. Returns one reveal track per id, staggered by `delay`. */
|
|
140
140
|
declare function drawOnEach(targets: readonly string[], opts?: DrawOnEachOptions): Track<number>[];
|
|
141
141
|
//#endregion
|
|
142
|
+
//#region src/guards.d.ts
|
|
143
|
+
/**
|
|
144
|
+
* Render-mode determinism guards (DESIGN.md §5.5). During export the banned
|
|
145
|
+
* globals are patched — for the synchronous scope of a single evaluate() call —
|
|
146
|
+
* to throw (CLI/CI) or warn-once-then-delegate (browser/dev), then restored.
|
|
147
|
+
* Scoped to evaluate() re-entry, never installed globally, so timers and clocks
|
|
148
|
+
* outside the read phase are untouched. This backstops the static eslint rules
|
|
149
|
+
* (`@glissade/eslint-plugin`) for the cases lint can't see (closures, indirect
|
|
150
|
+
* calls, third-party code reachable from a node's emit()).
|
|
151
|
+
*/
|
|
152
|
+
declare class DeterminismViolationError extends Error {
|
|
153
|
+
constructor(api: string);
|
|
154
|
+
}
|
|
155
|
+
type GuardMode = 'throw' | 'warn' | 'off';
|
|
156
|
+
/**
|
|
157
|
+
* Run `fn` (a single synchronous evaluate()) with the banned globals guarded.
|
|
158
|
+
* `throw` rejects any call (CLI/CI); `warn` warns once per API then delegates
|
|
159
|
+
* to the real implementation (browser/dev); `off` is a no-op. Globals are
|
|
160
|
+
* always restored, even if `fn` throws. `fn` MUST be synchronous — patching is
|
|
161
|
+
* only valid for the sync read phase, never across an await.
|
|
162
|
+
*/
|
|
163
|
+
declare function withDeterminismGuards<T>(mode: GuardMode, fn: () => T): T;
|
|
164
|
+
//#endregion
|
|
165
|
+
//#region src/scene.d.ts
|
|
166
|
+
interface Scene {
|
|
167
|
+
readonly root: Group;
|
|
168
|
+
readonly nodes: ReadonlyMap<string, Node>;
|
|
169
|
+
readonly size: {
|
|
170
|
+
w: number;
|
|
171
|
+
h: number;
|
|
172
|
+
};
|
|
173
|
+
/** Per-scene playhead; players and evaluate() write it. */
|
|
174
|
+
readonly playhead: Playhead;
|
|
175
|
+
resolveTarget(target: string): BindablePropTarget | undefined;
|
|
176
|
+
/**
|
|
177
|
+
* Inject the active backend's TextMeasurer (§3.2) so line breaking always
|
|
178
|
+
* measures with the rasterizer that will draw. Defaults to an estimator.
|
|
179
|
+
*/
|
|
180
|
+
setTextMeasurer(measurer: TextMeasurer): void;
|
|
181
|
+
readonly textMeasurer: TextMeasurer;
|
|
182
|
+
}
|
|
183
|
+
declare class DuplicateNodeIdError extends Error {
|
|
184
|
+
constructor(id: string);
|
|
185
|
+
}
|
|
186
|
+
interface SceneInit {
|
|
187
|
+
size: {
|
|
188
|
+
w: number;
|
|
189
|
+
h: number;
|
|
190
|
+
};
|
|
191
|
+
children: Node[];
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* The scene-module convention: what `gs render`, the golden harness, and the
|
|
195
|
+
* studio load. createScene is a factory — every consumer gets a fresh graph.
|
|
196
|
+
*/
|
|
197
|
+
interface SceneModule {
|
|
198
|
+
createScene(): Scene;
|
|
199
|
+
timeline: Timeline;
|
|
200
|
+
}
|
|
201
|
+
declare function createScene(init: SceneInit): Scene;
|
|
202
|
+
interface BindingCacheEntry {
|
|
203
|
+
compiled: CompiledTimeline;
|
|
204
|
+
bound: BoundTimeline;
|
|
205
|
+
}
|
|
206
|
+
declare function bindScene(scene: Scene, doc: Timeline): BindingCacheEntry;
|
|
207
|
+
/**
|
|
208
|
+
* The non-negotiable contract (§2.5): same (scene, timeline, t) → identical
|
|
209
|
+
* DisplayList, in any call order. Never awaits; asset readiness is the
|
|
210
|
+
* caller's precondition.
|
|
211
|
+
*/
|
|
212
|
+
declare function evaluate(scene: Scene, doc: Timeline, t: number): DisplayList;
|
|
213
|
+
//#endregion
|
|
214
|
+
//#region src/cacheColdAudit.d.ts
|
|
215
|
+
interface CacheColdResult {
|
|
216
|
+
ok: boolean;
|
|
217
|
+
/** id of the first node whose isolated emit() diverged (set only when !ok). */
|
|
218
|
+
node?: string;
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Evaluate two fresh scenes from `createScene` at `t` and confirm the
|
|
222
|
+
* DisplayLists are byte-identical. Returns `{ ok: true }` for a pure scene, or
|
|
223
|
+
* `{ ok: false, node }` naming the first divergent node. DEV-only — never on
|
|
224
|
+
* the render hot path.
|
|
225
|
+
*/
|
|
226
|
+
declare function auditCacheCold(createScene: () => Scene, doc: Timeline, t: number): CacheColdResult;
|
|
227
|
+
//#endregion
|
|
142
228
|
//#region src/motionPath.d.ts
|
|
143
229
|
/** An arc-length-parameterized sampler over a path. */
|
|
144
230
|
interface PathSampler {
|
|
@@ -388,53 +474,4 @@ declare class Raster2D<TCanvas extends CanvasLike, TPath extends PathLike, TDraw
|
|
|
388
474
|
render(target: TCanvas, list: DisplayList): void;
|
|
389
475
|
}
|
|
390
476
|
//#endregion
|
|
391
|
-
|
|
392
|
-
interface Scene {
|
|
393
|
-
readonly root: Group;
|
|
394
|
-
readonly nodes: ReadonlyMap<string, Node>;
|
|
395
|
-
readonly size: {
|
|
396
|
-
w: number;
|
|
397
|
-
h: number;
|
|
398
|
-
};
|
|
399
|
-
/** Per-scene playhead; players and evaluate() write it. */
|
|
400
|
-
readonly playhead: Playhead;
|
|
401
|
-
resolveTarget(target: string): BindablePropTarget | undefined;
|
|
402
|
-
/**
|
|
403
|
-
* Inject the active backend's TextMeasurer (§3.2) so line breaking always
|
|
404
|
-
* measures with the rasterizer that will draw. Defaults to an estimator.
|
|
405
|
-
*/
|
|
406
|
-
setTextMeasurer(measurer: TextMeasurer): void;
|
|
407
|
-
readonly textMeasurer: TextMeasurer;
|
|
408
|
-
}
|
|
409
|
-
declare class DuplicateNodeIdError extends Error {
|
|
410
|
-
constructor(id: string);
|
|
411
|
-
}
|
|
412
|
-
interface SceneInit {
|
|
413
|
-
size: {
|
|
414
|
-
w: number;
|
|
415
|
-
h: number;
|
|
416
|
-
};
|
|
417
|
-
children: Node[];
|
|
418
|
-
}
|
|
419
|
-
/**
|
|
420
|
-
* The scene-module convention: what `gs render`, the golden harness, and the
|
|
421
|
-
* studio load. createScene is a factory — every consumer gets a fresh graph.
|
|
422
|
-
*/
|
|
423
|
-
interface SceneModule {
|
|
424
|
-
createScene(): Scene;
|
|
425
|
-
timeline: Timeline;
|
|
426
|
-
}
|
|
427
|
-
declare function createScene(init: SceneInit): Scene;
|
|
428
|
-
interface BindingCacheEntry {
|
|
429
|
-
compiled: CompiledTimeline;
|
|
430
|
-
bound: BoundTimeline;
|
|
431
|
-
}
|
|
432
|
-
declare function bindScene(scene: Scene, doc: Timeline): BindingCacheEntry;
|
|
433
|
-
/**
|
|
434
|
-
* The non-negotiable contract (§2.5): same (scene, timeline, t) → identical
|
|
435
|
-
* DisplayList, in any call order. Never awaits; asset readiness is the
|
|
436
|
-
* caller's precondition.
|
|
437
|
-
*/
|
|
438
|
-
declare function evaluate(scene: Scene, doc: Timeline, t: number): DisplayList;
|
|
439
|
-
//#endregion
|
|
440
|
-
export { type AnchorSpec, type BindablePropTarget, type BlendMode, type CanvasLike, Circle, ColdAssetError, type Ctx2DLike, type DisplayList, type DisplayListBuilder, type DrawCommand, type DrawOnEachOptions, type DrawOnOptions, DuplicateNodeIdError, type EditMark, type EvalContext, type FilterSpec, FilterValidationError, FollowPath, type FollowPathProps, type FontSpec, Group, type HachureSpec, Highlight, type HighlightProps, type HitArea, IDENTITY, type ImageHandle, ImageNode, type ImageProps, type LayoutBox, type LayoutChildSpec, type LayoutContainerSpec, type LayoutEngine, LayoutEngineMissingError, type LineBox, type Mat2x3, Node, type NodeProps, type Paint, Path, type PathLike, type PathProps, type PathSampler, type PathSeg, type Polyline, type PropInit, Raster2D, type Raster2DHost, Rect, type Rect$1 as RectShape, type ResolvedSketch, type Resource, type ResourceId, type RevealMark, type Scene, type SceneInit, type SceneModule, type ShaderCaps, ShaderEffect, type ShaderEffectProps, type ShaderRef, type ShapeProps, type SketchStyle, SketchValidationError, type StepMark, type StrokeStyle, Text, TextCursor, type TextCursorProps, type TextMeasurer, type TextMetricsLite, type TextProps, TokenHighlight, type TokenHighlightProps, TokenMatchError, type TokenRange, type TypeEdit, type TypewriterResult, Video, type VideoFrameSource, type VideoProps, type WordBox, applyToPoint, arcLength, bindScene, breakLines, createDisplayListBuilder, createScene, drawOn, drawOnEach, estimatingMeasurer, evaluate, filtersToCanvasFilter, flatten, followPath, fontString, fromTRS, getLayoutEngine, glow, hachureLines, highlight, invert, matEquals, matchTokenRun, motionPath, multiply, pathFromSegs, pathLength, pointAtLength, quantize, requireLayoutEngine, resolveAnchor, resolveSketch, revealSchedule, roughen, roundedRectSegs, segmentGraphemes, segmentWords, setDefaultMeasurer, setLayoutEngine, sketchStrokes, textCursor, tokenHighlight, typewriter, validateFilters, validateHachure, validateSketch };
|
|
477
|
+
export { type AnchorSpec, type BindablePropTarget, type BlendMode, type CacheColdResult, type CanvasLike, Circle, ColdAssetError, type Ctx2DLike, DeterminismViolationError, type DisplayList, type DisplayListBuilder, type DrawCommand, type DrawOnEachOptions, type DrawOnOptions, DuplicateNodeIdError, type EditMark, type EvalContext, type FilterSpec, FilterValidationError, FollowPath, type FollowPathProps, type FontSpec, Group, type GuardMode, type HachureSpec, Highlight, type HighlightProps, type HitArea, IDENTITY, type ImageHandle, ImageNode, type ImageProps, type LayoutBox, type LayoutChildSpec, type LayoutContainerSpec, type LayoutEngine, LayoutEngineMissingError, type LineBox, type Mat2x3, Node, type NodeProps, type Paint, Path, type PathLike, type PathProps, type PathSampler, type PathSeg, type Polyline, type PropInit, Raster2D, type Raster2DHost, Rect, type Rect$1 as RectShape, type ResolvedSketch, type Resource, type ResourceId, type RevealMark, type Scene, type SceneInit, type SceneModule, type ShaderCaps, ShaderEffect, type ShaderEffectProps, type ShaderRef, type ShapeProps, type SketchStyle, SketchValidationError, type StepMark, type StrokeStyle, Text, TextCursor, type TextCursorProps, type TextMeasurer, type TextMetricsLite, type TextProps, TokenHighlight, type TokenHighlightProps, TokenMatchError, type TokenRange, type TypeEdit, type TypewriterResult, Video, type VideoFrameSource, type VideoProps, type WordBox, applyToPoint, arcLength, auditCacheCold, bindScene, breakLines, createDisplayListBuilder, createScene, drawOn, drawOnEach, estimatingMeasurer, evaluate, filtersToCanvasFilter, flatten, followPath, fontString, fromTRS, getLayoutEngine, glow, hachureLines, highlight, invert, matEquals, matchTokenRun, motionPath, multiply, pathFromSegs, pathLength, pointAtLength, quantize, requireLayoutEngine, resolveAnchor, resolveSketch, revealSchedule, roughen, roundedRectSegs, segmentGraphemes, segmentWords, setDefaultMeasurer, setLayoutEngine, sketchStrokes, textCursor, tokenHighlight, typewriter, validateFilters, validateHachure, validateSketch, withDeterminismGuards };
|
package/dist/index.js
CHANGED
|
@@ -254,6 +254,234 @@ function drawOnEach(targets, opts = {}) {
|
|
|
254
254
|
return stagger(targets.map((t) => drawOn(t, opts)), delay);
|
|
255
255
|
}
|
|
256
256
|
//#endregion
|
|
257
|
+
//#region src/guards.ts
|
|
258
|
+
/**
|
|
259
|
+
* Render-mode determinism guards (DESIGN.md §5.5). During export the banned
|
|
260
|
+
* globals are patched — for the synchronous scope of a single evaluate() call —
|
|
261
|
+
* to throw (CLI/CI) or warn-once-then-delegate (browser/dev), then restored.
|
|
262
|
+
* Scoped to evaluate() re-entry, never installed globally, so timers and clocks
|
|
263
|
+
* outside the read phase are untouched. This backstops the static eslint rules
|
|
264
|
+
* (`@glissade/eslint-plugin`) for the cases lint can't see (closures, indirect
|
|
265
|
+
* calls, third-party code reachable from a node's emit()).
|
|
266
|
+
*/
|
|
267
|
+
var DeterminismViolationError = class extends Error {
|
|
268
|
+
constructor(api) {
|
|
269
|
+
super(`'${api}' was called inside evaluate() — scene code must be a pure function of time (§5.5). Read ctx.time/frame, use the seeded random(seed) from @glissade/core, and resolve assets before rendering.`);
|
|
270
|
+
this.name = "DeterminismViolationError";
|
|
271
|
+
}
|
|
272
|
+
};
|
|
273
|
+
function bannedSlots() {
|
|
274
|
+
const g = globalThis;
|
|
275
|
+
const slots = [
|
|
276
|
+
{
|
|
277
|
+
target: Math,
|
|
278
|
+
key: "random",
|
|
279
|
+
label: "Math.random"
|
|
280
|
+
},
|
|
281
|
+
{
|
|
282
|
+
target: Date,
|
|
283
|
+
key: "now",
|
|
284
|
+
label: "Date.now"
|
|
285
|
+
},
|
|
286
|
+
{
|
|
287
|
+
target: g,
|
|
288
|
+
key: "setTimeout",
|
|
289
|
+
label: "setTimeout"
|
|
290
|
+
},
|
|
291
|
+
{
|
|
292
|
+
target: g,
|
|
293
|
+
key: "setInterval",
|
|
294
|
+
label: "setInterval"
|
|
295
|
+
}
|
|
296
|
+
];
|
|
297
|
+
if (typeof performance !== "undefined") slots.push({
|
|
298
|
+
target: performance,
|
|
299
|
+
key: "now",
|
|
300
|
+
label: "performance.now"
|
|
301
|
+
});
|
|
302
|
+
if (typeof g["requestAnimationFrame"] === "function") slots.push({
|
|
303
|
+
target: g,
|
|
304
|
+
key: "requestAnimationFrame",
|
|
305
|
+
label: "requestAnimationFrame"
|
|
306
|
+
});
|
|
307
|
+
return slots;
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* Run `fn` (a single synchronous evaluate()) with the banned globals guarded.
|
|
311
|
+
* `throw` rejects any call (CLI/CI); `warn` warns once per API then delegates
|
|
312
|
+
* to the real implementation (browser/dev); `off` is a no-op. Globals are
|
|
313
|
+
* always restored, even if `fn` throws. `fn` MUST be synchronous — patching is
|
|
314
|
+
* only valid for the sync read phase, never across an await.
|
|
315
|
+
*/
|
|
316
|
+
function withDeterminismGuards(mode, fn) {
|
|
317
|
+
if (mode === "off") return fn();
|
|
318
|
+
const slots = bannedSlots();
|
|
319
|
+
const saved = slots.map((s) => s.target[s.key]);
|
|
320
|
+
const warned = /* @__PURE__ */ new Set();
|
|
321
|
+
slots.forEach((s, i) => {
|
|
322
|
+
s.target[s.key] = (...args) => {
|
|
323
|
+
if (mode === "throw") throw new DeterminismViolationError(s.label);
|
|
324
|
+
if (!warned.has(s.label)) {
|
|
325
|
+
warned.add(s.label);
|
|
326
|
+
emitDevWarning(`${s.label} called inside evaluate() — nondeterministic, not reproducible on export (§5.5)`);
|
|
327
|
+
}
|
|
328
|
+
return saved[i].apply(s.target, args);
|
|
329
|
+
};
|
|
330
|
+
});
|
|
331
|
+
try {
|
|
332
|
+
return fn();
|
|
333
|
+
} finally {
|
|
334
|
+
slots.forEach((s, i) => {
|
|
335
|
+
s.target[s.key] = saved[i];
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
//#endregion
|
|
340
|
+
//#region src/scene.ts
|
|
341
|
+
/**
|
|
342
|
+
* Scene assembly + the canonical evaluate(scene, timeline, t) (DESIGN.md §2.5,
|
|
343
|
+
* §3.1): pure, total, deterministic — driver writes the playhead (sanctioned
|
|
344
|
+
* entry write), then a pull-only read phase collects the DisplayList.
|
|
345
|
+
*/
|
|
346
|
+
var DuplicateNodeIdError = class extends Error {
|
|
347
|
+
constructor(id) {
|
|
348
|
+
super(`duplicate node id '${id}' — explicit ids must be unique (§3.1/§6.5)`);
|
|
349
|
+
this.name = "DuplicateNodeIdError";
|
|
350
|
+
}
|
|
351
|
+
};
|
|
352
|
+
function indexNodes(root, into, measurerSource) {
|
|
353
|
+
root.measurerSource = measurerSource;
|
|
354
|
+
if (root.id !== void 0) {
|
|
355
|
+
if (into.has(root.id)) throw new DuplicateNodeIdError(root.id);
|
|
356
|
+
into.set(root.id, root);
|
|
357
|
+
}
|
|
358
|
+
if (root instanceof Group) for (const child of root.children) indexNodes(child, into, measurerSource);
|
|
359
|
+
}
|
|
360
|
+
function createScene(init) {
|
|
361
|
+
const root = new Group({
|
|
362
|
+
id: "__root",
|
|
363
|
+
children: init.children
|
|
364
|
+
});
|
|
365
|
+
const nodes = /* @__PURE__ */ new Map();
|
|
366
|
+
const playhead = createPlayhead();
|
|
367
|
+
let measurer = null;
|
|
368
|
+
indexNodes(root, nodes, () => measurer ?? fallbackMeasurer());
|
|
369
|
+
return {
|
|
370
|
+
root,
|
|
371
|
+
nodes,
|
|
372
|
+
size: init.size,
|
|
373
|
+
playhead,
|
|
374
|
+
resolveTarget: (target) => {
|
|
375
|
+
const slash = target.indexOf("/");
|
|
376
|
+
if (slash < 0) return void 0;
|
|
377
|
+
return nodes.get(target.slice(0, slash))?.resolveTarget(target.slice(slash + 1));
|
|
378
|
+
},
|
|
379
|
+
setTextMeasurer: (m) => {
|
|
380
|
+
measurer = m;
|
|
381
|
+
},
|
|
382
|
+
get textMeasurer() {
|
|
383
|
+
return measurer ?? fallbackMeasurer();
|
|
384
|
+
}
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
const bindings = /* @__PURE__ */ new WeakMap();
|
|
388
|
+
function bindScene(scene, doc) {
|
|
389
|
+
let perScene = bindings.get(scene);
|
|
390
|
+
if (!perScene) {
|
|
391
|
+
perScene = /* @__PURE__ */ new WeakMap();
|
|
392
|
+
bindings.set(scene, perScene);
|
|
393
|
+
}
|
|
394
|
+
let entry = perScene.get(doc);
|
|
395
|
+
if (!entry) {
|
|
396
|
+
const compiled = compileTimeline(doc);
|
|
397
|
+
entry = {
|
|
398
|
+
compiled,
|
|
399
|
+
bound: bindTimeline(compiled, scene.resolveTarget, scene.playhead)
|
|
400
|
+
};
|
|
401
|
+
perScene.set(doc, entry);
|
|
402
|
+
}
|
|
403
|
+
return entry;
|
|
404
|
+
}
|
|
405
|
+
/**
|
|
406
|
+
* The non-negotiable contract (§2.5): same (scene, timeline, t) → identical
|
|
407
|
+
* DisplayList, in any call order. Never awaits; asset readiness is the
|
|
408
|
+
* caller's precondition.
|
|
409
|
+
*/
|
|
410
|
+
function evaluate(scene, doc, t) {
|
|
411
|
+
bindScene(scene, doc);
|
|
412
|
+
const fps = doc.fps;
|
|
413
|
+
const ctx = {
|
|
414
|
+
time: t,
|
|
415
|
+
frame: fps !== void 0 ? Math.round(t * fps) : -1,
|
|
416
|
+
measurer: scene.textMeasurer
|
|
417
|
+
};
|
|
418
|
+
return evaluateAt(scene.playhead, t, () => {
|
|
419
|
+
const out = createDisplayListBuilder(scene.size);
|
|
420
|
+
scene.root.emit(out, ctx);
|
|
421
|
+
return out.finish();
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
//#endregion
|
|
425
|
+
//#region src/cacheColdAudit.ts
|
|
426
|
+
/** Stable string of a DisplayList for comparison (opaque resources collapse to a marker). */
|
|
427
|
+
function hashDisplayList(dl) {
|
|
428
|
+
return JSON.stringify(dl, (_key, value) => {
|
|
429
|
+
if (value instanceof ArrayBuffer) return `ab:${value.byteLength}`;
|
|
430
|
+
if (ArrayBuffer.isView(value)) return `view:${value.byteLength}`;
|
|
431
|
+
if (typeof value === "function") return void 0;
|
|
432
|
+
return value;
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
/**
|
|
436
|
+
* Evaluate two fresh scenes from `createScene` at `t` and confirm the
|
|
437
|
+
* DisplayLists are byte-identical. Returns `{ ok: true }` for a pure scene, or
|
|
438
|
+
* `{ ok: false, node }` naming the first divergent node. DEV-only — never on
|
|
439
|
+
* the render hot path.
|
|
440
|
+
*/
|
|
441
|
+
function auditCacheCold(createScene, doc, t) {
|
|
442
|
+
const warm = createScene();
|
|
443
|
+
const cold = createScene();
|
|
444
|
+
const a = evaluate(warm, doc, t);
|
|
445
|
+
const b = evaluate(cold, doc, t);
|
|
446
|
+
if (hashDisplayList(a) === hashDisplayList(b)) return { ok: true };
|
|
447
|
+
const frame = doc.fps !== void 0 ? Math.round(t * doc.fps) : -1;
|
|
448
|
+
const ctxA = {
|
|
449
|
+
time: t,
|
|
450
|
+
frame,
|
|
451
|
+
measurer: warm.textMeasurer
|
|
452
|
+
};
|
|
453
|
+
const ctxB = {
|
|
454
|
+
time: t,
|
|
455
|
+
frame,
|
|
456
|
+
measurer: cold.textMeasurer
|
|
457
|
+
};
|
|
458
|
+
let groupFallback;
|
|
459
|
+
for (const [id, nodeA] of warm.nodes) {
|
|
460
|
+
const nodeB = cold.nodes.get(id);
|
|
461
|
+
if (!nodeB) return {
|
|
462
|
+
ok: false,
|
|
463
|
+
node: id
|
|
464
|
+
};
|
|
465
|
+
const ea = createDisplayListBuilder(warm.size);
|
|
466
|
+
nodeA.emit(ea, ctxA);
|
|
467
|
+
const eb = createDisplayListBuilder(cold.size);
|
|
468
|
+
nodeB.emit(eb, ctxB);
|
|
469
|
+
if (hashDisplayList(ea.finish()) === hashDisplayList(eb.finish())) continue;
|
|
470
|
+
if (nodeA instanceof Group) {
|
|
471
|
+
groupFallback ??= id;
|
|
472
|
+
continue;
|
|
473
|
+
}
|
|
474
|
+
return {
|
|
475
|
+
ok: false,
|
|
476
|
+
node: id
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
return {
|
|
480
|
+
ok: false,
|
|
481
|
+
...groupFallback !== void 0 ? { node: groupFallback } : {}
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
//#endregion
|
|
257
485
|
//#region src/motionPath.ts
|
|
258
486
|
/**
|
|
259
487
|
* Motion along a path: sample a point (and tangent) at an arc-length position on
|
|
@@ -1016,89 +1244,4 @@ var Raster2D = class {
|
|
|
1016
1244
|
}
|
|
1017
1245
|
};
|
|
1018
1246
|
//#endregion
|
|
1019
|
-
|
|
1020
|
-
/**
|
|
1021
|
-
* Scene assembly + the canonical evaluate(scene, timeline, t) (DESIGN.md §2.5,
|
|
1022
|
-
* §3.1): pure, total, deterministic — driver writes the playhead (sanctioned
|
|
1023
|
-
* entry write), then a pull-only read phase collects the DisplayList.
|
|
1024
|
-
*/
|
|
1025
|
-
var DuplicateNodeIdError = class extends Error {
|
|
1026
|
-
constructor(id) {
|
|
1027
|
-
super(`duplicate node id '${id}' — explicit ids must be unique (§3.1/§6.5)`);
|
|
1028
|
-
this.name = "DuplicateNodeIdError";
|
|
1029
|
-
}
|
|
1030
|
-
};
|
|
1031
|
-
function indexNodes(root, into, measurerSource) {
|
|
1032
|
-
root.measurerSource = measurerSource;
|
|
1033
|
-
if (root.id !== void 0) {
|
|
1034
|
-
if (into.has(root.id)) throw new DuplicateNodeIdError(root.id);
|
|
1035
|
-
into.set(root.id, root);
|
|
1036
|
-
}
|
|
1037
|
-
if (root instanceof Group) for (const child of root.children) indexNodes(child, into, measurerSource);
|
|
1038
|
-
}
|
|
1039
|
-
function createScene(init) {
|
|
1040
|
-
const root = new Group({
|
|
1041
|
-
id: "__root",
|
|
1042
|
-
children: init.children
|
|
1043
|
-
});
|
|
1044
|
-
const nodes = /* @__PURE__ */ new Map();
|
|
1045
|
-
const playhead = createPlayhead();
|
|
1046
|
-
let measurer = null;
|
|
1047
|
-
indexNodes(root, nodes, () => measurer ?? fallbackMeasurer());
|
|
1048
|
-
return {
|
|
1049
|
-
root,
|
|
1050
|
-
nodes,
|
|
1051
|
-
size: init.size,
|
|
1052
|
-
playhead,
|
|
1053
|
-
resolveTarget: (target) => {
|
|
1054
|
-
const slash = target.indexOf("/");
|
|
1055
|
-
if (slash < 0) return void 0;
|
|
1056
|
-
return nodes.get(target.slice(0, slash))?.resolveTarget(target.slice(slash + 1));
|
|
1057
|
-
},
|
|
1058
|
-
setTextMeasurer: (m) => {
|
|
1059
|
-
measurer = m;
|
|
1060
|
-
},
|
|
1061
|
-
get textMeasurer() {
|
|
1062
|
-
return measurer ?? fallbackMeasurer();
|
|
1063
|
-
}
|
|
1064
|
-
};
|
|
1065
|
-
}
|
|
1066
|
-
const bindings = /* @__PURE__ */ new WeakMap();
|
|
1067
|
-
function bindScene(scene, doc) {
|
|
1068
|
-
let perScene = bindings.get(scene);
|
|
1069
|
-
if (!perScene) {
|
|
1070
|
-
perScene = /* @__PURE__ */ new WeakMap();
|
|
1071
|
-
bindings.set(scene, perScene);
|
|
1072
|
-
}
|
|
1073
|
-
let entry = perScene.get(doc);
|
|
1074
|
-
if (!entry) {
|
|
1075
|
-
const compiled = compileTimeline(doc);
|
|
1076
|
-
entry = {
|
|
1077
|
-
compiled,
|
|
1078
|
-
bound: bindTimeline(compiled, scene.resolveTarget, scene.playhead)
|
|
1079
|
-
};
|
|
1080
|
-
perScene.set(doc, entry);
|
|
1081
|
-
}
|
|
1082
|
-
return entry;
|
|
1083
|
-
}
|
|
1084
|
-
/**
|
|
1085
|
-
* The non-negotiable contract (§2.5): same (scene, timeline, t) → identical
|
|
1086
|
-
* DisplayList, in any call order. Never awaits; asset readiness is the
|
|
1087
|
-
* caller's precondition.
|
|
1088
|
-
*/
|
|
1089
|
-
function evaluate(scene, doc, t) {
|
|
1090
|
-
bindScene(scene, doc);
|
|
1091
|
-
const fps = doc.fps;
|
|
1092
|
-
const ctx = {
|
|
1093
|
-
time: t,
|
|
1094
|
-
frame: fps !== void 0 ? Math.round(t * fps) : -1,
|
|
1095
|
-
measurer: scene.textMeasurer
|
|
1096
|
-
};
|
|
1097
|
-
return evaluateAt(scene.playhead, t, () => {
|
|
1098
|
-
const out = createDisplayListBuilder(scene.size);
|
|
1099
|
-
scene.root.emit(out, ctx);
|
|
1100
|
-
return out.finish();
|
|
1101
|
-
});
|
|
1102
|
-
}
|
|
1103
|
-
//#endregion
|
|
1104
|
-
export { Circle, ColdAssetError, DuplicateNodeIdError, FilterValidationError, FollowPath, Group, Highlight, IDENTITY, ImageNode, LayoutEngineMissingError, Node, Path, Raster2D, Rect, ShaderEffect, SketchValidationError, Text, TextCursor, TokenHighlight, TokenMatchError, Video, applyToPoint, arcLength, bindScene, breakLines, createDisplayListBuilder, createScene, drawOn, drawOnEach, estimatingMeasurer, evaluate, filtersToCanvasFilter, flatten, followPath, fontString, fromTRS, getLayoutEngine, glow, hachureLines, highlight, invert, matEquals, matchTokenRun, motionPath, multiply, pathFromSegs, pathLength, pointAtLength, quantize, requireLayoutEngine, resolveAnchor, resolveSketch, revealSchedule, roughen, roundedRectSegs, segmentGraphemes, segmentWords, setDefaultMeasurer, setLayoutEngine, sketchStrokes, textCursor, tokenHighlight, typewriter, validateFilters, validateHachure, validateSketch };
|
|
1247
|
+
export { Circle, ColdAssetError, DeterminismViolationError, DuplicateNodeIdError, FilterValidationError, FollowPath, Group, Highlight, IDENTITY, ImageNode, LayoutEngineMissingError, Node, Path, Raster2D, Rect, ShaderEffect, SketchValidationError, Text, TextCursor, TokenHighlight, TokenMatchError, Video, applyToPoint, arcLength, auditCacheCold, bindScene, breakLines, createDisplayListBuilder, createScene, drawOn, drawOnEach, estimatingMeasurer, evaluate, filtersToCanvasFilter, flatten, followPath, fontString, fromTRS, getLayoutEngine, glow, hachureLines, highlight, invert, matEquals, matchTokenRun, motionPath, multiply, pathFromSegs, pathLength, pointAtLength, quantize, requireLayoutEngine, resolveAnchor, resolveSketch, revealSchedule, roughen, roundedRectSegs, segmentGraphemes, segmentWords, setDefaultMeasurer, setLayoutEngine, sketchStrokes, textCursor, tokenHighlight, typewriter, validateFilters, validateHachure, validateSketch, withDeterminismGuards };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@glissade/scene",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "glissade scene graph: nodes, transforms, DisplayList emission. Renderer-agnostic; zero DOM/Node dependencies.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
],
|
|
21
21
|
"dependencies": {
|
|
22
22
|
"yoga-layout": "^3.2.1",
|
|
23
|
-
"@glissade/core": "0.
|
|
23
|
+
"@glissade/core": "0.7.0"
|
|
24
24
|
},
|
|
25
25
|
"repository": {
|
|
26
26
|
"type": "git",
|