@glissade/scene 0.19.1 → 0.20.0-pre.4
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/collapseReplacer.d.ts +40 -0
- package/dist/constructionProps.js +82 -0
- package/dist/describe.d.ts +27 -1
- package/dist/describe.js +130 -24
- package/dist/diagnostics.d.ts +159 -0
- package/dist/diagnostics.js +384 -0
- package/dist/displayList.d.ts +27 -0
- package/dist/grid.d.ts +56 -0
- package/dist/grid.js +83 -0
- package/dist/identity.d.ts +33 -0
- package/dist/identity.js +67 -0
- package/dist/index.d.ts +14 -302
- package/dist/index.js +19 -523
- package/dist/layout.d.ts +3 -95
- package/dist/layout.js +11 -196
- package/dist/layoutCtors.d.ts +99 -0
- package/dist/layoutCtors.js +210 -0
- package/dist/motion.d.ts +60 -0
- package/dist/motion.js +171 -0
- package/dist/nodes.d.ts +32 -8
- package/dist/nodes.js +62 -187
- package/dist/scene.d.ts +71 -0
- package/dist/scene.js +120 -0
- package/package.json +23 -3
package/dist/scene.d.ts
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { n as DisplayList } from "./displayList.js";
|
|
2
|
+
import { I as BindablePropTarget, W as TextMeasurer, i as Group, z as Node } from "./nodes.js";
|
|
3
|
+
import { BoundTimeline, CompiledTimeline, Playhead, Timeline } from "@glissade/core";
|
|
4
|
+
|
|
5
|
+
//#region src/scene.d.ts
|
|
6
|
+
|
|
7
|
+
interface Scene {
|
|
8
|
+
readonly root: Group;
|
|
9
|
+
readonly nodes: ReadonlyMap<string, Node>;
|
|
10
|
+
readonly size: {
|
|
11
|
+
w: number;
|
|
12
|
+
h: number;
|
|
13
|
+
};
|
|
14
|
+
/** Per-scene playhead; players and evaluate() write it. */
|
|
15
|
+
readonly playhead: Playhead;
|
|
16
|
+
resolveTarget(target: string): BindablePropTarget | undefined;
|
|
17
|
+
/**
|
|
18
|
+
* Inject the active backend's TextMeasurer (§3.2) so line breaking always
|
|
19
|
+
* measures with the rasterizer that will draw. Defaults to an estimator.
|
|
20
|
+
*/
|
|
21
|
+
setTextMeasurer(measurer: TextMeasurer): void;
|
|
22
|
+
readonly textMeasurer: TextMeasurer;
|
|
23
|
+
}
|
|
24
|
+
declare class DuplicateNodeIdError extends Error {
|
|
25
|
+
constructor(id: string);
|
|
26
|
+
}
|
|
27
|
+
declare class ReservedNodeIdError extends Error {
|
|
28
|
+
constructor(id: string);
|
|
29
|
+
}
|
|
30
|
+
interface SceneInit {
|
|
31
|
+
size: {
|
|
32
|
+
w: number;
|
|
33
|
+
h: number;
|
|
34
|
+
};
|
|
35
|
+
children: Node[];
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* The scene-module convention: what `gs render`, the golden harness, and the
|
|
39
|
+
* studio load. createScene is a factory — every consumer gets a fresh graph.
|
|
40
|
+
*/
|
|
41
|
+
interface SceneModule {
|
|
42
|
+
createScene(): Scene;
|
|
43
|
+
timeline: Timeline;
|
|
44
|
+
}
|
|
45
|
+
declare function createScene(init: SceneInit): Scene;
|
|
46
|
+
interface BindingCacheEntry {
|
|
47
|
+
compiled: CompiledTimeline;
|
|
48
|
+
bound: BoundTimeline;
|
|
49
|
+
}
|
|
50
|
+
declare function bindScene(scene: Scene, doc: Timeline): BindingCacheEntry;
|
|
51
|
+
/**
|
|
52
|
+
* The non-negotiable contract (§2.5): same (scene, timeline, t) → identical
|
|
53
|
+
* DisplayList, in any call order. Never awaits; asset readiness is the
|
|
54
|
+
* caller's precondition.
|
|
55
|
+
*/
|
|
56
|
+
declare function evaluate(scene: Scene, doc: Timeline, t: number): DisplayList;
|
|
57
|
+
/**
|
|
58
|
+
* Controlled / imperative drive (0.19): `evaluate(scene)` with NO timeline —
|
|
59
|
+
* the host owns the clock. It evaluates against an EMPTY timeline at the
|
|
60
|
+
* scene's current playhead value (`peek()`, 0 by default), so values set
|
|
61
|
+
* imperatively via `node.set(...)` between frames survive into the DisplayList
|
|
62
|
+
* (no track clobbers them). See docs/controlled-drive.md for the loop and the
|
|
63
|
+
* `.set()`-vs-timeline precedence contract.
|
|
64
|
+
*
|
|
65
|
+
* PRECEDENCE: a timeline track ALWAYS overrides `.set()` on the prop it
|
|
66
|
+
* targets while that track is live — pass the timeline through the 3-arg form
|
|
67
|
+
* for animated props; reserve this overload for host-owned props.
|
|
68
|
+
*/
|
|
69
|
+
declare function evaluate(scene: Scene): DisplayList;
|
|
70
|
+
//#endregion
|
|
71
|
+
export { SceneModule as a, evaluate as c, SceneInit as i, ReservedNodeIdError as n, bindScene as o, Scene as r, createScene as s, DuplicateNodeIdError as t };
|
package/dist/scene.js
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { L as createDisplayListBuilder, r as Group, y as fallbackMeasurer } from "./nodes.js";
|
|
2
|
+
import { r as isConstructionProp } from "./constructionProps.js";
|
|
3
|
+
import { bindTimeline, compileTimeline, createPlayhead, evaluateAt, signal } from "@glissade/core";
|
|
4
|
+
//#region src/scene.ts
|
|
5
|
+
/**
|
|
6
|
+
* Scene assembly + the canonical evaluate(scene, timeline, t) (DESIGN.md §2.5,
|
|
7
|
+
* §3.1): pure, total, deterministic — driver writes the playhead (sanctioned
|
|
8
|
+
* entry write), then a pull-only read phase collects the DisplayList.
|
|
9
|
+
*/
|
|
10
|
+
var DuplicateNodeIdError = class extends Error {
|
|
11
|
+
constructor(id) {
|
|
12
|
+
super(`duplicate node id '${id}' — explicit ids must be unique (§3.1/§6.5)`);
|
|
13
|
+
this.name = "DuplicateNodeIdError";
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
var ReservedNodeIdError = class extends Error {
|
|
17
|
+
constructor(id) {
|
|
18
|
+
super(`node id '${id}' uses the reserved '~' prefix — that namespace is for structural fallback ids (§6.5), which are inspection-only and never track targets; choose another id`);
|
|
19
|
+
this.name = "ReservedNodeIdError";
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
function indexNodes(root, into, measurerSource) {
|
|
23
|
+
root.measurerSource = measurerSource;
|
|
24
|
+
if (root.id !== void 0) {
|
|
25
|
+
if (root.id.startsWith("~")) throw new ReservedNodeIdError(root.id);
|
|
26
|
+
if (into.has(root.id)) throw new DuplicateNodeIdError(root.id);
|
|
27
|
+
into.set(root.id, root);
|
|
28
|
+
}
|
|
29
|
+
if (root instanceof Group) for (const child of root.children) indexNodes(child, into, measurerSource);
|
|
30
|
+
}
|
|
31
|
+
function createScene(init) {
|
|
32
|
+
const root = new Group({
|
|
33
|
+
id: "__root",
|
|
34
|
+
children: init.children
|
|
35
|
+
});
|
|
36
|
+
const nodes = /* @__PURE__ */ new Map();
|
|
37
|
+
const playhead = createPlayhead();
|
|
38
|
+
const measurer = signal(null);
|
|
39
|
+
indexNodes(root, nodes, () => measurer() ?? fallbackMeasurer());
|
|
40
|
+
return {
|
|
41
|
+
root,
|
|
42
|
+
nodes,
|
|
43
|
+
size: init.size,
|
|
44
|
+
playhead,
|
|
45
|
+
resolveTarget: (target) => {
|
|
46
|
+
for (let slash = target.lastIndexOf("/"); slash > 0; slash = target.lastIndexOf("/", slash - 1)) {
|
|
47
|
+
const node = nodes.get(target.slice(0, slash));
|
|
48
|
+
if (node) return node.resolveTarget(target.slice(slash + 1));
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
setTextMeasurer: (m) => {
|
|
52
|
+
measurer.set(m);
|
|
53
|
+
},
|
|
54
|
+
get textMeasurer() {
|
|
55
|
+
return measurer.peek() ?? fallbackMeasurer();
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
const bindings = /* @__PURE__ */ new WeakMap();
|
|
60
|
+
/**
|
|
61
|
+
* When a target fails to resolve, return a friendlier reason IF it names a
|
|
62
|
+
* known CONSTRUCTION prop (animatable: false — e.g. `assetId`, `fontFamily`):
|
|
63
|
+
* the target IS correctly rejected, but "no signal resolves to it" hides WHY.
|
|
64
|
+
* Resolves the node by the same longest-registered-id-prefix walk as
|
|
65
|
+
* `resolveTarget`, then checks the node's construction-prop schema. Returns
|
|
66
|
+
* undefined (⇒ the generic message) for a genuinely-unknown prop or unknown id.
|
|
67
|
+
*/
|
|
68
|
+
function constructionPropMessage(nodes, target) {
|
|
69
|
+
for (let slash = target.lastIndexOf("/"); slash > 0; slash = target.lastIndexOf("/", slash - 1)) {
|
|
70
|
+
const node = nodes.get(target.slice(0, slash));
|
|
71
|
+
if (!node) continue;
|
|
72
|
+
const prop = target.slice(slash + 1);
|
|
73
|
+
if (isConstructionProp(node.describeType, prop)) return `'${target}' is a construction prop (animatable:false) — set it at construction (new ${node.describeType}({ ${prop} })); it is not an animatable target.`;
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function bindScene(scene, doc) {
|
|
78
|
+
let perScene = bindings.get(scene);
|
|
79
|
+
if (!perScene) {
|
|
80
|
+
perScene = /* @__PURE__ */ new WeakMap();
|
|
81
|
+
bindings.set(scene, perScene);
|
|
82
|
+
}
|
|
83
|
+
let entry = perScene.get(doc);
|
|
84
|
+
if (!entry) {
|
|
85
|
+
const compiled = compileTimeline(doc);
|
|
86
|
+
entry = {
|
|
87
|
+
compiled,
|
|
88
|
+
bound: bindTimeline(compiled, scene.resolveTarget, scene.playhead, { unboundMessage: (target) => constructionPropMessage(scene.nodes, target) })
|
|
89
|
+
};
|
|
90
|
+
perScene.set(doc, entry);
|
|
91
|
+
}
|
|
92
|
+
return entry;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Empty timeline — zero tracks, so binding installs ZERO computed sources and
|
|
96
|
+
* any imperative `node.set(...)` value survives evaluate untouched. Shared and
|
|
97
|
+
* frozen so the WeakMap binding cache keys on a single stable document across
|
|
98
|
+
* the whole controlled-drive loop (one bind, reused every frame).
|
|
99
|
+
*/
|
|
100
|
+
const EMPTY_TIMELINE = Object.freeze({
|
|
101
|
+
version: 1,
|
|
102
|
+
tracks: []
|
|
103
|
+
});
|
|
104
|
+
function evaluate(scene, doc, t = 0) {
|
|
105
|
+
if (doc === void 0) return evaluate(scene, EMPTY_TIMELINE, scene.playhead.peek());
|
|
106
|
+
bindScene(scene, doc);
|
|
107
|
+
const fps = doc.fps;
|
|
108
|
+
const ctx = {
|
|
109
|
+
time: t,
|
|
110
|
+
frame: fps !== void 0 ? Math.round(t * fps) : -1,
|
|
111
|
+
measurer: scene.textMeasurer
|
|
112
|
+
};
|
|
113
|
+
return evaluateAt(scene.playhead, t, () => {
|
|
114
|
+
const out = createDisplayListBuilder(scene.size);
|
|
115
|
+
scene.root.emit(out, ctx);
|
|
116
|
+
return out.finish();
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
//#endregion
|
|
120
|
+
export { evaluate as a, createScene as i, ReservedNodeIdError as n, bindScene as r, DuplicateNodeIdError as t };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@glissade/scene",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.20.0-pre.4",
|
|
4
4
|
"description": "glissade scene graph: nodes, transforms, DisplayList emission. Renderer-agnostic; zero DOM/Node dependencies.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"engines": {
|
|
@@ -17,6 +17,14 @@
|
|
|
17
17
|
"types": "./dist/layout.d.ts",
|
|
18
18
|
"default": "./dist/layout.js"
|
|
19
19
|
},
|
|
20
|
+
"./layout-ctors": {
|
|
21
|
+
"types": "./dist/layoutCtors.d.ts",
|
|
22
|
+
"default": "./dist/layoutCtors.js"
|
|
23
|
+
},
|
|
24
|
+
"./grid": {
|
|
25
|
+
"types": "./dist/grid.d.ts",
|
|
26
|
+
"default": "./dist/grid.js"
|
|
27
|
+
},
|
|
20
28
|
"./path": {
|
|
21
29
|
"types": "./dist/path.d.ts",
|
|
22
30
|
"default": "./dist/path.js"
|
|
@@ -28,6 +36,18 @@
|
|
|
28
36
|
"./type": {
|
|
29
37
|
"types": "./dist/type.d.ts",
|
|
30
38
|
"default": "./dist/type.js"
|
|
39
|
+
},
|
|
40
|
+
"./diagnostics": {
|
|
41
|
+
"types": "./dist/diagnostics.d.ts",
|
|
42
|
+
"default": "./dist/diagnostics.js"
|
|
43
|
+
},
|
|
44
|
+
"./motion": {
|
|
45
|
+
"types": "./dist/motion.d.ts",
|
|
46
|
+
"default": "./dist/motion.js"
|
|
47
|
+
},
|
|
48
|
+
"./identity": {
|
|
49
|
+
"types": "./dist/identity.d.ts",
|
|
50
|
+
"default": "./dist/identity.js"
|
|
31
51
|
}
|
|
32
52
|
},
|
|
33
53
|
"files": [
|
|
@@ -35,7 +55,7 @@
|
|
|
35
55
|
],
|
|
36
56
|
"dependencies": {
|
|
37
57
|
"yoga-layout": "^3.2.1",
|
|
38
|
-
"@glissade/core": "0.
|
|
58
|
+
"@glissade/core": "0.20.0-pre.4"
|
|
39
59
|
},
|
|
40
60
|
"repository": {
|
|
41
61
|
"type": "git",
|
|
@@ -44,6 +64,6 @@
|
|
|
44
64
|
},
|
|
45
65
|
"scripts": {
|
|
46
66
|
"build": "tsdown",
|
|
47
|
-
"typecheck": "tsc --noEmit"
|
|
67
|
+
"typecheck": "tsc --noEmit && tsc --noEmit -p spike/tsconfig.json"
|
|
48
68
|
}
|
|
49
69
|
}
|