@godot-scene-web/vue 0.1.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/LICENSE +21 -0
- package/dist/index.d.ts +216 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +467 -0
- package/dist/index.js.map +1 -0
- package/package.json +49 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Tomás Fox
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import { GodotHtmlModel, GodotHtmlRenderOptions } from "@godot-scene-web/html";
|
|
2
|
+
import { GodotHtmlRuntimeOptions } from "@godot-scene-web/html/runtime";
|
|
3
|
+
import { GodotLayoutOptions } from "@godot-scene-web/layout";
|
|
4
|
+
import { GodotResourceStatus, SceneGraph, SceneStructureOptions } from "@godot-scene-web/scene-graph";
|
|
5
|
+
import { ComputedRef, PropType, VNodeChild } from "vue";
|
|
6
|
+
import { GodotNode, GodotResourceRefValue, GodotSceneState, GodotVariant } from "@godot-scene-web/core";
|
|
7
|
+
|
|
8
|
+
//#region src/index.d.ts
|
|
9
|
+
type GodotSceneViewOptions = SceneStructureOptions & GodotLayoutOptions & GodotHtmlRenderOptions & GodotHtmlRuntimeOptions & {
|
|
10
|
+
/**
|
|
11
|
+
* Layout pipeline. `"browser"` (default): the browser CSS engine resolves
|
|
12
|
+
* layout (containers flex/grid, anchored Controls via CSS insets, text sizes
|
|
13
|
+
* itself) straight from the structural `SceneGraph`, never running the rect
|
|
14
|
+
* cascade. `"computed"`: gsw's rect cascade produces absolute px — opt in for
|
|
15
|
+
* the layout-diff/parity oracle or pixel-exact needs.
|
|
16
|
+
*/
|
|
17
|
+
layoutMode?: "browser" | "computed";
|
|
18
|
+
};
|
|
19
|
+
interface GodotSubscribableResourceSource {
|
|
20
|
+
subscribe: (listener: () => void) => () => void;
|
|
21
|
+
/**
|
|
22
|
+
* Monotonic settle counters from the backing resolver (see the gsw fetch
|
|
23
|
+
* resolver's `generations()`). When present, `useGodotSceneModel` memoizes the
|
|
24
|
+
* scene-graph derivation on (input identities + the `scenes` counter): graph
|
|
25
|
+
* derivation reads scene documents but never `.tres`/font/texture content, so
|
|
26
|
+
* resource-only settle notifications skip the derive and rebuild only the HTML
|
|
27
|
+
* model. Absent ⇒ derive runs on every recompute (always correct).
|
|
28
|
+
*/
|
|
29
|
+
generations?: () => {
|
|
30
|
+
scenes: number;
|
|
31
|
+
resources: number;
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
interface GodotSceneModelSource {
|
|
35
|
+
scene?: GodotSceneState;
|
|
36
|
+
model?: GodotHtmlModel;
|
|
37
|
+
options?: GodotSceneViewOptions;
|
|
38
|
+
resolveResource?: (ref: GodotResourceRefValue, node: GodotNode) => unknown;
|
|
39
|
+
resolveResourcePath?: (path: string, node: GodotNode) => unknown;
|
|
40
|
+
overrideNodeProps?: (node: GodotNode, path: string) => Record<string, GodotVariant> | undefined;
|
|
41
|
+
overrideNodeType?: (node: GodotNode, path: string) => string | undefined;
|
|
42
|
+
nodeOverrides?: GodotNodePropertyOverrideMap;
|
|
43
|
+
nodeNameOverrides?: GodotNodePropertyOverrideMap;
|
|
44
|
+
mountExternalScene?: (ref: GodotResourceRefValue, node: GodotNode) => GodotSceneState | undefined;
|
|
45
|
+
resourceSource?: GodotSubscribableResourceSource;
|
|
46
|
+
}
|
|
47
|
+
type GodotNodePropertyOverrideMap = Record<string, Record<string, GodotVariant>>;
|
|
48
|
+
type GodotNodePropertyOverrideResolver = (node: GodotNode, path: string) => Record<string, GodotVariant> | undefined;
|
|
49
|
+
declare const GodotNodeView: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
|
|
50
|
+
path: {
|
|
51
|
+
type: StringConstructor;
|
|
52
|
+
required: true;
|
|
53
|
+
};
|
|
54
|
+
}>, () => VNodeChild, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
|
|
55
|
+
path: {
|
|
56
|
+
type: StringConstructor;
|
|
57
|
+
required: true;
|
|
58
|
+
};
|
|
59
|
+
}>> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
60
|
+
declare const GodotSceneView: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
|
|
61
|
+
scene: {
|
|
62
|
+
type: PropType<GodotSceneState>;
|
|
63
|
+
required: false;
|
|
64
|
+
default: undefined;
|
|
65
|
+
};
|
|
66
|
+
model: {
|
|
67
|
+
type: PropType<GodotHtmlModel>;
|
|
68
|
+
required: false;
|
|
69
|
+
default: undefined;
|
|
70
|
+
};
|
|
71
|
+
options: {
|
|
72
|
+
type: PropType<GodotSceneViewOptions>;
|
|
73
|
+
required: false;
|
|
74
|
+
default: () => {};
|
|
75
|
+
};
|
|
76
|
+
resolveResource: {
|
|
77
|
+
type: PropType<(ref: GodotResourceRefValue, node: GodotNode) => unknown>;
|
|
78
|
+
required: false;
|
|
79
|
+
default: undefined;
|
|
80
|
+
};
|
|
81
|
+
resolveResourcePath: {
|
|
82
|
+
type: PropType<(path: string, node: GodotNode) => unknown>;
|
|
83
|
+
required: false;
|
|
84
|
+
default: undefined;
|
|
85
|
+
};
|
|
86
|
+
overrideNodeProps: {
|
|
87
|
+
type: PropType<(node: GodotNode, path: string) => Record<string, GodotVariant> | undefined>;
|
|
88
|
+
required: false;
|
|
89
|
+
default: undefined;
|
|
90
|
+
};
|
|
91
|
+
overrideNodeType: {
|
|
92
|
+
type: PropType<(node: GodotNode, path: string) => string | undefined>;
|
|
93
|
+
required: false;
|
|
94
|
+
default: undefined;
|
|
95
|
+
};
|
|
96
|
+
nodeOverrides: {
|
|
97
|
+
type: PropType<GodotNodePropertyOverrideMap>;
|
|
98
|
+
required: false;
|
|
99
|
+
default: () => {};
|
|
100
|
+
};
|
|
101
|
+
nodeNameOverrides: {
|
|
102
|
+
type: PropType<GodotNodePropertyOverrideMap>;
|
|
103
|
+
required: false;
|
|
104
|
+
default: () => {};
|
|
105
|
+
};
|
|
106
|
+
mountExternalScene: {
|
|
107
|
+
type: PropType<(ref: GodotResourceRefValue, node: GodotNode) => GodotSceneState | undefined>;
|
|
108
|
+
required: false;
|
|
109
|
+
default: undefined;
|
|
110
|
+
};
|
|
111
|
+
resourceSource: {
|
|
112
|
+
type: PropType<GodotSubscribableResourceSource>;
|
|
113
|
+
required: false;
|
|
114
|
+
default: undefined;
|
|
115
|
+
};
|
|
116
|
+
debug: {
|
|
117
|
+
type: BooleanConstructor;
|
|
118
|
+
default: boolean;
|
|
119
|
+
};
|
|
120
|
+
componentTree: {
|
|
121
|
+
type: BooleanConstructor;
|
|
122
|
+
default: boolean;
|
|
123
|
+
};
|
|
124
|
+
}>, () => VNodeChild[] | null, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
|
|
125
|
+
scene: {
|
|
126
|
+
type: PropType<GodotSceneState>;
|
|
127
|
+
required: false;
|
|
128
|
+
default: undefined;
|
|
129
|
+
};
|
|
130
|
+
model: {
|
|
131
|
+
type: PropType<GodotHtmlModel>;
|
|
132
|
+
required: false;
|
|
133
|
+
default: undefined;
|
|
134
|
+
};
|
|
135
|
+
options: {
|
|
136
|
+
type: PropType<GodotSceneViewOptions>;
|
|
137
|
+
required: false;
|
|
138
|
+
default: () => {};
|
|
139
|
+
};
|
|
140
|
+
resolveResource: {
|
|
141
|
+
type: PropType<(ref: GodotResourceRefValue, node: GodotNode) => unknown>;
|
|
142
|
+
required: false;
|
|
143
|
+
default: undefined;
|
|
144
|
+
};
|
|
145
|
+
resolveResourcePath: {
|
|
146
|
+
type: PropType<(path: string, node: GodotNode) => unknown>;
|
|
147
|
+
required: false;
|
|
148
|
+
default: undefined;
|
|
149
|
+
};
|
|
150
|
+
overrideNodeProps: {
|
|
151
|
+
type: PropType<(node: GodotNode, path: string) => Record<string, GodotVariant> | undefined>;
|
|
152
|
+
required: false;
|
|
153
|
+
default: undefined;
|
|
154
|
+
};
|
|
155
|
+
overrideNodeType: {
|
|
156
|
+
type: PropType<(node: GodotNode, path: string) => string | undefined>;
|
|
157
|
+
required: false;
|
|
158
|
+
default: undefined;
|
|
159
|
+
};
|
|
160
|
+
nodeOverrides: {
|
|
161
|
+
type: PropType<GodotNodePropertyOverrideMap>;
|
|
162
|
+
required: false;
|
|
163
|
+
default: () => {};
|
|
164
|
+
};
|
|
165
|
+
nodeNameOverrides: {
|
|
166
|
+
type: PropType<GodotNodePropertyOverrideMap>;
|
|
167
|
+
required: false;
|
|
168
|
+
default: () => {};
|
|
169
|
+
};
|
|
170
|
+
mountExternalScene: {
|
|
171
|
+
type: PropType<(ref: GodotResourceRefValue, node: GodotNode) => GodotSceneState | undefined>;
|
|
172
|
+
required: false;
|
|
173
|
+
default: undefined;
|
|
174
|
+
};
|
|
175
|
+
resourceSource: {
|
|
176
|
+
type: PropType<GodotSubscribableResourceSource>;
|
|
177
|
+
required: false;
|
|
178
|
+
default: undefined;
|
|
179
|
+
};
|
|
180
|
+
debug: {
|
|
181
|
+
type: BooleanConstructor;
|
|
182
|
+
default: boolean;
|
|
183
|
+
};
|
|
184
|
+
componentTree: {
|
|
185
|
+
type: BooleanConstructor;
|
|
186
|
+
default: boolean;
|
|
187
|
+
};
|
|
188
|
+
}>> & Readonly<{}>, {
|
|
189
|
+
scene: GodotSceneState;
|
|
190
|
+
model: GodotHtmlModel;
|
|
191
|
+
options: GodotSceneViewOptions;
|
|
192
|
+
resolveResource: (ref: GodotResourceRefValue, node: GodotNode) => unknown;
|
|
193
|
+
resolveResourcePath: (path: string, node: GodotNode) => unknown;
|
|
194
|
+
overrideNodeProps: (node: GodotNode, path: string) => Record<string, GodotVariant> | undefined;
|
|
195
|
+
overrideNodeType: (node: GodotNode, path: string) => string | undefined;
|
|
196
|
+
nodeOverrides: GodotNodePropertyOverrideMap;
|
|
197
|
+
nodeNameOverrides: GodotNodePropertyOverrideMap;
|
|
198
|
+
mountExternalScene: (ref: GodotResourceRefValue, node: GodotNode) => GodotSceneState | undefined;
|
|
199
|
+
resourceSource: GodotSubscribableResourceSource;
|
|
200
|
+
debug: boolean;
|
|
201
|
+
componentTree: boolean;
|
|
202
|
+
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
203
|
+
declare function useGodotSceneModel(source: GodotSceneModelSource): {
|
|
204
|
+
model: ComputedRef<GodotHtmlModel | null>;
|
|
205
|
+
graph: ComputedRef<SceneGraph | null>;
|
|
206
|
+
tintFilterIdPrefix: string;
|
|
207
|
+
revision: ComputedRef<number>;
|
|
208
|
+
pendingResources: ComputedRef<GodotResourceStatus[]>;
|
|
209
|
+
failedResources: ComputedRef<GodotResourceStatus[]>;
|
|
210
|
+
};
|
|
211
|
+
declare function overrideGodotNodePropsByPath(overrides: GodotNodePropertyOverrideMap): GodotNodePropertyOverrideResolver;
|
|
212
|
+
declare function overrideGodotNodePropsByName(overrides: GodotNodePropertyOverrideMap): GodotNodePropertyOverrideResolver;
|
|
213
|
+
declare function mergeGodotNodePropOverrides(...resolvers: Array<GodotNodePropertyOverrideResolver | undefined>): GodotNodePropertyOverrideResolver;
|
|
214
|
+
//#endregion
|
|
215
|
+
export { GodotNodePropertyOverrideMap, GodotNodePropertyOverrideResolver, GodotNodeView, GodotSceneModelSource, GodotSceneView, GodotSceneViewOptions, GodotSubscribableResourceSource, mergeGodotNodePropOverrides, overrideGodotNodePropsByName, overrideGodotNodePropsByPath, useGodotSceneModel };
|
|
216
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/index.ts"],"mappings":"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"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,467 @@
|
|
|
1
|
+
import { DEFAULT_BROWSER_VIEWPORT, FRAME_CLASS, ROOT_PARENT_CONTEXT, STAGE_CLASS, buildGraphNodeHtml, buildHiddenGraphNode, buildNodePresentation, buildSceneFragment, buildSceneStructure, buildStyleElement, buildTintDefsElement, contentScaleStageStyle, observeContentScale, renderSceneGraphToHtmlModel, renderSceneToHtmlModel, resolveContentScale, stabilizeRenderElements, uniqueFontFaces } from "@godot-scene-web/html";
|
|
2
|
+
import { createHtmlEffectsHost } from "@godot-scene-web/html/runtime";
|
|
3
|
+
import { resolveGodotSceneTree } from "@godot-scene-web/layout";
|
|
4
|
+
import { createSceneGraphNodeMemo, deriveSceneGraph } from "@godot-scene-web/scene-graph";
|
|
5
|
+
import { computed, createCommentVNode, defineComponent, h, inject, onBeforeUnmount, onMounted, provide, ref, watch } from "vue";
|
|
6
|
+
//#region src/index.ts
|
|
7
|
+
let tintFilterViewSeq = 0;
|
|
8
|
+
function nextTintFilterIdPrefix() {
|
|
9
|
+
tintFilterViewSeq += 1;
|
|
10
|
+
return `godot-tint-v${tintFilterViewSeq}-`;
|
|
11
|
+
}
|
|
12
|
+
const COMPONENT_MODEL_KEY = Symbol("godot-component-model");
|
|
13
|
+
const EMPTY_SLOTS = {};
|
|
14
|
+
const NO_CHILD_SLOTS = {
|
|
15
|
+
behind: [],
|
|
16
|
+
normal: []
|
|
17
|
+
};
|
|
18
|
+
const GodotNodeView = defineComponent({
|
|
19
|
+
name: "GodotNodeView",
|
|
20
|
+
props: { path: {
|
|
21
|
+
type: String,
|
|
22
|
+
required: true
|
|
23
|
+
} },
|
|
24
|
+
setup(props) {
|
|
25
|
+
const model = inject(COMPONENT_MODEL_KEY, null);
|
|
26
|
+
const node = computed(() => model?.value?.nodesByPath.get(props.path) ?? null);
|
|
27
|
+
const slots = computed(() => model?.value?.childrenByPath.get(props.path) ?? NO_CHILD_SLOTS);
|
|
28
|
+
return () => {
|
|
29
|
+
const current = node.value;
|
|
30
|
+
if (!current) return null;
|
|
31
|
+
return renderComponentNode(current, slots.value);
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
function renderComponentNode(node, slots) {
|
|
36
|
+
const shell = buildNodePresentation(node);
|
|
37
|
+
if (shell.kind === "comment") return createCommentVNode(shell.text ?? "");
|
|
38
|
+
const self = shell.children?.[0];
|
|
39
|
+
const childComponent = (path) => h(GodotNodeView, {
|
|
40
|
+
key: path,
|
|
41
|
+
path
|
|
42
|
+
});
|
|
43
|
+
return h(shell.tag, {
|
|
44
|
+
key: shell.key,
|
|
45
|
+
class: shell.className,
|
|
46
|
+
style: shell.style,
|
|
47
|
+
...shell.attributes
|
|
48
|
+
}, [
|
|
49
|
+
...slots.behind.map(childComponent),
|
|
50
|
+
...self ? [renderElement(self, EMPTY_SLOTS)] : [],
|
|
51
|
+
...slots.normal.map(childComponent)
|
|
52
|
+
]);
|
|
53
|
+
}
|
|
54
|
+
function renderComponentTree(model, frameRef, stageRef, debug) {
|
|
55
|
+
const out = [];
|
|
56
|
+
const style = buildStyleElement(model, { includeBaseCss: false });
|
|
57
|
+
if (style) out.push(renderElement(style, EMPTY_SLOTS));
|
|
58
|
+
const tintDefs = buildTintDefsElement(model);
|
|
59
|
+
if (tintDefs) out.push(renderElement(tintDefs, EMPTY_SLOTS));
|
|
60
|
+
const stageStyle = model.contentScale ? { ...contentScaleStageStyle(model.contentScale) } : {
|
|
61
|
+
width: `${model.viewport.width}px`,
|
|
62
|
+
height: `${model.viewport.height}px`
|
|
63
|
+
};
|
|
64
|
+
const stage = h("div", {
|
|
65
|
+
class: debug ? `${STAGE_CLASS} godot-scene-debug` : STAGE_CLASS,
|
|
66
|
+
"data-godot-stage": "true",
|
|
67
|
+
ref: stageRef,
|
|
68
|
+
style: stageStyle
|
|
69
|
+
}, model.rootPaths.map((path) => h(GodotNodeView, {
|
|
70
|
+
key: path,
|
|
71
|
+
path
|
|
72
|
+
})));
|
|
73
|
+
if (!model.contentScale) {
|
|
74
|
+
out.push(stage);
|
|
75
|
+
return out;
|
|
76
|
+
}
|
|
77
|
+
const background = model.contentScale.background;
|
|
78
|
+
out.push(h("div", {
|
|
79
|
+
class: FRAME_CLASS,
|
|
80
|
+
ref: frameRef,
|
|
81
|
+
style: background ? { background } : {}
|
|
82
|
+
}, [stage]));
|
|
83
|
+
return out;
|
|
84
|
+
}
|
|
85
|
+
function parentContextKey(ctx) {
|
|
86
|
+
return `${ctx.parentType ?? ""}|${ctx.parentHorizontal ? 1 : 0}`;
|
|
87
|
+
}
|
|
88
|
+
function childSlotsKey(slots) {
|
|
89
|
+
return `${slots.behind.join("\n")}\u0000${slots.normal.join("\n")}`;
|
|
90
|
+
}
|
|
91
|
+
function buildComponentSceneModel(graph, options, viewport, contentScale, nodeCache, childCache, tintFilters) {
|
|
92
|
+
const structure = buildSceneStructure(graph);
|
|
93
|
+
const nodesByPath = /* @__PURE__ */ new Map();
|
|
94
|
+
const fontFaces = [];
|
|
95
|
+
const resourceStatuses = [...graph.resourceStatuses ?? []];
|
|
96
|
+
const builtPaths = /* @__PURE__ */ new Set();
|
|
97
|
+
const walk = (path, parentLayout, parentPath, parentContext) => {
|
|
98
|
+
const graphNode = structure.nodesByPath.get(path);
|
|
99
|
+
if (!graphNode) return;
|
|
100
|
+
builtPaths.add(path);
|
|
101
|
+
if (!graphNode.visible) {
|
|
102
|
+
nodesByPath.set(path, buildHiddenGraphNode(graphNode, parentPath));
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
const ctxKey = parentContextKey(parentContext);
|
|
106
|
+
let entry = nodeCache.get(path);
|
|
107
|
+
const hadPendingResource = entry?.result.resourceStatuses.some((status) => status.status === "pending") ?? false;
|
|
108
|
+
if (!entry || entry.graphNode !== graphNode || entry.ctxKey !== ctxKey || hadPendingResource) {
|
|
109
|
+
entry = {
|
|
110
|
+
graphNode,
|
|
111
|
+
ctxKey,
|
|
112
|
+
result: buildGraphNodeHtml(graphNode, parentLayout, parentPath, parentContext, options, tintFilters, viewport, contentScale)
|
|
113
|
+
};
|
|
114
|
+
nodeCache.set(path, entry);
|
|
115
|
+
}
|
|
116
|
+
nodesByPath.set(path, entry.result.node);
|
|
117
|
+
fontFaces.push(...entry.result.fontFaces);
|
|
118
|
+
resourceStatuses.push(...entry.result.resourceStatuses);
|
|
119
|
+
const slots = structure.childrenByPath.get(path);
|
|
120
|
+
if (slots) {
|
|
121
|
+
for (const childPath of slots.behind) walk(childPath, entry.result.layout, path, entry.result.childContext);
|
|
122
|
+
for (const childPath of slots.normal) walk(childPath, entry.result.layout, path, entry.result.childContext);
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
for (const rootPath of structure.rootPaths) walk(rootPath, void 0, null, ROOT_PARENT_CONTEXT);
|
|
126
|
+
for (const key of [...nodeCache.keys()]) if (!builtPaths.has(key)) nodeCache.delete(key);
|
|
127
|
+
const childrenByPath = /* @__PURE__ */ new Map();
|
|
128
|
+
for (const [path, slots] of structure.childrenByPath) {
|
|
129
|
+
if (!builtPaths.has(path) || !structure.nodesByPath.get(path)?.visible) continue;
|
|
130
|
+
const key = childSlotsKey(slots);
|
|
131
|
+
const cached = childCache.get(path);
|
|
132
|
+
if (cached && cached.key === key) childrenByPath.set(path, cached.slots);
|
|
133
|
+
else {
|
|
134
|
+
childCache.set(path, {
|
|
135
|
+
key,
|
|
136
|
+
slots
|
|
137
|
+
});
|
|
138
|
+
childrenByPath.set(path, slots);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
for (const key of [...childCache.keys()]) if (!childrenByPath.has(key)) childCache.delete(key);
|
|
142
|
+
return {
|
|
143
|
+
rootPaths: structure.rootPaths,
|
|
144
|
+
nodesByPath,
|
|
145
|
+
childrenByPath,
|
|
146
|
+
fontFaces: uniqueFontFaces(fontFaces),
|
|
147
|
+
tintFilters: [...tintFilters].map(([markup, id]) => ({
|
|
148
|
+
id,
|
|
149
|
+
markup
|
|
150
|
+
})),
|
|
151
|
+
resourceStatuses,
|
|
152
|
+
viewport,
|
|
153
|
+
contentScale,
|
|
154
|
+
css: ""
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
const GodotSceneView = defineComponent({
|
|
158
|
+
name: "GodotSceneView",
|
|
159
|
+
props: {
|
|
160
|
+
scene: {
|
|
161
|
+
type: Object,
|
|
162
|
+
required: false,
|
|
163
|
+
default: void 0
|
|
164
|
+
},
|
|
165
|
+
model: {
|
|
166
|
+
type: Object,
|
|
167
|
+
required: false,
|
|
168
|
+
default: void 0
|
|
169
|
+
},
|
|
170
|
+
options: {
|
|
171
|
+
type: Object,
|
|
172
|
+
required: false,
|
|
173
|
+
default: () => ({})
|
|
174
|
+
},
|
|
175
|
+
resolveResource: {
|
|
176
|
+
type: Function,
|
|
177
|
+
required: false,
|
|
178
|
+
default: void 0
|
|
179
|
+
},
|
|
180
|
+
resolveResourcePath: {
|
|
181
|
+
type: Function,
|
|
182
|
+
required: false,
|
|
183
|
+
default: void 0
|
|
184
|
+
},
|
|
185
|
+
overrideNodeProps: {
|
|
186
|
+
type: Function,
|
|
187
|
+
required: false,
|
|
188
|
+
default: void 0
|
|
189
|
+
},
|
|
190
|
+
overrideNodeType: {
|
|
191
|
+
type: Function,
|
|
192
|
+
required: false,
|
|
193
|
+
default: void 0
|
|
194
|
+
},
|
|
195
|
+
nodeOverrides: {
|
|
196
|
+
type: Object,
|
|
197
|
+
required: false,
|
|
198
|
+
default: () => ({})
|
|
199
|
+
},
|
|
200
|
+
nodeNameOverrides: {
|
|
201
|
+
type: Object,
|
|
202
|
+
required: false,
|
|
203
|
+
default: () => ({})
|
|
204
|
+
},
|
|
205
|
+
mountExternalScene: {
|
|
206
|
+
type: Function,
|
|
207
|
+
required: false,
|
|
208
|
+
default: void 0
|
|
209
|
+
},
|
|
210
|
+
resourceSource: {
|
|
211
|
+
type: Object,
|
|
212
|
+
required: false,
|
|
213
|
+
default: void 0
|
|
214
|
+
},
|
|
215
|
+
debug: {
|
|
216
|
+
type: Boolean,
|
|
217
|
+
default: false
|
|
218
|
+
},
|
|
219
|
+
componentTree: {
|
|
220
|
+
type: Boolean,
|
|
221
|
+
default: false
|
|
222
|
+
}
|
|
223
|
+
},
|
|
224
|
+
setup(props, { slots }) {
|
|
225
|
+
const sceneModel = useGodotSceneModel(props);
|
|
226
|
+
const fragmentModel = sceneModel.model;
|
|
227
|
+
const nodeCache = /* @__PURE__ */ new Map();
|
|
228
|
+
const childCache = /* @__PURE__ */ new Map();
|
|
229
|
+
const componentTintFilters = /* @__PURE__ */ new Map();
|
|
230
|
+
const componentModel = computed(() => {
|
|
231
|
+
if (!props.componentTree) return null;
|
|
232
|
+
sceneModel.revision.value;
|
|
233
|
+
const graph = sceneModel.graph.value;
|
|
234
|
+
if (!graph) return null;
|
|
235
|
+
const options = props.options ?? {};
|
|
236
|
+
const htmlOptions = resolveHtmlOptions(props, sceneModel.tintFilterIdPrefix);
|
|
237
|
+
const viewport = {
|
|
238
|
+
width: options.viewport?.width ?? DEFAULT_BROWSER_VIEWPORT.width,
|
|
239
|
+
height: options.viewport?.height ?? DEFAULT_BROWSER_VIEWPORT.height
|
|
240
|
+
};
|
|
241
|
+
return buildComponentSceneModel(graph, htmlOptions, viewport, resolveContentScale(options.contentScale, viewport), nodeCache, childCache, componentTintFilters);
|
|
242
|
+
});
|
|
243
|
+
provide(COMPONENT_MODEL_KEY, componentModel);
|
|
244
|
+
const activeModel = computed(() => props.componentTree ? componentModel.value : fragmentModel.value);
|
|
245
|
+
const frame = ref(null);
|
|
246
|
+
const stage = ref(null);
|
|
247
|
+
let disposeScale = null;
|
|
248
|
+
const syncScale = () => {
|
|
249
|
+
disposeScale?.();
|
|
250
|
+
disposeScale = null;
|
|
251
|
+
const contentScale = activeModel.value?.contentScale;
|
|
252
|
+
if (contentScale && contentScale.technique === "transform" && frame.value) disposeScale = observeContentScale(frame.value, contentScale.base);
|
|
253
|
+
};
|
|
254
|
+
onMounted(syncScale);
|
|
255
|
+
watch([activeModel, frame], syncScale, { flush: "post" });
|
|
256
|
+
onBeforeUnmount(() => {
|
|
257
|
+
disposeScale?.();
|
|
258
|
+
disposeScale = null;
|
|
259
|
+
});
|
|
260
|
+
let effectsHost = null;
|
|
261
|
+
let runtimeRoot = null;
|
|
262
|
+
const disposeRuntimes = () => {
|
|
263
|
+
effectsHost?.dispose();
|
|
264
|
+
effectsHost = null;
|
|
265
|
+
runtimeRoot = null;
|
|
266
|
+
};
|
|
267
|
+
const syncRuntimes = () => {
|
|
268
|
+
const el = stage.value;
|
|
269
|
+
if (!el) {
|
|
270
|
+
disposeRuntimes();
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
const options = resolveHtmlOptions(props, sceneModel.tintFilterIdPrefix);
|
|
274
|
+
if (runtimeRoot !== el) {
|
|
275
|
+
disposeRuntimes();
|
|
276
|
+
runtimeRoot = el;
|
|
277
|
+
effectsHost = createHtmlEffectsHost(el, options);
|
|
278
|
+
} else effectsHost?.updateOptions(options);
|
|
279
|
+
effectsHost?.reconcile();
|
|
280
|
+
};
|
|
281
|
+
onMounted(syncRuntimes);
|
|
282
|
+
watch([activeModel, stage], syncRuntimes, { flush: "post" });
|
|
283
|
+
onBeforeUnmount(() => {
|
|
284
|
+
disposeRuntimes();
|
|
285
|
+
});
|
|
286
|
+
let previousFragment;
|
|
287
|
+
const vnodeCache = /* @__PURE__ */ new WeakMap();
|
|
288
|
+
return () => {
|
|
289
|
+
if (props.componentTree) {
|
|
290
|
+
previousFragment = void 0;
|
|
291
|
+
const current = componentModel.value;
|
|
292
|
+
if (!current) return null;
|
|
293
|
+
return renderComponentTree(current, frame, stage, props.debug);
|
|
294
|
+
}
|
|
295
|
+
const current = fragmentModel.value;
|
|
296
|
+
if (!current) {
|
|
297
|
+
previousFragment = void 0;
|
|
298
|
+
return null;
|
|
299
|
+
}
|
|
300
|
+
let fragment = buildSceneFragment(current, {
|
|
301
|
+
debug: props.debug,
|
|
302
|
+
includeStyle: true,
|
|
303
|
+
includeBaseCss: false
|
|
304
|
+
});
|
|
305
|
+
if (slots.node) {
|
|
306
|
+
previousFragment = void 0;
|
|
307
|
+
return fragment.map((element) => renderElement(element, slots, frame, void 0, stage));
|
|
308
|
+
}
|
|
309
|
+
fragment = stabilizeRenderElements(previousFragment, fragment);
|
|
310
|
+
previousFragment = fragment;
|
|
311
|
+
return fragment.map((element) => renderElement(element, slots, frame, vnodeCache, stage));
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
});
|
|
315
|
+
function useGodotSceneModel(source) {
|
|
316
|
+
const revision = ref(0);
|
|
317
|
+
const tintFilterIdPrefix = nextTintFilterIdPrefix();
|
|
318
|
+
let unsubscribe = null;
|
|
319
|
+
const resubscribe = (resourceSource) => {
|
|
320
|
+
unsubscribe?.();
|
|
321
|
+
unsubscribe = resourceSource ? resourceSource.subscribe(() => {
|
|
322
|
+
revision.value += 1;
|
|
323
|
+
}) : null;
|
|
324
|
+
};
|
|
325
|
+
watch(() => source.resourceSource, resubscribe, { immediate: true });
|
|
326
|
+
onBeforeUnmount(() => {
|
|
327
|
+
unsubscribe?.();
|
|
328
|
+
unsubscribe = null;
|
|
329
|
+
});
|
|
330
|
+
const deriveMemo = { entry: null };
|
|
331
|
+
const nodeMemo = createSceneGraphNodeMemo();
|
|
332
|
+
const graph = computed(() => {
|
|
333
|
+
revision.value;
|
|
334
|
+
if (source.model) return null;
|
|
335
|
+
return resolveSceneGraph(source, deriveMemo, nodeMemo);
|
|
336
|
+
});
|
|
337
|
+
const model = computed(() => {
|
|
338
|
+
if (source.model) return source.model;
|
|
339
|
+
const resolved = graph.value;
|
|
340
|
+
if (!resolved) return null;
|
|
341
|
+
const options = source.options ?? {};
|
|
342
|
+
const htmlOptions = resolveHtmlOptions(source, tintFilterIdPrefix);
|
|
343
|
+
if (options.layoutMode === "computed") return renderSceneToHtmlModel(resolveGodotSceneTree(resolved, resolveStructureOptions(source)), htmlOptions);
|
|
344
|
+
return renderSceneGraphToHtmlModel(resolved, {
|
|
345
|
+
...htmlOptions,
|
|
346
|
+
viewport: options.viewport
|
|
347
|
+
});
|
|
348
|
+
});
|
|
349
|
+
const pendingResources = computed(() => (model.value?.resourceStatuses ?? []).filter((resource) => resource.status === "pending"));
|
|
350
|
+
const failedResources = computed(() => (model.value?.resourceStatuses ?? []).filter((resource) => resource.status === "error"));
|
|
351
|
+
return {
|
|
352
|
+
model,
|
|
353
|
+
graph,
|
|
354
|
+
tintFilterIdPrefix,
|
|
355
|
+
revision: computed(() => revision.value),
|
|
356
|
+
pendingResources,
|
|
357
|
+
failedResources
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
function deriveMemoKeys(source) {
|
|
361
|
+
return [
|
|
362
|
+
source.scene,
|
|
363
|
+
source.options,
|
|
364
|
+
source.resolveResource,
|
|
365
|
+
source.resolveResourcePath,
|
|
366
|
+
source.overrideNodeProps,
|
|
367
|
+
source.overrideNodeType,
|
|
368
|
+
source.nodeOverrides,
|
|
369
|
+
source.nodeNameOverrides,
|
|
370
|
+
source.mountExternalScene
|
|
371
|
+
];
|
|
372
|
+
}
|
|
373
|
+
function resolveStructureOptions(source) {
|
|
374
|
+
const options = source.options ?? {};
|
|
375
|
+
const resolveResource = source.resolveResource ?? options.resolveResource;
|
|
376
|
+
return {
|
|
377
|
+
...options,
|
|
378
|
+
resolveResource,
|
|
379
|
+
overrideNodeProps: resolveVueNodePropsOverride(options.overrideNodeProps, source.overrideNodeProps, source.nodeNameOverrides ?? {}, source.nodeOverrides ?? {}),
|
|
380
|
+
overrideNodeType: source.overrideNodeType ?? options.overrideNodeType,
|
|
381
|
+
mountExternalScene: source.mountExternalScene ?? options.mountExternalScene
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
function resolveHtmlOptions(source, tintFilterIdPrefix) {
|
|
385
|
+
const options = source.options ?? {};
|
|
386
|
+
return {
|
|
387
|
+
...options,
|
|
388
|
+
resolveResource: source.resolveResource ?? options.resolveResource,
|
|
389
|
+
resolveResourcePath: source.resolveResourcePath ?? options.resolveResourcePath,
|
|
390
|
+
tintFilterIdPrefix: options.tintFilterIdPrefix ?? tintFilterIdPrefix
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
function resolveSceneGraph(source, deriveMemo, nodeMemo) {
|
|
394
|
+
if (!source.scene) return null;
|
|
395
|
+
const structureOptions = resolveStructureOptions(source);
|
|
396
|
+
const scenesGeneration = source.resourceSource?.generations?.().scenes;
|
|
397
|
+
const memoKeys = deriveMemoKeys(source);
|
|
398
|
+
const memoEntry = deriveMemo?.entry;
|
|
399
|
+
if (memoEntry && scenesGeneration !== void 0 && memoEntry.scenesGeneration === scenesGeneration && memoEntry.keys.length === memoKeys.length && memoEntry.keys.every((key, index) => key === memoKeys[index])) return memoEntry.graph;
|
|
400
|
+
const graph = deriveSceneGraph(source.scene, structureOptions, nodeMemo);
|
|
401
|
+
if (deriveMemo && scenesGeneration !== void 0) deriveMemo.entry = {
|
|
402
|
+
keys: memoKeys,
|
|
403
|
+
scenesGeneration,
|
|
404
|
+
graph
|
|
405
|
+
};
|
|
406
|
+
return graph;
|
|
407
|
+
}
|
|
408
|
+
function overrideGodotNodePropsByPath(overrides) {
|
|
409
|
+
return (_node, path) => overrides[path];
|
|
410
|
+
}
|
|
411
|
+
function overrideGodotNodePropsByName(overrides) {
|
|
412
|
+
return (node) => overrides[node.name];
|
|
413
|
+
}
|
|
414
|
+
function mergeGodotNodePropOverrides(...resolvers) {
|
|
415
|
+
return (node, path) => {
|
|
416
|
+
const merged = {};
|
|
417
|
+
for (const resolver of resolvers) Object.assign(merged, resolver?.(node, path));
|
|
418
|
+
return Object.keys(merged).length > 0 ? merged : void 0;
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
function resolveVueNodePropsOverride(optionsOverride, propOverride, nameOverrides, pathOverrides) {
|
|
422
|
+
return mergeGodotNodePropOverrides(optionsOverride, propOverride, overrideGodotNodePropsByName(nameOverrides), overrideGodotNodePropsByPath(pathOverrides));
|
|
423
|
+
}
|
|
424
|
+
function renderElement(element, slots, frameRef, cache, stageRef) {
|
|
425
|
+
const cached = cache?.get(element);
|
|
426
|
+
if (cached !== void 0) return cached;
|
|
427
|
+
if (element.kind === "comment") {
|
|
428
|
+
const comment = createCommentVNode(element.text ?? "");
|
|
429
|
+
cache?.set(element, comment);
|
|
430
|
+
return comment;
|
|
431
|
+
}
|
|
432
|
+
const data = {};
|
|
433
|
+
if (element.key !== void 0) data.key = element.key;
|
|
434
|
+
if (element.className !== void 0) data.class = element.className;
|
|
435
|
+
if (element.style !== void 0) data.style = element.style;
|
|
436
|
+
if (element.attributes) Object.assign(data, element.attributes);
|
|
437
|
+
if (frameRef && element.className === FRAME_CLASS) data.ref = frameRef;
|
|
438
|
+
if (stageRef && element.attributes?.["data-godot-stage"] === "true") data.ref = stageRef;
|
|
439
|
+
if (element.sourceNode && slots.node) {
|
|
440
|
+
const node = element.sourceNode;
|
|
441
|
+
const renderedByPath = /* @__PURE__ */ new Map();
|
|
442
|
+
const defaultChildren = (element.children ?? []).map((child) => {
|
|
443
|
+
const vnode = renderElement(child, slots, frameRef, void 0, stageRef);
|
|
444
|
+
if (child.sourceNode) renderedByPath.set(child.sourceNode.path, vnode);
|
|
445
|
+
return vnode;
|
|
446
|
+
});
|
|
447
|
+
const childNodeVnodes = node.children.map((path) => renderedByPath.get(path)).filter((vnode) => vnode !== void 0);
|
|
448
|
+
const slotResult = slots.node({
|
|
449
|
+
node,
|
|
450
|
+
children: childNodeVnodes
|
|
451
|
+
});
|
|
452
|
+
return h(element.tag, data, slotResult ?? defaultChildren);
|
|
453
|
+
}
|
|
454
|
+
let vnode;
|
|
455
|
+
if (element.rawHtml !== void 0) vnode = h(element.tag, {
|
|
456
|
+
...data,
|
|
457
|
+
innerHTML: element.rawHtml
|
|
458
|
+
});
|
|
459
|
+
else if (element.text !== void 0) vnode = h(element.tag, data, [element.text]);
|
|
460
|
+
else vnode = h(element.tag, data, (element.children ?? []).map((child) => renderElement(child, slots, frameRef, cache, stageRef)));
|
|
461
|
+
cache?.set(element, vnode);
|
|
462
|
+
return vnode;
|
|
463
|
+
}
|
|
464
|
+
//#endregion
|
|
465
|
+
export { GodotNodeView, GodotSceneView, mergeGodotNodePropOverrides, overrideGodotNodePropsByName, overrideGodotNodePropsByPath, useGodotSceneModel };
|
|
466
|
+
|
|
467
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["import type {\n GodotNode,\n GodotResourceRefValue,\n GodotSceneState,\n GodotVariant,\n} from \"@godot-scene-web/core\";\nimport {\n type BrowserNativeParentContext,\n buildGraphNodeHtml,\n buildHiddenGraphNode,\n buildNodePresentation,\n buildSceneFragment,\n buildSceneStructure,\n buildStyleElement,\n buildTintDefsElement,\n contentScaleStageStyle,\n DEFAULT_BROWSER_VIEWPORT,\n FRAME_CLASS,\n type GodotHtmlFontFace,\n type GodotHtmlModel,\n type GodotHtmlNode,\n type GodotHtmlRenderOptions,\n type GraphNodeBuildResult,\n observeContentScale,\n type RenderElement,\n type ResolvedContentScale,\n ROOT_PARENT_CONTEXT,\n renderSceneGraphToHtmlModel,\n renderSceneToHtmlModel,\n resolveContentScale,\n STAGE_CLASS,\n stabilizeRenderElements,\n uniqueFontFaces,\n} from \"@godot-scene-web/html\";\nimport {\n createHtmlEffectsHost,\n type GodotHtmlMountOptions,\n type GodotHtmlRuntimeOptions,\n type HtmlEffectsHost,\n} from \"@godot-scene-web/html/runtime\";\nimport type { GodotSceneTreeNode } from \"@godot-scene-web/layout\";\nimport {\n type GodotLayoutOptions,\n resolveGodotSceneTree,\n} from \"@godot-scene-web/layout\";\nimport type {\n GodotResourceStatus,\n SceneGraph,\n SceneGraphNode,\n SceneStructureOptions,\n} from \"@godot-scene-web/scene-graph\";\nimport {\n createSceneGraphNodeMemo,\n deriveSceneGraph,\n type SceneGraphNodeMemo,\n} from \"@godot-scene-web/scene-graph\";\nimport {\n type ComputedRef,\n computed,\n createCommentVNode,\n defineComponent,\n h,\n type InjectionKey,\n inject,\n onBeforeUnmount,\n onMounted,\n type PropType,\n provide,\n type Ref,\n ref,\n type Slots,\n type VNodeChild,\n watch,\n} from \"vue\";\n\nexport type GodotSceneViewOptions = SceneStructureOptions &\n GodotLayoutOptions &\n GodotHtmlRenderOptions &\n GodotHtmlRuntimeOptions & {\n /**\n * Layout pipeline. `\"browser\"` (default): the browser CSS engine resolves\n * layout (containers flex/grid, anchored Controls via CSS insets, text sizes\n * itself) straight from the structural `SceneGraph`, never running the rect\n * cascade. `\"computed\"`: gsw's rect cascade produces absolute px — opt in for\n * the layout-diff/parity oracle or pixel-exact needs.\n */\n layoutMode?: \"browser\" | \"computed\";\n };\n\n// Per-mounted-view counter feeding a distinct tint-filter id prefix. The\n// `filter: url(#id)` references and their `<svg><defs>` share the document, so\n// two live views (couch-coop's per-player renders) — or a dev harness showing a\n// live view beside a static one — would otherwise collide on `godot-tint-N` and\n// resolve each other's filters. Each `useGodotSceneModel` call (one per view)\n// takes the next prefix; the static renderers keep the unprefixed default, so\n// their goldens are unchanged.\nlet tintFilterViewSeq = 0;\nfunction nextTintFilterIdPrefix(): string {\n tintFilterViewSeq += 1;\n return `godot-tint-v${tintFilterViewSeq}-`;\n}\n\nexport interface GodotSubscribableResourceSource {\n subscribe: (listener: () => void) => () => void;\n /**\n * Monotonic settle counters from the backing resolver (see the gsw fetch\n * resolver's `generations()`). When present, `useGodotSceneModel` memoizes the\n * scene-graph derivation on (input identities + the `scenes` counter): graph\n * derivation reads scene documents but never `.tres`/font/texture content, so\n * resource-only settle notifications skip the derive and rebuild only the HTML\n * model. Absent ⇒ derive runs on every recompute (always correct).\n */\n generations?: () => { scenes: number; resources: number };\n}\n\nexport interface GodotSceneModelSource {\n scene?: GodotSceneState;\n model?: GodotHtmlModel;\n options?: GodotSceneViewOptions;\n resolveResource?: (ref: GodotResourceRefValue, node: GodotNode) => unknown;\n resolveResourcePath?: (path: string, node: GodotNode) => unknown;\n overrideNodeProps?: (\n node: GodotNode,\n path: string,\n ) => Record<string, GodotVariant> | undefined;\n overrideNodeType?: (node: GodotNode, path: string) => string | undefined;\n nodeOverrides?: GodotNodePropertyOverrideMap;\n nodeNameOverrides?: GodotNodePropertyOverrideMap;\n mountExternalScene?: (\n ref: GodotResourceRefValue,\n node: GodotNode,\n ) => GodotSceneState | undefined;\n resourceSource?: GodotSubscribableResourceSource;\n}\n\nexport type GodotNodePropertyOverrideMap = Record<\n string,\n Record<string, GodotVariant>\n>;\nexport type GodotNodePropertyOverrideResolver = (\n node: GodotNode,\n path: string,\n) => Record<string, GodotVariant> | undefined;\n\n// The model the COMPONENT renderer consumes: per-node browser-native html\n// (identity-stable for unchanged nodes), the behind/normal child slots (stabilized\n// so a parent's child list keeps identity across leaf-only changes), and the shared\n// font/tint/resource accumulators. Built incrementally by `buildComponentSceneModel`\n// (per-node memo over the post-3.1 identity-stable scene graph), then PROVIDED so\n// each `GodotNodeView` pulls only its own node — a leaf change re-renders just it.\ninterface ComponentSceneModel {\n rootPaths: string[];\n nodesByPath: Map<string, GodotHtmlNode>;\n childrenByPath: Map<string, { behind: string[]; normal: string[] }>;\n fontFaces: GodotHtmlFontFace[];\n tintFilters: Array<{ id: string; markup: string }>;\n resourceStatuses: GodotResourceStatus[];\n viewport: { width: number; height: number };\n contentScale: ResolvedContentScale | null;\n // Only read with `includeBaseCss` (component mode never does); kept so the model\n // satisfies `buildStyleElement`'s input.\n css: string;\n}\n\n// The provided component model. A `ComputedRef` (not the value) so each node\n// component subscribes once and re-pulls its own entry when the model rebuilds —\n// `null` outside component mode.\nconst COMPONENT_MODEL_KEY: InjectionKey<\n ComputedRef<ComponentSceneModel | null>\n> = Symbol(\"godot-component-model\");\n\n// Scaffolding (font `<style>`, tint `<defs>`) carries no node children, so the\n// shared `renderElement` emits it identically with no slots needed.\nconst EMPTY_SLOTS: Slots = {};\n\n// A leaf (or hidden/missing) node's slots — a shared constant so a leaf's `slots`\n// pull stays `===` across rebuilds and never forces a re-render.\nconst NO_CHILD_SLOTS: { behind: string[]; normal: string[] } = {\n behind: [],\n normal: [],\n};\n\n// One scene node, rendered as its OWN Vue component. It PULLS its built html node\n// and child slots from the injected component model (keyed by `path`) rather than\n// receiving them as pushed props — so when the model rebuilds, only the components\n// whose own node/slots changed VALUE re-render (Vue's computed value-equality skips\n// the rest), while a parent never re-renders just because a descendant changed.\n// Vue devtools shows one component per scene node.\nexport const GodotNodeView = defineComponent({\n name: \"GodotNodeView\",\n props: {\n path: {\n type: String,\n required: true,\n },\n },\n setup(props) {\n const model = inject(COMPONENT_MODEL_KEY, null);\n const node = computed<GodotHtmlNode | null>(\n () => model?.value?.nodesByPath.get(props.path) ?? null,\n );\n const slots = computed(\n () => model?.value?.childrenByPath.get(props.path) ?? NO_CHILD_SLOTS,\n );\n return () => {\n const current = node.value;\n if (!current) {\n return null;\n }\n return renderComponentNode(current, slots.value);\n };\n },\n});\n\n// Emit one built node as Vue vnodes — the SAME shape as `buildNodeElement`: the\n// outer element (carrying `data-godot-path` etc.) wraps `[behind children,\n// self-layer, normal children]` in Godot's around-the-node paint order; the\n// self-layer carries the node's paint/text. Children are nested `GodotNodeView`s\n// addressed by path (they pull their own data).\nfunction renderComponentNode(\n node: GodotHtmlNode,\n slots: { behind: string[]; normal: string[] },\n): VNodeChild {\n const shell = buildNodePresentation(node);\n if (shell.kind === \"comment\") return createCommentVNode(shell.text ?? \"\");\n const self = shell.children?.[0];\n const childComponent = (path: string): VNodeChild =>\n h(GodotNodeView, { key: path, path });\n return h(\n shell.tag,\n {\n key: shell.key,\n class: shell.className,\n style: shell.style,\n ...shell.attributes,\n },\n [\n ...slots.behind.map(childComponent),\n ...(self ? [renderElement(self, EMPTY_SLOTS)] : []),\n ...slots.normal.map(childComponent),\n ],\n );\n}\n\n// The scene as a component tree: the shared scaffolding (font `<style>`, tint\n// `<defs>`) emitted from the model's accumulators exactly as the fragment path,\n// then the stage (+ content-scale frame) whose ROOT nodes are `GodotNodeView`\n// components addressed by path. The component model itself is provided in\n// `GodotSceneView` setup; this only emits the shell.\nfunction renderComponentTree(\n model: ComponentSceneModel,\n frameRef: Ref<HTMLElement | null>,\n stageRef: Ref<HTMLElement | null>,\n debug: boolean,\n): VNodeChild[] {\n const out: VNodeChild[] = [];\n const style = buildStyleElement(model, { includeBaseCss: false });\n if (style) {\n out.push(renderElement(style, EMPTY_SLOTS));\n }\n const tintDefs = buildTintDefsElement(model);\n if (tintDefs) {\n out.push(renderElement(tintDefs, EMPTY_SLOTS));\n }\n const stageStyle: Record<string, string> = model.contentScale\n ? { ...contentScaleStageStyle(model.contentScale) }\n : {\n width: `${model.viewport.width}px`,\n height: `${model.viewport.height}px`,\n };\n const stage = h(\n \"div\",\n {\n class: debug ? `${STAGE_CLASS} godot-scene-debug` : STAGE_CLASS,\n \"data-godot-stage\": \"true\",\n ref: stageRef,\n style: stageStyle,\n },\n model.rootPaths.map((path) => h(GodotNodeView, { key: path, path })),\n );\n if (!model.contentScale) {\n out.push(stage);\n return out;\n }\n const background = model.contentScale.background;\n out.push(\n h(\n \"div\",\n {\n class: FRAME_CLASS,\n ref: frameRef,\n style: background ? { background } : {},\n },\n [stage],\n ),\n );\n return out;\n}\n\n// Per-view caches threaded across rebuilds (held in `GodotSceneView` setup): a\n// node entry is reused when its (identity-stable) SceneGraphNode AND parent context\n// are unchanged; a child-slots entry when its children's identities are unchanged.\ninterface ComponentNodeCacheEntry {\n graphNode: SceneGraphNode;\n ctxKey: string;\n result: GraphNodeBuildResult;\n}\ninterface ComponentChildCacheEntry {\n key: string;\n slots: { behind: string[]; normal: string[] };\n}\n\nfunction parentContextKey(ctx: BrowserNativeParentContext): string {\n return `${ctx.parentType ?? \"\"}|${ctx.parentHorizontal ? 1 : 0}`;\n}\n\n// Identity key for a parent's child slots: ONLY the child PATHS + their behind/\n// normal split (newline-joined; paths never contain a newline). A child's PROP\n// change does NOT change this — so the parent's `children` pull stays `===` and the\n// parent never re-renders just because a descendant changed; only a structural\n// change (child added/removed/reordered, or a behind/draw-order shift) does.\nfunction childSlotsKey(slots: { behind: string[]; normal: string[] }): string {\n return `${slots.behind.join(\"\\n\")}\\u0000${slots.normal.join(\"\\n\")}`;\n}\n\n// Build the component model from a `SceneGraph`, reusing per-node html for every\n// node whose SceneGraphNode identity + parent context are unchanged. Because the\n// post-3.1 graph keeps unchanged nodes `===`, a leaf change rebuilds only the\n// changed node(s); every other entry (and every stabilized child-slot object)\n// keeps identity, so the downstream pulls compare equal and skip re-rendering.\n// `tintFilters` is the view's lifetime-stable filter table (append-only: a markup\n// keeps its id), so a reused node's baked `filter: url(#id)` stays valid.\nfunction buildComponentSceneModel(\n graph: SceneGraph,\n options: GodotHtmlRenderOptions,\n viewport: { width: number; height: number },\n contentScale: ResolvedContentScale | null,\n nodeCache: Map<string, ComponentNodeCacheEntry>,\n childCache: Map<string, ComponentChildCacheEntry>,\n tintFilters: Map<string, string>,\n): ComponentSceneModel {\n const structure = buildSceneStructure(graph);\n const nodesByPath = new Map<string, GodotHtmlNode>();\n const fontFaces: GodotHtmlFontFace[] = [];\n const resourceStatuses: GodotResourceStatus[] = [\n ...(graph.resourceStatuses ?? []),\n ];\n const builtPaths = new Set<string>();\n\n const walk = (\n path: string,\n parentLayout: GodotSceneTreeNode | undefined,\n parentPath: string | null,\n parentContext: BrowserNativeParentContext,\n ): void => {\n const graphNode = structure.nodesByPath.get(path);\n if (!graphNode) {\n return;\n }\n builtPaths.add(path);\n if (!graphNode.visible) {\n // Hidden: comment placeholder in its slot; prune the subtree (descendants\n // dropped, fonts/tints unregistered) — matching the batch model.\n nodesByPath.set(path, buildHiddenGraphNode(graphNode, parentPath));\n return;\n }\n const ctxKey = parentContextKey(parentContext);\n let entry = nodeCache.get(path);\n // Force re-derive a node whose previous build still had PENDING resources. The graph node\n // identity is stable across a resource-only settle (the graph is memoized on the SCENES\n // generation), so without this the cached entry pins the node's pending placeholder (e.g. an\n // atlas sprite showing its raw `.tres` doc url) even after the doc loads and the resolver can\n // return the cropped image. Load-time-bounded: once every resource is ready the node has no\n // pending status, so steady state never re-derives.\n const hadPendingResource =\n entry?.result.resourceStatuses.some(\n (status) => status.status === \"pending\",\n ) ?? false;\n if (\n !entry ||\n entry.graphNode !== graphNode ||\n entry.ctxKey !== ctxKey ||\n hadPendingResource\n ) {\n const result = buildGraphNodeHtml(\n graphNode,\n parentLayout,\n parentPath,\n parentContext,\n options,\n tintFilters,\n viewport,\n contentScale,\n );\n entry = { graphNode, ctxKey, result };\n nodeCache.set(path, entry);\n }\n nodesByPath.set(path, entry.result.node);\n fontFaces.push(...entry.result.fontFaces);\n resourceStatuses.push(...entry.result.resourceStatuses);\n const slots = structure.childrenByPath.get(path);\n if (slots) {\n for (const childPath of slots.behind) {\n walk(childPath, entry.result.layout, path, entry.result.childContext);\n }\n for (const childPath of slots.normal) {\n walk(childPath, entry.result.layout, path, entry.result.childContext);\n }\n }\n };\n for (const rootPath of structure.rootPaths) {\n walk(rootPath, undefined, null, ROOT_PARENT_CONTEXT);\n }\n // Evict cache entries for nodes that vanished (e.g. a mount removed).\n for (const key of [...nodeCache.keys()]) {\n if (!builtPaths.has(key)) {\n nodeCache.delete(key);\n }\n }\n\n // Stabilize behind/normal child slots: reuse the previous object when the child\n // PATHS are unchanged, so a parent's `children` pull stays `===` across leaf-only\n // changes (no parent re-render) — only a structural change yields a new object.\n const childrenByPath = new Map<\n string,\n { behind: string[]; normal: string[] }\n >();\n for (const [path, slots] of structure.childrenByPath) {\n if (!builtPaths.has(path) || !structure.nodesByPath.get(path)?.visible) {\n continue;\n }\n const key = childSlotsKey(slots);\n const cached = childCache.get(path);\n if (cached && cached.key === key) {\n childrenByPath.set(path, cached.slots);\n } else {\n childCache.set(path, { key, slots });\n childrenByPath.set(path, slots);\n }\n }\n for (const key of [...childCache.keys()]) {\n if (!childrenByPath.has(key)) {\n childCache.delete(key);\n }\n }\n\n return {\n rootPaths: structure.rootPaths,\n nodesByPath,\n childrenByPath,\n fontFaces: uniqueFontFaces(fontFaces),\n tintFilters: [...tintFilters].map(([markup, id]) => ({ id, markup })),\n resourceStatuses,\n viewport,\n contentScale,\n css: \"\",\n };\n}\n\nexport const GodotSceneView = defineComponent({\n name: \"GodotSceneView\",\n props: {\n scene: {\n type: Object as PropType<GodotSceneState>,\n required: false,\n default: undefined,\n },\n model: {\n type: Object as PropType<GodotHtmlModel>,\n required: false,\n default: undefined,\n },\n options: {\n type: Object as PropType<GodotSceneViewOptions>,\n required: false,\n default: () => ({}),\n },\n resolveResource: {\n type: Function as PropType<\n (ref: GodotResourceRefValue, node: GodotNode) => unknown\n >,\n required: false,\n default: undefined,\n },\n resolveResourcePath: {\n type: Function as PropType<(path: string, node: GodotNode) => unknown>,\n required: false,\n default: undefined,\n },\n overrideNodeProps: {\n type: Function as PropType<\n (\n node: GodotNode,\n path: string,\n ) => Record<string, GodotVariant> | undefined\n >,\n required: false,\n default: undefined,\n },\n overrideNodeType: {\n type: Function as PropType<\n (node: GodotNode, path: string) => string | undefined\n >,\n required: false,\n default: undefined,\n },\n nodeOverrides: {\n type: Object as PropType<GodotNodePropertyOverrideMap>,\n required: false,\n default: () => ({}),\n },\n nodeNameOverrides: {\n type: Object as PropType<GodotNodePropertyOverrideMap>,\n required: false,\n default: () => ({}),\n },\n mountExternalScene: {\n type: Function as PropType<\n (\n ref: GodotResourceRefValue,\n node: GodotNode,\n ) => GodotSceneState | undefined\n >,\n required: false,\n default: undefined,\n },\n resourceSource: {\n type: Object as PropType<GodotSubscribableResourceSource>,\n required: false,\n default: undefined,\n },\n debug: {\n type: Boolean,\n default: false,\n },\n // Render the scene as a recursive `GodotNodeView` COMPONENT tree (one component\n // per node) instead of the flat single-vnode fragment. Same DOM + same model\n // inputs; opt-in while the component path is validated (structural parity) and\n // grown toward fine-grained per-node reactivity. The `#node` slot and the\n // stabilize/vnode-cache fast path are fragment-mode only.\n componentTree: {\n type: Boolean,\n default: false,\n },\n },\n setup(props, { slots }) {\n const sceneModel = useGodotSceneModel(props);\n const fragmentModel = sceneModel.model;\n\n // Component-tree mode: build an incremental, per-node-memoized model from the\n // (identity-stable, post-3.1) scene graph and PROVIDE it as a `ComputedRef` so\n // each `GodotNodeView` pulls only its own node. Caches + the lifetime-stable\n // tint table live here, threaded across rebuilds. `null` outside component mode\n // (the graph is never read, so nothing derives).\n const nodeCache = new Map<string, ComponentNodeCacheEntry>();\n const childCache = new Map<string, ComponentChildCacheEntry>();\n const componentTintFilters = new Map<string, string>();\n const componentModel = computed<ComponentSceneModel | null>(() => {\n if (!props.componentTree) {\n return null;\n }\n // Rebuild on EVERY resolver settle, not only when the graph identity changes. A resource-only\n // settle (a `.tres`/font/texture doc finishing load) keeps the memoized graph `===` (it's keyed\n // on the SCENES generation), so depending on `graph.value` alone would never rebuild and a node\n // that resolved to a PENDING placeholder (an atlas sprite showing its raw `.tres` url) would be\n // pinned forever. `buildComponentSceneModel` re-derives only the nodes whose resources were\n // pending; everything else keeps its cached entry, so steady state stays cheap.\n sceneModel.revision.value;\n const graph = sceneModel.graph.value;\n if (!graph) {\n return null;\n }\n const options = props.options ?? {};\n const htmlOptions = resolveHtmlOptions(\n props,\n sceneModel.tintFilterIdPrefix,\n );\n const viewport = {\n width: options.viewport?.width ?? DEFAULT_BROWSER_VIEWPORT.width,\n height: options.viewport?.height ?? DEFAULT_BROWSER_VIEWPORT.height,\n };\n const contentScale = resolveContentScale(options.contentScale, viewport);\n return buildComponentSceneModel(\n graph,\n htmlOptions,\n viewport,\n contentScale,\n nodeCache,\n childCache,\n componentTintFilters,\n );\n });\n provide(COMPONENT_MODEL_KEY, componentModel);\n\n // The model the render actually consumes (so the scale observer never forces\n // the inactive renderer to build).\n const activeModel = computed<GodotHtmlModel | ComponentSceneModel | null>(\n () => (props.componentTree ? componentModel.value : fragmentModel.value),\n );\n\n // Transform-technique content-scale needs the live frame size; drive\n // `--godot-scale` from a ResizeObserver. The container technique is CSS-only\n // and needs none of this.\n const frame = ref<HTMLElement | null>(null);\n // The live runtimes (WebGL shaders + particles) attach to the mounted stage.\n const stage = ref<HTMLElement | null>(null);\n let disposeScale: (() => void) | null = null;\n const syncScale = (): void => {\n disposeScale?.();\n disposeScale = null;\n const contentScale = activeModel.value?.contentScale;\n if (\n contentScale &&\n contentScale.technique === \"transform\" &&\n frame.value\n ) {\n disposeScale = observeContentScale(frame.value, contentScale.base);\n }\n };\n onMounted(syncScale);\n watch([activeModel, frame], syncScale, { flush: \"post\" });\n onBeforeUnmount(() => {\n disposeScale?.();\n disposeScale = null;\n });\n\n // Live runtime ownership, mirroring the content-scale observer above. Runs\n // post-DOM-update so `stage.value` is the freshly mounted element; reconciles on\n // model changes (the DOM nodes + their `data-godot-particle-specs` may have changed).\n // Both runtimes are no-ops unless their option is set / WebGL2 is available.\n // `externalRuntimes` hosts own persistent runtimes over this DOM — attaching here\n // too would double-bind every node (two stacked canvases per shader node).\n let effectsHost: HtmlEffectsHost | null = null;\n let runtimeRoot: HTMLElement | null = null;\n const disposeRuntimes = (): void => {\n effectsHost?.dispose();\n effectsHost = null;\n runtimeRoot = null;\n };\n const syncRuntimes = (): void => {\n const el = stage.value;\n if (!el) {\n disposeRuntimes();\n return;\n }\n const options = resolveHtmlOptions(props, sceneModel.tintFilterIdPrefix);\n if (runtimeRoot !== el) {\n disposeRuntimes();\n runtimeRoot = el;\n effectsHost = createHtmlEffectsHost(el, options);\n } else {\n effectsHost?.updateOptions(options);\n }\n effectsHost?.reconcile();\n };\n onMounted(syncRuntimes);\n watch([activeModel, stage], syncRuntimes, { flush: \"post\" });\n onBeforeUnmount(() => {\n disposeRuntimes();\n });\n\n // Incremental re-render (fragment path): each model rebuild hands us an all-new\n // element tree, so without help every cycle re-creates every vnode and Vue\n // re-diffs every prop. `stabilizeRenderElements` restores object identity for\n // unchanged subtrees against the previous fragment; the WeakMap then reuses their\n // vnodes wholesale — Vue's normalization clones a reused vnode but keeps its props\n // and children REFERENCES, so per-prop diffing collapses to identity checks. The\n // `#node` slot disables this (slot output can depend on state the element\n // comparison can't see, and it reads `sourceNode`, which stabilization ignores).\n // The component path replaces all of this with per-node component reactivity.\n let previousFragment: RenderElement[] | undefined;\n const vnodeCache = new WeakMap<RenderElement, VNodeChild>();\n\n return () => {\n // Component-tree mode: render each node as its own `GodotNodeView` component\n // pulling from the provided model (Vue-native per-node reactivity + devtools),\n // emitting the same scaffolding + DOM as the fragment path.\n if (props.componentTree) {\n previousFragment = undefined;\n const current = componentModel.value;\n if (!current) {\n return null;\n }\n return renderComponentTree(current, frame, stage, props.debug);\n }\n const current = fragmentModel.value;\n if (!current) {\n previousFragment = undefined;\n return null;\n }\n // Emit the SAME structural fragment the DOM and HTML-string renderers build\n // (`<style>` @font-face, tint-filter defs, stage/frame). The Vue view is now a\n // thin emitter over the shared tree, so it can't drift from the others. Fonts\n // are self-injected (as the live view always has); base CSS stays a host\n // concern, matching the prior behavior. The frame element (when content-\n // scaled) takes the `frame` ref so the ResizeObserver can drive `--godot-scale`.\n let fragment = buildSceneFragment(current, {\n debug: props.debug,\n includeStyle: true,\n includeBaseCss: false,\n });\n if (slots.node) {\n previousFragment = undefined;\n return fragment.map((element) =>\n renderElement(element, slots, frame, undefined, stage),\n );\n }\n fragment = stabilizeRenderElements(previousFragment, fragment);\n previousFragment = fragment;\n return fragment.map((element) =>\n renderElement(element, slots, frame, vnodeCache, stage),\n );\n };\n },\n});\n\nexport function useGodotSceneModel(source: GodotSceneModelSource): {\n model: ComputedRef<GodotHtmlModel | null>;\n // The identity-stable scene graph (post-3.1 node memo). Exposed so the component\n // renderer can build its model from the SAME derivation; `null` in direct-`model`\n // mode (no scene to derive).\n graph: ComputedRef<SceneGraph | null>;\n // The per-view tint-filter id prefix (lifetime-stable) the component build reuses.\n tintFilterIdPrefix: string;\n // Bumps on every resolver settle (scene/resource/font doc finishing load). The component\n // model depends on it so it rebuilds when a resource transitions PENDING → ready even though\n // the memoized scene graph keeps identity (the fragment `model` reads it for the same reason).\n revision: ComputedRef<number>;\n pendingResources: ComputedRef<GodotResourceStatus[]>;\n failedResources: ComputedRef<GodotResourceStatus[]>;\n} {\n const revision = ref(0);\n // Stable for this view's lifetime (re-renders reuse it, so filter ids don't churn).\n const tintFilterIdPrefix = nextTintFilterIdPrefix();\n let unsubscribe: (() => void) | null = null;\n const resubscribe = (\n resourceSource: GodotSubscribableResourceSource | undefined,\n ): void => {\n unsubscribe?.();\n unsubscribe = resourceSource\n ? resourceSource.subscribe(() => {\n revision.value += 1;\n })\n : null;\n };\n watch(() => source.resourceSource, resubscribe, { immediate: true });\n onBeforeUnmount(() => {\n unsubscribe?.();\n unsubscribe = null;\n });\n const deriveMemo: DeriveMemo = { entry: null };\n // Per-view cross-render node cache: when the one-entry graph memo misses (any\n // override changed), this lets `deriveSceneGraph` reuse the SceneGraphNode object of\n // every node whose render fields are unchanged, so a host attack re-derives only the\n // changed nodes (and the html/component builds downstream bail out on the reused\n // identity).\n const nodeMemo = createSceneGraphNodeMemo();\n // The graph is the single shared producer; both the fragment html model and the\n // component model read `graph.value` (so derivation runs at most once per change).\n const graph = computed<SceneGraph | null>(() => {\n revision.value;\n if (source.model) {\n return null;\n }\n return resolveSceneGraph(source, deriveMemo, nodeMemo);\n });\n const model = computed<GodotHtmlModel | null>(() => {\n if (source.model) {\n return source.model;\n }\n const resolved = graph.value;\n if (!resolved) {\n return null;\n }\n const options = source.options ?? {};\n const htmlOptions = resolveHtmlOptions(source, tintFilterIdPrefix);\n // Computed mode (opt-in): run the rect cascade and render absolute px.\n if (options.layoutMode === \"computed\") {\n const tree = resolveGodotSceneTree(\n resolved,\n resolveStructureOptions(source),\n );\n return renderSceneToHtmlModel(tree, htmlOptions);\n }\n // Browser-native (default): the CSS engine resolves geometry from the graph.\n return renderSceneGraphToHtmlModel(resolved, {\n ...htmlOptions,\n viewport: options.viewport,\n });\n });\n const pendingResources = computed(() =>\n (model.value?.resourceStatuses ?? []).filter(\n (resource) => resource.status === \"pending\",\n ),\n );\n const failedResources = computed(() =>\n (model.value?.resourceStatuses ?? []).filter(\n (resource) => resource.status === \"error\",\n ),\n );\n return {\n model,\n graph,\n tintFilterIdPrefix,\n revision: computed(() => revision.value),\n pendingResources,\n failedResources,\n };\n}\n\n// One-entry derive memo, per view. The scene graph is a pure function of the\n// scene document tree and the structural option hooks: it mounts external\n// SCENES but never reads `.tres`/font/texture content. So a cached graph stays\n// valid while (a) every structural input keeps object identity and (b) the\n// resolver's SCENE cache is unchanged — the `scenes` settle counter from\n// `GodotSubscribableResourceSource.generations`. Resource-only settles (the\n// majority of a load's notifications) then skip straight to the HTML rebuild.\ninterface DeriveMemo {\n entry: {\n keys: readonly unknown[];\n scenesGeneration: number;\n graph: SceneGraph;\n } | null;\n}\n\nfunction deriveMemoKeys(source: GodotSceneModelSource): readonly unknown[] {\n return [\n source.scene,\n source.options,\n source.resolveResource,\n source.resolveResourcePath,\n source.overrideNodeProps,\n source.overrideNodeType,\n source.nodeOverrides,\n source.nodeNameOverrides,\n source.mountExternalScene,\n ];\n}\n\n// Structural options drive the single shared producer (`deriveSceneGraph`), which\n// both render modes consume. Merges the resolver/override/mount hooks from `options`\n// and the dedicated props.\nfunction resolveStructureOptions(source: GodotSceneModelSource) {\n const options = source.options ?? {};\n const resolveResource = source.resolveResource ?? options.resolveResource;\n return {\n ...options,\n resolveResource,\n overrideNodeProps: resolveVueNodePropsOverride(\n options.overrideNodeProps,\n source.overrideNodeProps,\n source.nodeNameOverrides ?? {},\n source.nodeOverrides ?? {},\n ),\n overrideNodeType: source.overrideNodeType ?? options.overrideNodeType,\n mountExternalScene: source.mountExternalScene ?? options.mountExternalScene,\n };\n}\n\n// The per-node html options (paint/text resolvers + tint id prefix), shared by the\n// fragment html model and the component build.\nfunction resolveHtmlOptions(\n source: GodotSceneModelSource,\n tintFilterIdPrefix?: string,\n): GodotHtmlMountOptions {\n const options = source.options ?? {};\n return {\n ...options,\n resolveResource: source.resolveResource ?? options.resolveResource,\n resolveResourcePath:\n source.resolveResourcePath ?? options.resolveResourcePath,\n // A caller-supplied prefix (via `options`) wins; otherwise the per-view prefix\n // keeps each mounted view's `<filter>` ids from colliding.\n tintFilterIdPrefix: options.tintFilterIdPrefix ?? tintFilterIdPrefix,\n };\n}\n\n// Derive (or reuse via the one-entry memo) the structural scene graph. The node\n// memo inside `deriveSceneGraph` keeps unchanged nodes identity-stable across\n// derives even when this one-entry memo misses.\nfunction resolveSceneGraph(\n source: GodotSceneModelSource,\n deriveMemo?: DeriveMemo,\n nodeMemo?: SceneGraphNodeMemo,\n): SceneGraph | null {\n if (!source.scene) {\n return null;\n }\n const structureOptions = resolveStructureOptions(source);\n const scenesGeneration = source.resourceSource?.generations?.().scenes;\n const memoKeys = deriveMemoKeys(source);\n const memoEntry = deriveMemo?.entry;\n if (\n memoEntry &&\n scenesGeneration !== undefined &&\n memoEntry.scenesGeneration === scenesGeneration &&\n memoEntry.keys.length === memoKeys.length &&\n memoEntry.keys.every((key, index) => key === memoKeys[index])\n ) {\n return memoEntry.graph;\n }\n const graph = deriveSceneGraph(source.scene, structureOptions, nodeMemo);\n if (deriveMemo && scenesGeneration !== undefined) {\n deriveMemo.entry = { keys: memoKeys, scenesGeneration, graph };\n }\n return graph;\n}\n\nexport function overrideGodotNodePropsByPath(\n overrides: GodotNodePropertyOverrideMap,\n): GodotNodePropertyOverrideResolver {\n return (_node, path) => overrides[path];\n}\n\nexport function overrideGodotNodePropsByName(\n overrides: GodotNodePropertyOverrideMap,\n): GodotNodePropertyOverrideResolver {\n return (node) => overrides[node.name];\n}\n\nexport function mergeGodotNodePropOverrides(\n ...resolvers: Array<GodotNodePropertyOverrideResolver | undefined>\n): GodotNodePropertyOverrideResolver {\n return (node, path) => {\n const merged: Record<string, GodotVariant> = {};\n for (const resolver of resolvers) {\n Object.assign(merged, resolver?.(node, path));\n }\n return Object.keys(merged).length > 0 ? merged : undefined;\n };\n}\n\nfunction resolveVueNodePropsOverride(\n optionsOverride: GodotNodePropertyOverrideResolver | undefined,\n propOverride: GodotNodePropertyOverrideResolver | undefined,\n nameOverrides: GodotNodePropertyOverrideMap,\n pathOverrides: GodotNodePropertyOverrideMap,\n): GodotNodePropertyOverrideResolver {\n return mergeGodotNodePropOverrides(\n optionsOverride,\n propOverride,\n overrideGodotNodePropsByName(nameOverrides),\n overrideGodotNodePropsByPath(pathOverrides),\n );\n}\n\n// Emit a shared `RenderElement` tree as Vue vnodes. The structure (node element,\n// self-layer, behind/self/normal order, rich-text innerHTML, scaffolding) comes\n// from the shared builder; this function only translates it to `h()` calls and\n// layers on the Vue-specific concerns: per-node `key`, the `#node` full-replace\n// slot, and the content-scale frame `ref`.\n//\n// With a `cache` (stabilized fragments only — see GodotSceneView), an element\n// whose object identity survived stabilization returns its previous vnode\n// untouched. Reusing a mounted vnode at the same position is the static-hoist\n// pattern: Vue clones it during child normalization, and the clone's shared\n// props/children references short-circuit the patch. Never combined with the\n// `#node` slot (the caller guarantees `slots.node` is absent when caching).\nfunction renderElement(\n element: RenderElement,\n slots: Slots,\n frameRef?: Ref<HTMLElement | null>,\n cache?: WeakMap<RenderElement, VNodeChild>,\n stageRef?: Ref<HTMLElement | null>,\n): VNodeChild {\n const cached = cache?.get(element);\n if (cached !== undefined) {\n return cached;\n }\n if (element.kind === \"comment\") {\n // Hidden-placeholder node: emit the same comment anchor Vue produces for\n // `v-if: false`. Carries no `sourceNode`, so the `#node` slot never sees it.\n const comment = createCommentVNode(element.text ?? \"\");\n cache?.set(element, comment);\n return comment;\n }\n const data: Record<string, unknown> = {};\n if (element.key !== undefined) {\n data.key = element.key;\n }\n if (element.className !== undefined) {\n data.class = element.className;\n }\n if (element.style !== undefined) {\n data.style = element.style;\n }\n if (element.attributes) {\n Object.assign(data, element.attributes);\n }\n if (frameRef && element.className === FRAME_CLASS) {\n data.ref = frameRef;\n }\n // The live runtimes (WebGL shaders, particles) attach to the mounted stage element.\n if (stageRef && element.attributes?.[\"data-godot-stage\"] === \"true\") {\n data.ref = stageRef;\n }\n\n // Per-node element: expose the `#node` full-replace slot (`slot ?? default`).\n // The slot receives the model node and its rendered CHILD-NODE vnodes in source\n // order (excluding the self-layer), preserving the prior public contract.\n if (element.sourceNode && slots.node) {\n const node = element.sourceNode;\n const renderedByPath = new Map<string, VNodeChild>();\n const defaultChildren = (element.children ?? []).map((child) => {\n const vnode = renderElement(child, slots, frameRef, undefined, stageRef);\n if (child.sourceNode) {\n renderedByPath.set(child.sourceNode.path, vnode);\n }\n return vnode;\n });\n const childNodeVnodes = node.children\n .map((path) => renderedByPath.get(path))\n .filter((vnode): vnode is VNodeChild => vnode !== undefined);\n const slotResult = slots.node({ node, children: childNodeVnodes });\n return h(element.tag, data, slotResult ?? defaultChildren);\n }\n\n let vnode: VNodeChild;\n if (element.rawHtml !== undefined) {\n vnode = h(element.tag, { ...data, innerHTML: element.rawHtml });\n } else if (element.text !== undefined) {\n vnode = h(element.tag, data, [element.text]);\n } else {\n vnode = h(\n element.tag,\n data,\n (element.children ?? []).map((child) =>\n renderElement(child, slots, frameRef, cache, stageRef),\n ),\n );\n }\n cache?.set(element, vnode);\n return vnode;\n}\n"],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@godot-scene-web/vue",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"description": "Vue 3 components for godot-scene-web HTML scenes.",
|
|
7
|
+
"publishConfig": {
|
|
8
|
+
"access": "public",
|
|
9
|
+
"registry": "https://registry.npmjs.org/"
|
|
10
|
+
},
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "git+https://github.com/tfoxy/godot-scene-web.git"
|
|
14
|
+
},
|
|
15
|
+
"homepage": "https://github.com/tfoxy/godot-scene-web#readme",
|
|
16
|
+
"bugs": {
|
|
17
|
+
"url": "https://github.com/tfoxy/godot-scene-web/issues"
|
|
18
|
+
},
|
|
19
|
+
"exports": {
|
|
20
|
+
".": {
|
|
21
|
+
"development": "./src/index.ts",
|
|
22
|
+
"types": "./dist/index.d.ts",
|
|
23
|
+
"import": "./dist/index.js"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"main": "./dist/index.js",
|
|
27
|
+
"types": "./dist/index.d.ts",
|
|
28
|
+
"files": [
|
|
29
|
+
"dist",
|
|
30
|
+
"LICENSE"
|
|
31
|
+
],
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"@godot-scene-web/core": "0.1.0",
|
|
34
|
+
"@godot-scene-web/scene-graph": "0.1.0",
|
|
35
|
+
"@godot-scene-web/html": "0.1.0",
|
|
36
|
+
"@godot-scene-web/layout": "0.1.0"
|
|
37
|
+
},
|
|
38
|
+
"peerDependencies": {
|
|
39
|
+
"vue": "^3.5.0"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"vue": "^3.5.34"
|
|
43
|
+
},
|
|
44
|
+
"scripts": {
|
|
45
|
+
"build": "tsdown",
|
|
46
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
47
|
+
"test": "cd ../.. && vitest run --config vitest.config.ts packages/vue/test"
|
|
48
|
+
}
|
|
49
|
+
}
|