@aethervtt/shard-scene 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +3 -0
- package/dist/components.d.ts +47 -0
- package/dist/components.js +93 -0
- package/dist/format.d.ts +53 -0
- package/dist/format.js +6 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +121 -0
- package/dist/instances.d.ts +120 -0
- package/dist/instances.js +1295 -0
- package/dist/prefab-file.d.ts +40 -0
- package/dist/prefab-file.js +446 -0
- package/dist/procedural.d.ts +36 -0
- package/dist/procedural.js +84 -0
- package/dist/scene.d.ts +93 -0
- package/dist/scene.js +736 -0
- package/dist/schema.d.ts +11 -0
- package/dist/schema.js +122 -0
- package/package.json +38 -0
|
@@ -0,0 +1,1295 @@
|
|
|
1
|
+
import { assetServer } from "@aethervtt/shard-assets";
|
|
2
|
+
import {
|
|
3
|
+
ChildOf,
|
|
4
|
+
Children,
|
|
5
|
+
Commands,
|
|
6
|
+
defineSystem,
|
|
7
|
+
isPlainObject,
|
|
8
|
+
onAdd,
|
|
9
|
+
onRemove,
|
|
10
|
+
onSet,
|
|
11
|
+
PreUpdate,
|
|
12
|
+
pointer,
|
|
13
|
+
ShardError,
|
|
14
|
+
World
|
|
15
|
+
} from "@aethervtt/shard-core";
|
|
16
|
+
import { LogResource } from "@aethervtt/shard-runtime";
|
|
17
|
+
import { Transform } from "@aethervtt/shard-transform";
|
|
18
|
+
import * as componentsModule from "./components.js";
|
|
19
|
+
import {
|
|
20
|
+
InstancePart,
|
|
21
|
+
PrefabAssets,
|
|
22
|
+
PrefabInstance,
|
|
23
|
+
SceneAssets,
|
|
24
|
+
SceneIndex,
|
|
25
|
+
SceneInstance,
|
|
26
|
+
SceneMember
|
|
27
|
+
} from "./components.js";
|
|
28
|
+
import * as prefabFileModule from "./prefab-file.js";
|
|
29
|
+
import {
|
|
30
|
+
ALIASES,
|
|
31
|
+
componentDef,
|
|
32
|
+
equal,
|
|
33
|
+
expandAliases,
|
|
34
|
+
rebase,
|
|
35
|
+
resolveAssets,
|
|
36
|
+
serializeComponents
|
|
37
|
+
} from "./scene.js";
|
|
38
|
+
let nestedCounter = 0;
|
|
39
|
+
function sourceFile(world, entry) {
|
|
40
|
+
if (entry.type === "Prefab") return world.tryResource(PrefabAssets)?.byGuid(entry.guid);
|
|
41
|
+
if (entry.type === "Scene") return world.tryResource(SceneAssets)?.byGuid(entry.guid);
|
|
42
|
+
return void 0;
|
|
43
|
+
}
|
|
44
|
+
function expandComponents(components) {
|
|
45
|
+
const out = {};
|
|
46
|
+
if (!isPlainObject(components)) return out;
|
|
47
|
+
for (const [name, raw] of Object.entries(components)) {
|
|
48
|
+
if (isPlainObject(raw))
|
|
49
|
+
out[name] = expandAliases(name, raw, "", []);
|
|
50
|
+
}
|
|
51
|
+
return out;
|
|
52
|
+
}
|
|
53
|
+
function instanceDefOf(components) {
|
|
54
|
+
if (components[PrefabInstance.name]) return PrefabInstance;
|
|
55
|
+
if (components[SceneInstance.name]) return SceneInstance;
|
|
56
|
+
return void 0;
|
|
57
|
+
}
|
|
58
|
+
function handleOf(json, field) {
|
|
59
|
+
const ref = json?.[field];
|
|
60
|
+
return isPlainObject(ref) ? ref : void 0;
|
|
61
|
+
}
|
|
62
|
+
function flattenSource(world, entry, stack) {
|
|
63
|
+
if (stack.includes(entry.guid)) {
|
|
64
|
+
throw new ShardError(
|
|
65
|
+
"prefab/cycle",
|
|
66
|
+
`${entry.path} contains itself (${[...stack.map((g) => assetServer(world).entry(g)?.path ?? g), entry.path].join(" \u2192 ")})`,
|
|
67
|
+
{
|
|
68
|
+
path: entry.path,
|
|
69
|
+
hint: "A prefab can\u2019t contain or extend itself, directly or through others."
|
|
70
|
+
}
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
const file = sourceFile(world, entry);
|
|
74
|
+
const flat = {
|
|
75
|
+
root: void 0,
|
|
76
|
+
rootName: void 0,
|
|
77
|
+
nodes: [],
|
|
78
|
+
index: /* @__PURE__ */ new Map(),
|
|
79
|
+
removed: /* @__PURE__ */ new Set(),
|
|
80
|
+
touched: /* @__PURE__ */ new Set(),
|
|
81
|
+
assets: { ...file?.assets ?? {} },
|
|
82
|
+
deps: /* @__PURE__ */ new Map([[entry.guid, entry.version]]),
|
|
83
|
+
missing: /* @__PURE__ */ new Set(),
|
|
84
|
+
warnings: []
|
|
85
|
+
};
|
|
86
|
+
if (!file) return flat;
|
|
87
|
+
let children;
|
|
88
|
+
if (entry.type === "Prefab") {
|
|
89
|
+
const root = file.root;
|
|
90
|
+
flat.root = expandComponents(root?.components);
|
|
91
|
+
flat.rootName = root?.name;
|
|
92
|
+
children = root?.children ?? [];
|
|
93
|
+
} else {
|
|
94
|
+
children = file.entities ?? [];
|
|
95
|
+
}
|
|
96
|
+
const add = (list, prefix, parent) => {
|
|
97
|
+
for (const e of list) {
|
|
98
|
+
const path = prefix === "" ? e.name : `${prefix}/${e.name}`;
|
|
99
|
+
const i = flat.nodes.length;
|
|
100
|
+
flat.nodes.push({
|
|
101
|
+
name: e.name,
|
|
102
|
+
path,
|
|
103
|
+
parent,
|
|
104
|
+
components: expandComponents(e.components),
|
|
105
|
+
nested: false
|
|
106
|
+
});
|
|
107
|
+
flat.index.set(path, i);
|
|
108
|
+
if (Array.isArray(e.children)) add(e.children, path, i);
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
add(children, "", -1);
|
|
112
|
+
const own = flat.nodes.length;
|
|
113
|
+
const server = assetServer(world);
|
|
114
|
+
const next = [...stack, entry.guid];
|
|
115
|
+
for (let i = flat.root ? -1 : 0; i < own; i++) {
|
|
116
|
+
const components = i === -1 ? flat.root : flat.nodes[i].components;
|
|
117
|
+
const def = instanceDefOf(components);
|
|
118
|
+
if (!def) continue;
|
|
119
|
+
const instance = components[def.name];
|
|
120
|
+
const ref = handleOf(instance, def === PrefabInstance ? "prefab" : "scene");
|
|
121
|
+
const nested = ref && server.entry(ref);
|
|
122
|
+
if (!nested) continue;
|
|
123
|
+
if (nested.state === "unloaded" || nested.state === "loading") {
|
|
124
|
+
server.request(nested.guid);
|
|
125
|
+
flat.missing.add(nested.guid);
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
if (nested.state !== "loaded") continue;
|
|
129
|
+
inline(flat, i, flattenSource(world, nested, next));
|
|
130
|
+
if (i === -1) delete components[def.name];
|
|
131
|
+
const overrides = instance.overrides;
|
|
132
|
+
const at = i === -1 ? "" : flat.nodes[i].path;
|
|
133
|
+
if (isPlainObject(overrides)) {
|
|
134
|
+
applyOverrides(flat, overrides, at, (key, error) => {
|
|
135
|
+
flat.warnings.push(stale(`${entry.path}: ${at || flat.rootName}`, key, error));
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return flat;
|
|
140
|
+
}
|
|
141
|
+
function stale(where, key, error) {
|
|
142
|
+
return new ShardError("prefab/stale-override", `${where}: override "${key}": ${error.message}`, {
|
|
143
|
+
path: error.path,
|
|
144
|
+
hint: "The prefab changed (a renamed or removed entity?). The override is kept in the file; fix or delete it."
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
function inline(flat, at, nested) {
|
|
148
|
+
const hostPath = at === -1 ? "" : flat.nodes[at].path;
|
|
149
|
+
const hostComponents = at === -1 ? flat.root : flat.nodes[at].components;
|
|
150
|
+
const prefix = `n${nestedCounter++}~`;
|
|
151
|
+
for (const [name, asset] of Object.entries(nested.assets)) flat.assets[`${prefix}${name}`] = asset;
|
|
152
|
+
const move = (components) => {
|
|
153
|
+
const out = {};
|
|
154
|
+
for (const [name, json] of Object.entries(components))
|
|
155
|
+
out[name] = rebaseJson(name, json, hostPath, prefix);
|
|
156
|
+
return out;
|
|
157
|
+
};
|
|
158
|
+
if (nested.root) {
|
|
159
|
+
const root = move(nested.root);
|
|
160
|
+
for (const [name, json] of Object.entries(root)) {
|
|
161
|
+
hostComponents[name] = { ...json, ...hostComponents[name] ?? {} };
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
const offset = flat.nodes.length;
|
|
165
|
+
for (let k = 0; k < nested.nodes.length; k++) {
|
|
166
|
+
const n = nested.nodes[k];
|
|
167
|
+
const path = hostPath === "" ? n.path : `${hostPath}/${n.path}`;
|
|
168
|
+
flat.nodes.push({
|
|
169
|
+
name: n.name,
|
|
170
|
+
path,
|
|
171
|
+
parent: n.parent === -1 ? at : n.parent + offset,
|
|
172
|
+
components: move(n.components),
|
|
173
|
+
nested: n.nested
|
|
174
|
+
});
|
|
175
|
+
flat.index.set(path, offset + k);
|
|
176
|
+
if (nested.removed.has(k)) flat.removed.add(offset + k);
|
|
177
|
+
}
|
|
178
|
+
if (at !== -1) flat.nodes[at].nested = true;
|
|
179
|
+
for (const [g, v] of nested.deps) flat.deps.set(g, v);
|
|
180
|
+
for (const g of nested.missing) flat.missing.add(g);
|
|
181
|
+
flat.warnings.push(...nested.warnings);
|
|
182
|
+
}
|
|
183
|
+
function rebaseJson(name, json, base, prefix) {
|
|
184
|
+
const def = componentDef(name);
|
|
185
|
+
const renameAssets = (value) => {
|
|
186
|
+
if (Array.isArray(value)) return value.map(renameAssets);
|
|
187
|
+
if (!isPlainObject(value)) return value;
|
|
188
|
+
const out2 = {};
|
|
189
|
+
for (const [k, v] of Object.entries(value)) {
|
|
190
|
+
out2[k] = k === "path" && typeof v === "string" && v.startsWith("#") ? `#${prefix}${v.slice(1)}` : renameAssets(v);
|
|
191
|
+
}
|
|
192
|
+
return out2;
|
|
193
|
+
};
|
|
194
|
+
const out = renameAssets(json);
|
|
195
|
+
if (def) {
|
|
196
|
+
for (const { name: field, field: type } of def.layout) {
|
|
197
|
+
const v = out[field];
|
|
198
|
+
if (type.kind === "entity" && typeof v === "string" && base !== "")
|
|
199
|
+
out[field] = v === "." ? base : `${base}/${v}`;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return out;
|
|
203
|
+
}
|
|
204
|
+
function fork(flat) {
|
|
205
|
+
return {
|
|
206
|
+
...flat,
|
|
207
|
+
nodes: flat.nodes.slice(),
|
|
208
|
+
removed: new Set(flat.removed),
|
|
209
|
+
touched: /* @__PURE__ */ new Set(),
|
|
210
|
+
warnings: []
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
function touch(flat, i) {
|
|
214
|
+
if (flat.touched.has(i)) return flat.nodes[i];
|
|
215
|
+
const node = { ...flat.nodes[i], components: { ...flat.nodes[i].components } };
|
|
216
|
+
flat.nodes[i] = node;
|
|
217
|
+
flat.touched.add(i);
|
|
218
|
+
return node;
|
|
219
|
+
}
|
|
220
|
+
function isRemoved(flat, i) {
|
|
221
|
+
for (let k = i; k !== -1; k = flat.nodes[k].parent) if (flat.removed.has(k)) return true;
|
|
222
|
+
return false;
|
|
223
|
+
}
|
|
224
|
+
function splitComponentKey(flat, key) {
|
|
225
|
+
const parts = key.split("/");
|
|
226
|
+
if (parts.length < 3) return void 0;
|
|
227
|
+
const component = parts.slice(-2).join("/");
|
|
228
|
+
const i = flat.index.get(parts.slice(0, -2).join("/"));
|
|
229
|
+
return i === void 0 || !componentDef(component) ? void 0 : { i, component };
|
|
230
|
+
}
|
|
231
|
+
function applyOverrides(flat, overrides, prefix, report, validate) {
|
|
232
|
+
for (const [key, value] of Object.entries(overrides)) {
|
|
233
|
+
const at = pointer("", key);
|
|
234
|
+
const full = prefix === "" ? key : `${prefix}/${key}`;
|
|
235
|
+
if (value === null) {
|
|
236
|
+
const i2 = flat.index.get(full);
|
|
237
|
+
if (i2 !== void 0) {
|
|
238
|
+
flat.removed.add(i2);
|
|
239
|
+
continue;
|
|
240
|
+
}
|
|
241
|
+
const cut = splitComponentKey(flat, full);
|
|
242
|
+
if (cut) delete touch(flat, cut.i).components[cut.component];
|
|
243
|
+
else report(key, unknownPath(key, at, flat));
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
if (!isPlainObject(value)) {
|
|
247
|
+
report(
|
|
248
|
+
key,
|
|
249
|
+
new ShardError("schema/type-mismatch", `Override "${key}" must be an object or null`, {
|
|
250
|
+
path: at,
|
|
251
|
+
hint: 'Give { "<component>": { fields } } to change an entity, or null to remove it.'
|
|
252
|
+
})
|
|
253
|
+
);
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
const i = flat.index.get(full);
|
|
257
|
+
if (i === void 0) {
|
|
258
|
+
report(key, unknownPath(key, at, flat));
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
for (const [name, patch] of Object.entries(value)) {
|
|
262
|
+
const where = pointer(at, name);
|
|
263
|
+
const def = componentDef(name);
|
|
264
|
+
if (!def) {
|
|
265
|
+
report(
|
|
266
|
+
key,
|
|
267
|
+
new ShardError("scene/unknown-component", `Unknown component "${name}"`, {
|
|
268
|
+
path: where,
|
|
269
|
+
hint: 'Use a registered name like "core/Transform" or "render/Camera3d" (see .agents/components.md).'
|
|
270
|
+
})
|
|
271
|
+
);
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
if (!def.serializable || def === ChildOf) {
|
|
275
|
+
report(
|
|
276
|
+
key,
|
|
277
|
+
new ShardError("scene/derived-component", `"${name}" can't be overridden`, {
|
|
278
|
+
path: where
|
|
279
|
+
})
|
|
280
|
+
);
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
if (patch === null) {
|
|
284
|
+
delete touch(flat, i).components[name];
|
|
285
|
+
continue;
|
|
286
|
+
}
|
|
287
|
+
if (!isPlainObject(patch)) {
|
|
288
|
+
report(
|
|
289
|
+
key,
|
|
290
|
+
new ShardError("schema/type-mismatch", `"${name}" must be an object or null`, {
|
|
291
|
+
path: where
|
|
292
|
+
})
|
|
293
|
+
);
|
|
294
|
+
continue;
|
|
295
|
+
}
|
|
296
|
+
const errors = [];
|
|
297
|
+
const expanded = expandAliases(name, patch, where, errors);
|
|
298
|
+
const merged = { ...flat.nodes[i].components[name] ?? {}, ...expanded };
|
|
299
|
+
if (validate) for (const e of validate(def, merged)) errors.push(rebase(e, where));
|
|
300
|
+
if (errors.length > 0) {
|
|
301
|
+
for (const e of errors) report(key, e);
|
|
302
|
+
continue;
|
|
303
|
+
}
|
|
304
|
+
touch(flat, i).components[name] = merged;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
function unknownPath(key, at, flat) {
|
|
309
|
+
const names = flat.nodes.filter((n) => n.parent === -1).map((n) => n.name);
|
|
310
|
+
return new ShardError("prefab/unknown-path", `No generated entity at "${key}"`, {
|
|
311
|
+
path: at,
|
|
312
|
+
hint: names.length ? `Paths are relative to the instance, e.g. "${names[0]}"${flat.nodes.find((n) => n.parent !== -1) ? ` or "${flat.nodes.find((n) => n.parent !== -1).path}"` : ""}. End a path with a component name to remove that component.` : "This instance generates no entities."
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
function compileNode(world, flat, components, path, parent, ctx) {
|
|
316
|
+
const defs = [];
|
|
317
|
+
const values = [];
|
|
318
|
+
const refs = [];
|
|
319
|
+
for (const [name, json] of Object.entries(components)) {
|
|
320
|
+
const def = componentDef(name);
|
|
321
|
+
if (!def?.serializable || def === ChildOf) continue;
|
|
322
|
+
let value;
|
|
323
|
+
try {
|
|
324
|
+
value = def.deserialize(json, ctx);
|
|
325
|
+
} catch (err) {
|
|
326
|
+
world.tryResource(LogResource)?.error(
|
|
327
|
+
new ShardError(
|
|
328
|
+
"prefab/invalid-component",
|
|
329
|
+
`${path || "(root)"} ${name}: ${err.message}`,
|
|
330
|
+
{
|
|
331
|
+
hint: err.hint
|
|
332
|
+
}
|
|
333
|
+
)
|
|
334
|
+
);
|
|
335
|
+
continue;
|
|
336
|
+
}
|
|
337
|
+
for (const { name: field, field: type } of def.layout) {
|
|
338
|
+
const v = json[field];
|
|
339
|
+
if (type.kind !== "entity" || typeof v !== "string") continue;
|
|
340
|
+
refs.push({ component: defs.length, field, target: v === "." ? -1 : flat.index.get(v) });
|
|
341
|
+
value[field] = null;
|
|
342
|
+
}
|
|
343
|
+
defs.push(def);
|
|
344
|
+
values.push(value);
|
|
345
|
+
}
|
|
346
|
+
const childOf = { parent: -1 };
|
|
347
|
+
const part = { instance: -1, path };
|
|
348
|
+
const inits = defs.map((d, k) => [d, values[k]]);
|
|
349
|
+
inits.push([ChildOf, childOf], [InstancePart, part]);
|
|
350
|
+
return { path, parent, defs, values, refs, inits, childOf, part };
|
|
351
|
+
}
|
|
352
|
+
function compileContext(world, flat, id, requested) {
|
|
353
|
+
return {
|
|
354
|
+
resolveAsset: resolveAssets(world, flat, id, "create", requested),
|
|
355
|
+
resolveEntity: (path) => path === "." || flat.index.has(path) ? 0 : void 0
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
function compileTemplate(world, entry) {
|
|
359
|
+
const flat = flattenSource(world, entry, []);
|
|
360
|
+
const requested = /* @__PURE__ */ new Set();
|
|
361
|
+
const ctx = compileContext(world, flat, entry.path, requested);
|
|
362
|
+
const template = {
|
|
363
|
+
entry,
|
|
364
|
+
flat,
|
|
365
|
+
root: flat.root ? compileNode(world, flat, flat.root, "", -1, ctx) : void 0,
|
|
366
|
+
entities: [],
|
|
367
|
+
requested,
|
|
368
|
+
ctx,
|
|
369
|
+
canonical: void 0
|
|
370
|
+
};
|
|
371
|
+
for (let i = 0; i < flat.nodes.length; i++) {
|
|
372
|
+
const n = flat.nodes[i];
|
|
373
|
+
template.entities.push(
|
|
374
|
+
isRemoved(flat, i) ? void 0 : compileNode(world, flat, n.components, n.path, n.parent, ctx)
|
|
375
|
+
);
|
|
376
|
+
}
|
|
377
|
+
const log = world.tryResource(LogResource);
|
|
378
|
+
for (const w of flat.warnings) warn(log, w);
|
|
379
|
+
return template;
|
|
380
|
+
}
|
|
381
|
+
function warn(log, error) {
|
|
382
|
+
log?.log("warn", error.message, { code: error.code, path: error.path, hint: error.hint });
|
|
383
|
+
}
|
|
384
|
+
function planFor(world, template, overrides, report) {
|
|
385
|
+
if (!overrides || Object.keys(overrides).length === 0) return template;
|
|
386
|
+
const flat = fork(template.flat);
|
|
387
|
+
const check = checkContext(world, flat, template.entry.path);
|
|
388
|
+
applyOverrides(flat, overrides, "", report, (def, json) => def.validate(json, check));
|
|
389
|
+
const ctx = template.ctx;
|
|
390
|
+
const entities = [];
|
|
391
|
+
for (let i = 0; i < flat.nodes.length; i++) {
|
|
392
|
+
if (isRemoved(flat, i)) entities.push(void 0);
|
|
393
|
+
else if (flat.touched.has(i)) {
|
|
394
|
+
const n = flat.nodes[i];
|
|
395
|
+
entities.push(compileNode(world, flat, n.components, n.path, n.parent, ctx));
|
|
396
|
+
} else entities.push(template.entities[i]);
|
|
397
|
+
}
|
|
398
|
+
return { flat, root: template.root, entities };
|
|
399
|
+
}
|
|
400
|
+
function checkContext(world, flat, id) {
|
|
401
|
+
return {
|
|
402
|
+
resolveAsset: resolveAssets(world, flat, id, "check"),
|
|
403
|
+
resolveEntity: (path) => path === "." || flat.index.has(path) ? 0 : void 0
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
function handleEntry(world, ref) {
|
|
407
|
+
return ref ? assetServer(world).entry(ref) : void 0;
|
|
408
|
+
}
|
|
409
|
+
const sceneInstanceKind = {
|
|
410
|
+
def: SceneInstance,
|
|
411
|
+
source: (world, value) => handleEntry(world, value.scene)
|
|
412
|
+
};
|
|
413
|
+
const prefabInstanceKind = {
|
|
414
|
+
def: PrefabInstance,
|
|
415
|
+
source: (world, value) => handleEntry(world, value.prefab)
|
|
416
|
+
};
|
|
417
|
+
const kinds = [sceneInstanceKind, prefabInstanceKind];
|
|
418
|
+
function defineInstanceKind(kind) {
|
|
419
|
+
const i = kinds.findIndex((k) => k.def.name === kind.def.name);
|
|
420
|
+
if (i === -1) kinds.push(kind);
|
|
421
|
+
else kinds[i] = kind;
|
|
422
|
+
return kind;
|
|
423
|
+
}
|
|
424
|
+
function instanceKinds() {
|
|
425
|
+
return kinds;
|
|
426
|
+
}
|
|
427
|
+
function instanceKindOf(def) {
|
|
428
|
+
return kinds.find((k) => k.def === def);
|
|
429
|
+
}
|
|
430
|
+
const perWorld = /* @__PURE__ */ new WeakMap();
|
|
431
|
+
function stateOf(world) {
|
|
432
|
+
let s = perWorld.get(world);
|
|
433
|
+
if (!s) {
|
|
434
|
+
const off = assetServer(world).onEvent((e) => {
|
|
435
|
+
if (e.kind !== "loaded" && e.kind !== "modified") return;
|
|
436
|
+
const type = assetServer(world).entry(e.guid)?.type;
|
|
437
|
+
if (type === "Scene" || type === "Prefab") updateInstances(world);
|
|
438
|
+
});
|
|
439
|
+
s = { states: /* @__PURE__ */ new Map(), templates: /* @__PURE__ */ new Map(), off, dirty: true, observed: /* @__PURE__ */ new Set() };
|
|
440
|
+
perWorld.set(world, s);
|
|
441
|
+
}
|
|
442
|
+
if (s.observed.size !== kinds.length) {
|
|
443
|
+
const state = s;
|
|
444
|
+
const mark = () => {
|
|
445
|
+
state.dirty = true;
|
|
446
|
+
};
|
|
447
|
+
for (const { def } of kinds) {
|
|
448
|
+
if (state.observed.has(def)) continue;
|
|
449
|
+
state.observed.add(def);
|
|
450
|
+
world.observe(onAdd(def), mark);
|
|
451
|
+
world.observe(onSet(def), mark);
|
|
452
|
+
world.observe(onRemove(def), mark);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
return s;
|
|
456
|
+
}
|
|
457
|
+
function hookInstances(world) {
|
|
458
|
+
stateOf(world);
|
|
459
|
+
}
|
|
460
|
+
function releaseSceneHooks(world) {
|
|
461
|
+
perWorld.get(world)?.off();
|
|
462
|
+
perWorld.delete(world);
|
|
463
|
+
}
|
|
464
|
+
function isCurrent(world, template) {
|
|
465
|
+
const server = assetServer(world);
|
|
466
|
+
for (const [guid, version] of template.flat.deps) {
|
|
467
|
+
const e = server.entry(guid);
|
|
468
|
+
if (!e || e.version !== version) return false;
|
|
469
|
+
}
|
|
470
|
+
return true;
|
|
471
|
+
}
|
|
472
|
+
function templateFor(world, entry) {
|
|
473
|
+
const s = stateOf(world);
|
|
474
|
+
const cached = s.templates.get(entry.guid);
|
|
475
|
+
if (cached && isCurrent(world, cached) && cached.flat.missing.size === 0) return cached;
|
|
476
|
+
const template = compileTemplate(world, entry);
|
|
477
|
+
s.templates.set(entry.guid, template);
|
|
478
|
+
return template;
|
|
479
|
+
}
|
|
480
|
+
function resolveRefs(t, ids, instance) {
|
|
481
|
+
for (const r of t.refs) {
|
|
482
|
+
const value = { ...t.values[r.component] };
|
|
483
|
+
value[r.field] = r.target === -1 ? instance : (ids[r.target] ?? -1) < 0 ? null : ids[r.target];
|
|
484
|
+
t.inits[r.component][1] = value;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
function reserve(world, plan) {
|
|
488
|
+
const ids = new Array(plan.entities.length);
|
|
489
|
+
for (let i = 0; i < plan.entities.length; i++)
|
|
490
|
+
ids[i] = plan.entities[i] ? world.reserveEntity() : -1;
|
|
491
|
+
return ids;
|
|
492
|
+
}
|
|
493
|
+
function spawnReserved(world, plan, instance, ids, member, paths, mark) {
|
|
494
|
+
const scene = member ? world.tryResource(SceneIndex)?.get(member.scene) : void 0;
|
|
495
|
+
for (let i = 0; i < plan.entities.length; i++) {
|
|
496
|
+
const t = plan.entities[i];
|
|
497
|
+
const id = ids[i];
|
|
498
|
+
if (!t || id < 0) continue;
|
|
499
|
+
const parent = t.parent === -1 ? instance : ids[t.parent];
|
|
500
|
+
if (parent < 0) continue;
|
|
501
|
+
t.childOf.parent = parent;
|
|
502
|
+
t.part.instance = instance;
|
|
503
|
+
if (t.refs.length > 0) resolveRefs(t, ids, instance);
|
|
504
|
+
if (member) {
|
|
505
|
+
const path = `${member.path}/${t.path}`;
|
|
506
|
+
const inits = [
|
|
507
|
+
...t.inits,
|
|
508
|
+
[SceneMember, { scene: member.scene, path }]
|
|
509
|
+
];
|
|
510
|
+
if (mark) inits.push([mark, {}]);
|
|
511
|
+
world.spawnReserved(id, inits);
|
|
512
|
+
scene?.entities.set(path, id);
|
|
513
|
+
paths?.push(path);
|
|
514
|
+
} else {
|
|
515
|
+
world.spawnReserved(id, mark ? [...t.inits, [mark, {}]] : t.inits);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
function despawnGenerated(world, state, keepUnder) {
|
|
520
|
+
if (keepUnder !== void 0 && world.isAlive(keepUnder)) {
|
|
521
|
+
const generated = new Set(state.ids);
|
|
522
|
+
const foreign = [];
|
|
523
|
+
for (const id of state.ids) {
|
|
524
|
+
if (id < 0 || !world.isAlive(id)) continue;
|
|
525
|
+
const children = world.tryGet(id, Children)?.entities;
|
|
526
|
+
if (!children) continue;
|
|
527
|
+
for (const child of children) {
|
|
528
|
+
if (child === null || generated.has(child) || !world.isAlive(child)) continue;
|
|
529
|
+
if (world.has(child, InstancePart)) continue;
|
|
530
|
+
foreign.push(child);
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
for (const child of foreign) world.add(child, ChildOf, { parent: keepUnder });
|
|
534
|
+
}
|
|
535
|
+
for (let i = 0; i < state.ids.length; i++) {
|
|
536
|
+
const t = state.plan.entities[i];
|
|
537
|
+
const id = state.ids[i];
|
|
538
|
+
if (t && t.parent === -1 && world.isAlive(id)) world.despawn(id);
|
|
539
|
+
}
|
|
540
|
+
const scene = state.sceneId === void 0 ? void 0 : world.tryResource(SceneIndex)?.get(state.sceneId);
|
|
541
|
+
if (scene) for (const path of state.paths) scene.entities.delete(path);
|
|
542
|
+
}
|
|
543
|
+
function memberOf(world, entity) {
|
|
544
|
+
const m = world.tryGet(entity, SceneMember);
|
|
545
|
+
return m ? { scene: m.scene, path: m.path } : void 0;
|
|
546
|
+
}
|
|
547
|
+
function ownFields(world, entity) {
|
|
548
|
+
const out = /* @__PURE__ */ new Map();
|
|
549
|
+
const member = world.tryGet(entity, SceneMember);
|
|
550
|
+
const authored = member ? world.tryResource(SceneIndex)?.get(member.scene)?.authored.get(entity) : void 0;
|
|
551
|
+
if (authored) {
|
|
552
|
+
for (const [name, json] of Object.entries(expandComponents(authored.components)))
|
|
553
|
+
out.set(name, new Set(Object.keys(json)));
|
|
554
|
+
return out;
|
|
555
|
+
}
|
|
556
|
+
for (const def of world.componentsOf(entity)) {
|
|
557
|
+
if (!def.serializable || def === ChildOf) continue;
|
|
558
|
+
const now = def.serialize(world.get(entity, def));
|
|
559
|
+
const defaults = def.serialize(def.defaults());
|
|
560
|
+
const fields = Object.keys(now).filter((f) => !equal(now[f], defaults[f]));
|
|
561
|
+
if (fields.length > 0) out.set(def.name, new Set(fields));
|
|
562
|
+
}
|
|
563
|
+
return out;
|
|
564
|
+
}
|
|
565
|
+
function applyRoot(world, entity, state) {
|
|
566
|
+
const root = state.plan.root;
|
|
567
|
+
if (!root) return;
|
|
568
|
+
const canonical = canonicalOf(state.template).get(-1);
|
|
569
|
+
const prev = state.applied;
|
|
570
|
+
resolveRefs(root, state.ids, entity);
|
|
571
|
+
const member = world.tryGet(entity, SceneMember);
|
|
572
|
+
const loaded = member ? world.tryResource(SceneIndex)?.get(member.scene)?.loaded.get(entity) : void 0;
|
|
573
|
+
for (let k = 0; k < root.defs.length; k++) {
|
|
574
|
+
const def = root.defs[k];
|
|
575
|
+
const value = root.inits[k][1];
|
|
576
|
+
if (!world.has(entity, def)) {
|
|
577
|
+
if (prev?.has(def.name)) continue;
|
|
578
|
+
world.add(entity, def, value);
|
|
579
|
+
loaded?.set(def.name, def.serialize(world.get(entity, def)));
|
|
580
|
+
continue;
|
|
581
|
+
}
|
|
582
|
+
const own = state.own.get(def.name);
|
|
583
|
+
const before = prev?.get(def.name);
|
|
584
|
+
const current = before ? def.serialize(world.get(entity, def)) : void 0;
|
|
585
|
+
const partial = {};
|
|
586
|
+
const written = [];
|
|
587
|
+
for (const field of Object.keys(def.fields)) {
|
|
588
|
+
if (own?.has(field)) continue;
|
|
589
|
+
const isEntity = def.fields[field].kind === "entity";
|
|
590
|
+
if (!isEntity && before && current && !equal(current[field], before[field])) continue;
|
|
591
|
+
partial[field] = value[field];
|
|
592
|
+
written.push(field);
|
|
593
|
+
}
|
|
594
|
+
if (written.length === 0) continue;
|
|
595
|
+
world.set(entity, def, partial);
|
|
596
|
+
if (loaded) {
|
|
597
|
+
const now = def.serialize(world.get(entity, def));
|
|
598
|
+
const base = { ...loaded.get(def.name) ?? now };
|
|
599
|
+
for (const field of written) base[field] = now[field];
|
|
600
|
+
loaded.set(def.name, base);
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
if (prev) {
|
|
604
|
+
for (const name of prev.keys()) {
|
|
605
|
+
if (canonical.has(name) || state.own.has(name)) continue;
|
|
606
|
+
const def = componentDef(name);
|
|
607
|
+
if (def) world.remove(entity, def);
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
state.applied = canonical;
|
|
611
|
+
}
|
|
612
|
+
function reportTo(world, where) {
|
|
613
|
+
const log = world.tryResource(LogResource);
|
|
614
|
+
return (key, error) => warn(log, stale(where, key, error));
|
|
615
|
+
}
|
|
616
|
+
function overridesOf(value) {
|
|
617
|
+
return isPlainObject(value) ? value : {};
|
|
618
|
+
}
|
|
619
|
+
function instantiate(world, entity, def, template, overrides, previous, keep = false, mark) {
|
|
620
|
+
const member = memberOf(world, entity);
|
|
621
|
+
const where = member ? `${member.scene}:${member.path}` : `entity ${entity}`;
|
|
622
|
+
const plan = planFor(world, template, overrides, reportTo(world, where));
|
|
623
|
+
const paths = [];
|
|
624
|
+
let ids;
|
|
625
|
+
if (keep && previous) {
|
|
626
|
+
ids = respawnKeeping(world, previous, plan, entity, member, paths, mark);
|
|
627
|
+
} else {
|
|
628
|
+
if (previous) despawnGenerated(world, previous, entity);
|
|
629
|
+
ids = reserve(world, plan);
|
|
630
|
+
spawnReserved(world, plan, entity, ids, member, paths, mark);
|
|
631
|
+
}
|
|
632
|
+
const state = {
|
|
633
|
+
def,
|
|
634
|
+
template,
|
|
635
|
+
plan,
|
|
636
|
+
overridesKey: JSON.stringify(overrides),
|
|
637
|
+
overrides,
|
|
638
|
+
ids,
|
|
639
|
+
sceneId: member?.scene,
|
|
640
|
+
paths,
|
|
641
|
+
own: previous?.own ?? ownFields(world, entity),
|
|
642
|
+
applied: previous?.applied
|
|
643
|
+
};
|
|
644
|
+
if (def === PrefabInstance) applyRoot(world, entity, state);
|
|
645
|
+
const scene = member ? world.tryResource(SceneIndex)?.get(member.scene) : void 0;
|
|
646
|
+
if (scene) for (const g of template.requested) scene.assets.add(g);
|
|
647
|
+
return state;
|
|
648
|
+
}
|
|
649
|
+
function respawnKeeping(world, old, plan, instance, member, paths, mark) {
|
|
650
|
+
const byPath = /* @__PURE__ */ new Map();
|
|
651
|
+
for (let i = 0; i < old.ids.length; i++) {
|
|
652
|
+
const id = old.ids[i];
|
|
653
|
+
if (id >= 0 && world.isAlive(id)) byPath.set(old.plan.flat.nodes[i].path, i);
|
|
654
|
+
}
|
|
655
|
+
const ids = new Array(plan.entities.length);
|
|
656
|
+
const reused = /* @__PURE__ */ new Set();
|
|
657
|
+
for (let i = 0; i < plan.entities.length; i++) {
|
|
658
|
+
if (!plan.entities[i]) {
|
|
659
|
+
ids[i] = -1;
|
|
660
|
+
continue;
|
|
661
|
+
}
|
|
662
|
+
const k = byPath.get(plan.flat.nodes[i].path);
|
|
663
|
+
const id = k === void 0 ? -1 : old.ids[k];
|
|
664
|
+
if (id >= 0) {
|
|
665
|
+
ids[i] = id;
|
|
666
|
+
reused.add(id);
|
|
667
|
+
} else ids[i] = world.reserveEntity();
|
|
668
|
+
}
|
|
669
|
+
const generated = new Set(old.ids);
|
|
670
|
+
for (let i = 0; i < old.ids.length; i++) {
|
|
671
|
+
const id = old.ids[i];
|
|
672
|
+
if (id < 0 || reused.has(id) || !world.isAlive(id)) continue;
|
|
673
|
+
const children = world.tryGet(id, Children)?.entities;
|
|
674
|
+
if (children) {
|
|
675
|
+
for (const child of [...children]) {
|
|
676
|
+
if (child === null || generated.has(child) || !world.isAlive(child)) continue;
|
|
677
|
+
if (!world.has(child, InstancePart)) world.add(child, ChildOf, { parent: instance });
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
world.despawnSingle(id);
|
|
681
|
+
}
|
|
682
|
+
const scene = old.sceneId === void 0 ? void 0 : world.tryResource(SceneIndex)?.get(old.sceneId);
|
|
683
|
+
if (scene) for (const path of old.paths) scene.entities.delete(path);
|
|
684
|
+
const sceneNow = member ? world.tryResource(SceneIndex)?.get(member.scene) : void 0;
|
|
685
|
+
const oldDefs = /* @__PURE__ */ new Map();
|
|
686
|
+
for (let i = 0; i < old.ids.length; i++) {
|
|
687
|
+
const t = old.plan.entities[i];
|
|
688
|
+
if (t && reused.has(old.ids[i])) oldDefs.set(old.ids[i], t.defs);
|
|
689
|
+
}
|
|
690
|
+
for (let i = 0; i < plan.entities.length; i++) {
|
|
691
|
+
const t = plan.entities[i];
|
|
692
|
+
const id = ids[i];
|
|
693
|
+
if (!t || id < 0) continue;
|
|
694
|
+
const parent = t.parent === -1 ? instance : ids[t.parent];
|
|
695
|
+
if (parent < 0) continue;
|
|
696
|
+
t.childOf.parent = parent;
|
|
697
|
+
t.part.instance = instance;
|
|
698
|
+
if (t.refs.length > 0) resolveRefs(t, ids, instance);
|
|
699
|
+
const path = member ? `${member.path}/${t.path}` : void 0;
|
|
700
|
+
const inits = member ? [...t.inits, [SceneMember, { scene: member.scene, path }]] : [...t.inits];
|
|
701
|
+
if (mark) inits.push([mark, {}]);
|
|
702
|
+
if (reused.has(id)) {
|
|
703
|
+
const next = new Set(t.defs);
|
|
704
|
+
for (const d of oldDefs.get(id) ?? [])
|
|
705
|
+
if (!next.has(d) && world.has(id, d)) world.remove(id, d);
|
|
706
|
+
for (const [d, value] of inits) world.add(id, d, value);
|
|
707
|
+
if (!member && world.has(id, SceneMember)) world.remove(id, SceneMember);
|
|
708
|
+
} else {
|
|
709
|
+
world.spawnReserved(id, inits);
|
|
710
|
+
}
|
|
711
|
+
if (path !== void 0) {
|
|
712
|
+
sceneNow?.entities.set(path, id);
|
|
713
|
+
paths.push(path);
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
return ids;
|
|
717
|
+
}
|
|
718
|
+
const sceneInstancesSystem = defineSystem({
|
|
719
|
+
name: "scene/instances",
|
|
720
|
+
description: "Spawns and respawns SceneInstance and PrefabInstance children.",
|
|
721
|
+
run: (_, world) => {
|
|
722
|
+
const s = perWorld.get(world);
|
|
723
|
+
if (s?.dirty) updateInstances(world);
|
|
724
|
+
}
|
|
725
|
+
});
|
|
726
|
+
const ScenePlugin = {
|
|
727
|
+
name: "scene",
|
|
728
|
+
provides: [componentsModule, prefabFileModule, sceneInstanceKind, prefabInstanceKind],
|
|
729
|
+
build(app) {
|
|
730
|
+
hookInstances(app.world);
|
|
731
|
+
app.addSystems(PreUpdate, sceneInstancesSystem);
|
|
732
|
+
}
|
|
733
|
+
};
|
|
734
|
+
function updateInstances(world) {
|
|
735
|
+
const s = stateOf(world);
|
|
736
|
+
s.dirty = false;
|
|
737
|
+
const server = assetServer(world);
|
|
738
|
+
for (const [entity, state] of [...s.states]) {
|
|
739
|
+
if (world.isAlive(entity) && world.has(entity, state.def)) continue;
|
|
740
|
+
despawnGenerated(world, state, entity);
|
|
741
|
+
s.states.delete(entity);
|
|
742
|
+
}
|
|
743
|
+
const todo = [];
|
|
744
|
+
for (const kind of kinds) {
|
|
745
|
+
const def = kind.def;
|
|
746
|
+
const q = world.query(kind.nested ? { with: [def] } : { with: [def], without: [InstancePart] });
|
|
747
|
+
for (const table of q.tables) {
|
|
748
|
+
for (let i = 0; i < table.count; i++) {
|
|
749
|
+
const entity = table.entities[i];
|
|
750
|
+
const value = world.get(entity, def);
|
|
751
|
+
let entry;
|
|
752
|
+
try {
|
|
753
|
+
entry = kind.source(world, value, entity);
|
|
754
|
+
} catch (err) {
|
|
755
|
+
world.tryResource(LogResource)?.error(err);
|
|
756
|
+
continue;
|
|
757
|
+
}
|
|
758
|
+
if (!entry) continue;
|
|
759
|
+
if (entry.state === "unloaded") {
|
|
760
|
+
server.request(entry.guid);
|
|
761
|
+
continue;
|
|
762
|
+
}
|
|
763
|
+
if (entry.state !== "loaded") continue;
|
|
764
|
+
const state = s.states.get(entity);
|
|
765
|
+
const o = overridesOf(value.overrides);
|
|
766
|
+
if (state && state.def === def && state.template.entry.guid === entry.guid && isCurrent(world, state.template) && state.template.flat.missing.size === 0 && state.overridesKey === JSON.stringify(o)) {
|
|
767
|
+
continue;
|
|
768
|
+
}
|
|
769
|
+
todo.push([entity, kind, entry, o]);
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
for (const [entity, kind, entry, overrides] of todo) {
|
|
774
|
+
if (!world.isAlive(entity)) continue;
|
|
775
|
+
let template;
|
|
776
|
+
try {
|
|
777
|
+
template = templateFor(world, entry);
|
|
778
|
+
} catch (err) {
|
|
779
|
+
world.tryResource(LogResource)?.error(err);
|
|
780
|
+
continue;
|
|
781
|
+
}
|
|
782
|
+
if (template.flat.missing.size > 0) continue;
|
|
783
|
+
const old = s.states.get(entity);
|
|
784
|
+
const keep = kind.keepIds === true && old?.def === kind.def;
|
|
785
|
+
s.states.set(
|
|
786
|
+
entity,
|
|
787
|
+
instantiate(world, entity, kind.def, template, overrides, old, keep, kind.mark)
|
|
788
|
+
);
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
async function settleInstances(world) {
|
|
792
|
+
const server = assetServer(world);
|
|
793
|
+
for (let round = 0; round < 8; round++) {
|
|
794
|
+
updateInstances(world);
|
|
795
|
+
const pending = /* @__PURE__ */ new Set();
|
|
796
|
+
for (const kind of kinds) {
|
|
797
|
+
const def = kind.def;
|
|
798
|
+
const q = world.query(
|
|
799
|
+
kind.nested ? { with: [def] } : { with: [def], without: [InstancePart] }
|
|
800
|
+
);
|
|
801
|
+
for (const table of q.tables) {
|
|
802
|
+
for (let i = 0; i < table.count; i++) {
|
|
803
|
+
const entity = table.entities[i];
|
|
804
|
+
let entry;
|
|
805
|
+
try {
|
|
806
|
+
entry = kind.source(world, world.get(entity, def), entity);
|
|
807
|
+
} catch {
|
|
808
|
+
continue;
|
|
809
|
+
}
|
|
810
|
+
if (entry && (entry.state === "loading" || entry.state === "unloaded"))
|
|
811
|
+
pending.add(entry.guid);
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
for (const t of stateOf(world).templates.values())
|
|
816
|
+
for (const g of t.flat.missing) pending.add(g);
|
|
817
|
+
if (pending.size === 0) return;
|
|
818
|
+
await server.whenSettled(pending);
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
function prefabEntry(world, ref) {
|
|
822
|
+
const entry = assetServer(world).entry(ref);
|
|
823
|
+
const label = typeof ref === "string" ? ref : ref.path ?? ref.guid;
|
|
824
|
+
if (entry?.type !== "Prefab") {
|
|
825
|
+
throw new ShardError("prefab/not-found", `No prefab "${label}" in the catalog`, {
|
|
826
|
+
hint: "Pass a *.prefab.json path under an asset root (run `shard import` for new files), or register one with registerPrefab."
|
|
827
|
+
});
|
|
828
|
+
}
|
|
829
|
+
return entry;
|
|
830
|
+
}
|
|
831
|
+
async function loadPrefab(world, ref) {
|
|
832
|
+
const entry = prefabEntry(world, ref);
|
|
833
|
+
const server = assetServer(world);
|
|
834
|
+
for (let round = 0; round < 8; round++) {
|
|
835
|
+
await server.load(entry.guid);
|
|
836
|
+
const template = templateFor(world, entry);
|
|
837
|
+
if (template.flat.missing.size === 0) return;
|
|
838
|
+
await server.whenSettled(template.flat.missing);
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
function spawnPrefab(target, ref, options = {}) {
|
|
842
|
+
const world = target instanceof Commands ? target.world : target;
|
|
843
|
+
const entry = prefabEntry(world, ref);
|
|
844
|
+
let template;
|
|
845
|
+
if (entry.state === "loaded") {
|
|
846
|
+
const s = stateOf(world);
|
|
847
|
+
const cached = s.templates.get(entry.guid);
|
|
848
|
+
template = cached && isCurrent(world, cached) ? cached : templateFor(world, entry);
|
|
849
|
+
}
|
|
850
|
+
if (!template || template.flat.missing.size > 0) {
|
|
851
|
+
throw new ShardError("prefab/not-loaded", `Prefab ${entry.path} isn't loaded`, {
|
|
852
|
+
hint: "Preload it with `await loadPrefab(world, path)`, or place it with scene/PrefabInstance, which waits for it to load."
|
|
853
|
+
});
|
|
854
|
+
}
|
|
855
|
+
const overrides = options.overrides;
|
|
856
|
+
const plan = overrides ? planFor(world, template, overrides, reportTo(world, `spawnPrefab ${entry.path}`)) : template;
|
|
857
|
+
const root = world.reserveEntity();
|
|
858
|
+
const ids = reserve(world, plan);
|
|
859
|
+
const spawn = (w) => spawnInstance(w, entry, template, plan, root, ids, options);
|
|
860
|
+
if (target instanceof Commands) target.run(spawn);
|
|
861
|
+
else spawn(world);
|
|
862
|
+
return root;
|
|
863
|
+
}
|
|
864
|
+
function spawnInstance(world, entry, template, plan, root, ids, options) {
|
|
865
|
+
const inits = [];
|
|
866
|
+
const own = /* @__PURE__ */ new Map();
|
|
867
|
+
if (plan.root) {
|
|
868
|
+
resolveRefs(plan.root, ids, root);
|
|
869
|
+
for (let k = 0; k < plan.root.defs.length; k++) inits.push(plan.root.inits[k]);
|
|
870
|
+
}
|
|
871
|
+
if (options.transform) {
|
|
872
|
+
const k = inits.findIndex(([d]) => d === Transform);
|
|
873
|
+
const value = { ...k === -1 ? {} : inits[k][1], ...options.transform };
|
|
874
|
+
if (k === -1) inits.push([Transform, value]);
|
|
875
|
+
else inits[k] = [Transform, value];
|
|
876
|
+
own.set(Transform.name, new Set(Object.keys(options.transform)));
|
|
877
|
+
}
|
|
878
|
+
inits.push([
|
|
879
|
+
PrefabInstance,
|
|
880
|
+
{
|
|
881
|
+
prefab: { type: "Prefab", guid: entry.guid, path: entry.path },
|
|
882
|
+
overrides: options.overrides ?? {}
|
|
883
|
+
}
|
|
884
|
+
]);
|
|
885
|
+
if (options.parent !== void 0)
|
|
886
|
+
inits.push([ChildOf, { parent: options.parent }]);
|
|
887
|
+
const parent = options.parent === void 0 ? void 0 : memberOf(world, options.parent);
|
|
888
|
+
const member = parent && {
|
|
889
|
+
scene: parent.scene,
|
|
890
|
+
path: `${parent.path}/${template.flat.rootName ?? "prefab"}`
|
|
891
|
+
};
|
|
892
|
+
const paths = [];
|
|
893
|
+
if (member) {
|
|
894
|
+
inits.push([SceneMember, member]);
|
|
895
|
+
world.tryResource(SceneIndex)?.get(member.scene)?.entities.set(member.path, root);
|
|
896
|
+
paths.push(member.path);
|
|
897
|
+
}
|
|
898
|
+
world.spawnReserved(root, inits);
|
|
899
|
+
spawnReserved(world, plan, root, ids, member, paths);
|
|
900
|
+
const s = stateOf(world);
|
|
901
|
+
s.states.set(root, {
|
|
902
|
+
def: PrefabInstance,
|
|
903
|
+
template,
|
|
904
|
+
plan,
|
|
905
|
+
overridesKey: JSON.stringify(options.overrides ?? {}),
|
|
906
|
+
overrides: options.overrides ?? {},
|
|
907
|
+
ids,
|
|
908
|
+
sceneId: member?.scene,
|
|
909
|
+
paths,
|
|
910
|
+
own,
|
|
911
|
+
applied: template.root ? canonicalOf(template).get(-1) : void 0
|
|
912
|
+
});
|
|
913
|
+
}
|
|
914
|
+
function instanceEntities(world, entity) {
|
|
915
|
+
const state = perWorld.get(world)?.states.get(entity);
|
|
916
|
+
const out = /* @__PURE__ */ new Map();
|
|
917
|
+
if (!state) return out;
|
|
918
|
+
for (let i = 0; i < state.ids.length; i++) {
|
|
919
|
+
const id = state.ids[i];
|
|
920
|
+
if (id >= 0 && world.isAlive(id)) out.set(state.plan.flat.nodes[i].path, id);
|
|
921
|
+
}
|
|
922
|
+
return out;
|
|
923
|
+
}
|
|
924
|
+
function prefabRootComponents(world, entity) {
|
|
925
|
+
const state = perWorld.get(world)?.states.get(entity);
|
|
926
|
+
if (!state || state.def !== PrefabInstance) return void 0;
|
|
927
|
+
return canonicalOf(state.template).get(-1);
|
|
928
|
+
}
|
|
929
|
+
function instanceOf(world, entity) {
|
|
930
|
+
if (perWorld.get(world)?.states.has(entity)) return entity;
|
|
931
|
+
const part = world.tryGet(entity, InstancePart);
|
|
932
|
+
return part?.instance ?? void 0;
|
|
933
|
+
}
|
|
934
|
+
let scratch;
|
|
935
|
+
function canonicalOf(template) {
|
|
936
|
+
if (template.canonical) return template.canonical;
|
|
937
|
+
const out = /* @__PURE__ */ new Map();
|
|
938
|
+
const nodes = template.flat.nodes;
|
|
939
|
+
const pathOf = (target) => target === -1 ? "." : nodes[target].path;
|
|
940
|
+
const add = (i, t) => out.set(i, canonicalComponents(t, pathOf));
|
|
941
|
+
if (template.root) add(-1, template.root);
|
|
942
|
+
else out.set(-1, /* @__PURE__ */ new Map());
|
|
943
|
+
for (let i = 0; i < template.entities.length; i++) {
|
|
944
|
+
const t = template.entities[i];
|
|
945
|
+
if (t) add(i, t);
|
|
946
|
+
}
|
|
947
|
+
template.canonical = out;
|
|
948
|
+
return out;
|
|
949
|
+
}
|
|
950
|
+
function canonicalComponents(t, pathOf) {
|
|
951
|
+
scratch ??= new World();
|
|
952
|
+
const e = scratch.spawn(
|
|
953
|
+
...t.defs.map((d, k) => [d, t.values[k]])
|
|
954
|
+
);
|
|
955
|
+
const json = serializeComponents(scratch, e);
|
|
956
|
+
scratch.despawn(e);
|
|
957
|
+
for (const r of t.refs) {
|
|
958
|
+
const comp = json.get(t.defs[r.component].name);
|
|
959
|
+
if (comp) comp[r.field] = pathOf(r.target);
|
|
960
|
+
}
|
|
961
|
+
return json;
|
|
962
|
+
}
|
|
963
|
+
function liveComponents(world, entity, pathOf) {
|
|
964
|
+
const json = serializeComponents(world, entity);
|
|
965
|
+
for (const [name, value] of json) {
|
|
966
|
+
const def = componentDef(name);
|
|
967
|
+
for (const { name: field, field: type } of def.layout) {
|
|
968
|
+
const v = value[field];
|
|
969
|
+
if (type.kind === "entity" && typeof v === "number" && pathOf.has(v))
|
|
970
|
+
value[field] = pathOf.get(v);
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
return json;
|
|
974
|
+
}
|
|
975
|
+
function diffFields(name, authored, now, loaded, base) {
|
|
976
|
+
const out = {};
|
|
977
|
+
const aliases = ALIASES[name] ?? {};
|
|
978
|
+
const covered = /* @__PURE__ */ new Set();
|
|
979
|
+
for (const [key, value] of Object.entries(authored)) {
|
|
980
|
+
const field = aliases[key]?.field ?? key;
|
|
981
|
+
covered.add(field);
|
|
982
|
+
if (loaded && equal(now[field], loaded[field])) out[key] = value;
|
|
983
|
+
else if (base && equal(now[field], base[field])) continue;
|
|
984
|
+
else if (field in now) out[field] = now[field];
|
|
985
|
+
}
|
|
986
|
+
const reference = base ?? loaded;
|
|
987
|
+
for (const field of Object.keys(now)) {
|
|
988
|
+
if (covered.has(field)) continue;
|
|
989
|
+
if (!reference || !equal(now[field], reference[field])) out[field] = now[field];
|
|
990
|
+
}
|
|
991
|
+
return out;
|
|
992
|
+
}
|
|
993
|
+
function currentOverrides(world, entity) {
|
|
994
|
+
const state = perWorld.get(world)?.states.get(entity);
|
|
995
|
+
if (!state) return void 0;
|
|
996
|
+
const { template, plan, ids } = state;
|
|
997
|
+
const nodes = plan.flat.nodes;
|
|
998
|
+
const base = canonicalOf(template);
|
|
999
|
+
const pathOf = /* @__PURE__ */ new Map([[entity, "."]]);
|
|
1000
|
+
for (let i = 0; i < ids.length; i++) if (ids[i] >= 0) pathOf.set(ids[i], nodes[i].path);
|
|
1001
|
+
const alive = (i) => ids[i] >= 0 && world.isAlive(ids[i]);
|
|
1002
|
+
const loadedCache = /* @__PURE__ */ new Map();
|
|
1003
|
+
const loadedOf = (i) => {
|
|
1004
|
+
if (!plan.flat.touched.has(i)) return base.get(i);
|
|
1005
|
+
let m = loadedCache.get(i);
|
|
1006
|
+
if (!m && plan.entities[i]) {
|
|
1007
|
+
m = canonicalComponents(plan.entities[i], (t) => t === -1 ? "." : nodes[t].path);
|
|
1008
|
+
loadedCache.set(i, m);
|
|
1009
|
+
}
|
|
1010
|
+
return m;
|
|
1011
|
+
};
|
|
1012
|
+
const live = /* @__PURE__ */ new Map();
|
|
1013
|
+
const liveOf = (i) => {
|
|
1014
|
+
let m = live.get(i);
|
|
1015
|
+
if (!m) {
|
|
1016
|
+
m = liveComponents(world, ids[i], pathOf);
|
|
1017
|
+
live.set(i, m);
|
|
1018
|
+
}
|
|
1019
|
+
return m;
|
|
1020
|
+
};
|
|
1021
|
+
const out = {};
|
|
1022
|
+
const handled = /* @__PURE__ */ new Set();
|
|
1023
|
+
const gone = /* @__PURE__ */ new Set();
|
|
1024
|
+
for (const [key, value] of Object.entries(state.overrides)) {
|
|
1025
|
+
const i = plan.flat.index.get(key);
|
|
1026
|
+
if (value === null) {
|
|
1027
|
+
if (i !== void 0) {
|
|
1028
|
+
if (!alive(i)) {
|
|
1029
|
+
out[key] = null;
|
|
1030
|
+
gone.add(i);
|
|
1031
|
+
}
|
|
1032
|
+
continue;
|
|
1033
|
+
}
|
|
1034
|
+
const cut = splitComponentKey(plan.flat, key);
|
|
1035
|
+
if (!cut)
|
|
1036
|
+
out[key] = null;
|
|
1037
|
+
else if (alive(cut.i) && !liveOf(cut.i).has(cut.component)) {
|
|
1038
|
+
out[key] = null;
|
|
1039
|
+
handled.add(`${cut.i}|${cut.component}`);
|
|
1040
|
+
}
|
|
1041
|
+
continue;
|
|
1042
|
+
}
|
|
1043
|
+
if (i === void 0 || !isPlainObject(value)) {
|
|
1044
|
+
out[key] = value;
|
|
1045
|
+
continue;
|
|
1046
|
+
}
|
|
1047
|
+
if (!alive(i)) continue;
|
|
1048
|
+
const entry = {};
|
|
1049
|
+
for (const [name, patch] of Object.entries(value)) {
|
|
1050
|
+
const now = liveOf(i).get(name);
|
|
1051
|
+
if (patch === null) {
|
|
1052
|
+
if (!now) {
|
|
1053
|
+
entry[name] = null;
|
|
1054
|
+
handled.add(`${i}|${name}`);
|
|
1055
|
+
}
|
|
1056
|
+
continue;
|
|
1057
|
+
}
|
|
1058
|
+
if (!now || !isPlainObject(patch)) continue;
|
|
1059
|
+
handled.add(`${i}|${name}`);
|
|
1060
|
+
const b = base.get(i)?.get(name);
|
|
1061
|
+
const fields = diffFields(name, patch, now, loadedOf(i)?.get(name), b);
|
|
1062
|
+
if (Object.keys(fields).length > 0 || !b) entry[name] = fields;
|
|
1063
|
+
}
|
|
1064
|
+
if (Object.keys(entry).length > 0) out[key] = entry;
|
|
1065
|
+
}
|
|
1066
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
1067
|
+
if (!template.entities[i]) continue;
|
|
1068
|
+
const path = nodes[i].path;
|
|
1069
|
+
const parent = nodes[i].parent;
|
|
1070
|
+
if (!alive(i)) {
|
|
1071
|
+
const parentGone = parent !== -1 && (!alive(parent) || gone.has(parent));
|
|
1072
|
+
if (!parentGone && !gone.has(i)) out[path] = null;
|
|
1073
|
+
gone.add(i);
|
|
1074
|
+
continue;
|
|
1075
|
+
}
|
|
1076
|
+
const b = base.get(i) ?? /* @__PURE__ */ new Map();
|
|
1077
|
+
const now = liveOf(i);
|
|
1078
|
+
const existing = out[path];
|
|
1079
|
+
const entry = isPlainObject(existing) ? existing : {};
|
|
1080
|
+
const loaded = loadedOf(i);
|
|
1081
|
+
for (const name of b.keys()) {
|
|
1082
|
+
if (handled.has(`${i}|${name}`) || now.has(name) || !loaded?.has(name)) continue;
|
|
1083
|
+
out[`${path}/${name}`] = null;
|
|
1084
|
+
}
|
|
1085
|
+
for (const [name, value] of now) {
|
|
1086
|
+
if (handled.has(`${i}|${name}`)) continue;
|
|
1087
|
+
const before = b.get(name);
|
|
1088
|
+
if (!before) {
|
|
1089
|
+
const spawned = loaded?.get(name);
|
|
1090
|
+
if (!spawned || !equal(spawned, value)) entry[name] = value;
|
|
1091
|
+
continue;
|
|
1092
|
+
}
|
|
1093
|
+
const fields = {};
|
|
1094
|
+
for (const field of Object.keys(value))
|
|
1095
|
+
if (!equal(value[field], before[field])) fields[field] = value[field];
|
|
1096
|
+
if (Object.keys(fields).length > 0) entry[name] = fields;
|
|
1097
|
+
}
|
|
1098
|
+
if (Object.keys(entry).length > 0) out[path] = entry;
|
|
1099
|
+
}
|
|
1100
|
+
return out;
|
|
1101
|
+
}
|
|
1102
|
+
function mergeOverridesIntoPrefab(file, overrides, onUnknown) {
|
|
1103
|
+
const out = structuredClone(file);
|
|
1104
|
+
if (out.extends !== void 0 || !out.root) {
|
|
1105
|
+
const merged = { ...out.overrides ?? {} };
|
|
1106
|
+
for (const [key, value] of Object.entries(overrides)) {
|
|
1107
|
+
const prev = merged[key];
|
|
1108
|
+
merged[key] = isPlainObject(prev) && isPlainObject(value) ? mergePatch(prev, value) : value;
|
|
1109
|
+
}
|
|
1110
|
+
out.overrides = merged;
|
|
1111
|
+
return out;
|
|
1112
|
+
}
|
|
1113
|
+
const root = out.root;
|
|
1114
|
+
const find = (path) => {
|
|
1115
|
+
const parts = path.split("/");
|
|
1116
|
+
let list = root.children ?? [];
|
|
1117
|
+
let parent = root;
|
|
1118
|
+
for (let k = 0; k < parts.length; k++) {
|
|
1119
|
+
const e = list.find((c) => c.name === parts[k]);
|
|
1120
|
+
if (!e) {
|
|
1121
|
+
if (k > 0 && instanceDefOf(expandComponents(parent.components)))
|
|
1122
|
+
return { entity: parent, rest: parts.slice(k).join("/") };
|
|
1123
|
+
return void 0;
|
|
1124
|
+
}
|
|
1125
|
+
if (k === parts.length - 1) return { entity: e, rest: "", parent };
|
|
1126
|
+
parent = e;
|
|
1127
|
+
list = e.children ?? [];
|
|
1128
|
+
}
|
|
1129
|
+
return void 0;
|
|
1130
|
+
};
|
|
1131
|
+
const nestedOverrides = (entity) => {
|
|
1132
|
+
entity.components ??= {};
|
|
1133
|
+
const comps = entity.components;
|
|
1134
|
+
const def = instanceDefOf(comps);
|
|
1135
|
+
const inst = comps[def.name];
|
|
1136
|
+
if (!isPlainObject(inst.overrides)) inst.overrides = {};
|
|
1137
|
+
return inst.overrides;
|
|
1138
|
+
};
|
|
1139
|
+
const setNested = (host, key, value) => {
|
|
1140
|
+
const target = nestedOverrides(host);
|
|
1141
|
+
const prev = target[key];
|
|
1142
|
+
target[key] = isPlainObject(prev) && isPlainObject(value) ? mergePatch(prev, value) : value;
|
|
1143
|
+
};
|
|
1144
|
+
for (const [key, value] of Object.entries(overrides)) {
|
|
1145
|
+
const parts = key.split("/");
|
|
1146
|
+
const component = parts.slice(-2).join("/");
|
|
1147
|
+
const owner = value === null && parts.length >= 3 && componentDef(component) && !find(key) ? find(parts.slice(0, -2).join("/")) : void 0;
|
|
1148
|
+
if (owner) {
|
|
1149
|
+
if (owner.rest === "") delete owner.entity.components?.[component];
|
|
1150
|
+
else setNested(owner.entity, `${owner.rest}/${component}`, null);
|
|
1151
|
+
continue;
|
|
1152
|
+
}
|
|
1153
|
+
const hit = find(key);
|
|
1154
|
+
if (!hit) {
|
|
1155
|
+
onUnknown?.(key);
|
|
1156
|
+
continue;
|
|
1157
|
+
}
|
|
1158
|
+
if (hit.rest !== "") {
|
|
1159
|
+
setNested(hit.entity, hit.rest, value);
|
|
1160
|
+
continue;
|
|
1161
|
+
}
|
|
1162
|
+
if (value === null) {
|
|
1163
|
+
const siblings = hit.parent?.children;
|
|
1164
|
+
if (siblings) siblings.splice(siblings.indexOf(hit.entity), 1);
|
|
1165
|
+
continue;
|
|
1166
|
+
}
|
|
1167
|
+
hit.entity.components ??= {};
|
|
1168
|
+
const comps = hit.entity.components;
|
|
1169
|
+
for (const [name, patch] of Object.entries(value)) {
|
|
1170
|
+
if (patch === null) delete comps[name];
|
|
1171
|
+
else comps[name] = { ...stripAliases(name, comps[name] ?? {}, patch), ...patch };
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
return out;
|
|
1175
|
+
}
|
|
1176
|
+
function stripAliases(name, authored, patch) {
|
|
1177
|
+
const aliases = ALIASES[name];
|
|
1178
|
+
if (!aliases) return authored;
|
|
1179
|
+
const out = { ...authored };
|
|
1180
|
+
for (const [alias, { field }] of Object.entries(aliases)) {
|
|
1181
|
+
if (field in patch) delete out[alias];
|
|
1182
|
+
if (alias in patch) delete out[field];
|
|
1183
|
+
}
|
|
1184
|
+
return out;
|
|
1185
|
+
}
|
|
1186
|
+
function mergePatch(a, b) {
|
|
1187
|
+
const out = { ...a };
|
|
1188
|
+
for (const [name, patch] of Object.entries(b)) {
|
|
1189
|
+
const prev = out[name];
|
|
1190
|
+
out[name] = patch && prev ? { ...prev, ...patch } : patch;
|
|
1191
|
+
}
|
|
1192
|
+
return out;
|
|
1193
|
+
}
|
|
1194
|
+
function prefabOfInstance(world, entity) {
|
|
1195
|
+
const state = perWorld.get(world)?.states.get(entity);
|
|
1196
|
+
return state?.def === PrefabInstance ? state.template.entry : void 0;
|
|
1197
|
+
}
|
|
1198
|
+
function validateInstance(world, def, json, base, f, errors, options) {
|
|
1199
|
+
const overrides = json.overrides;
|
|
1200
|
+
const at = `${base}/overrides`;
|
|
1201
|
+
if (overrides === void 0 || overrides === null) return;
|
|
1202
|
+
if (!isPlainObject(overrides)) {
|
|
1203
|
+
errors.push(
|
|
1204
|
+
new ShardError("schema/type-mismatch", '"overrides" must be an object', {
|
|
1205
|
+
path: at,
|
|
1206
|
+
hint: 'Map paths to component patches: { "Exhaust": { "particles/ParticleSystem": { "timeScale": 2 } } }.'
|
|
1207
|
+
})
|
|
1208
|
+
);
|
|
1209
|
+
return;
|
|
1210
|
+
}
|
|
1211
|
+
if (!options.catalog) return;
|
|
1212
|
+
const ref = handleOf(json, def === PrefabInstance ? "prefab" : "scene");
|
|
1213
|
+
const entry = ref && assetServer(world).entry(ref);
|
|
1214
|
+
if (entry?.state !== "loaded") return;
|
|
1215
|
+
let flat;
|
|
1216
|
+
try {
|
|
1217
|
+
flat = fork(flattenSource(world, entry, []));
|
|
1218
|
+
} catch (err) {
|
|
1219
|
+
if (err instanceof ShardError)
|
|
1220
|
+
errors.push(new ShardError(err.code, err.message, { path: base, hint: err.hint }));
|
|
1221
|
+
return;
|
|
1222
|
+
}
|
|
1223
|
+
const check = checkContext(world, flat, entry.path);
|
|
1224
|
+
applyOverrides(
|
|
1225
|
+
flat,
|
|
1226
|
+
overrides,
|
|
1227
|
+
"",
|
|
1228
|
+
(_, e) => errors.push(rebase(e, at)),
|
|
1229
|
+
(d, j) => d.validate(j, check)
|
|
1230
|
+
);
|
|
1231
|
+
for (const [k, child] of (f.entity.children ?? []).entries()) {
|
|
1232
|
+
if (typeof child?.name === "string" && flat.index.has(child.name)) {
|
|
1233
|
+
errors.push(
|
|
1234
|
+
new ShardError(
|
|
1235
|
+
"prefab/duplicate-name",
|
|
1236
|
+
`"${child.name}" is also the name of an entity the instance generates`,
|
|
1237
|
+
{
|
|
1238
|
+
path: `${f.pointer}/children/${k}/name`,
|
|
1239
|
+
hint: 'Rename the child, or change the generated one with "overrides" instead.'
|
|
1240
|
+
}
|
|
1241
|
+
)
|
|
1242
|
+
);
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
async function loadInstanceAssets(world, json) {
|
|
1247
|
+
const server = assetServer(world);
|
|
1248
|
+
const refs = /* @__PURE__ */ new Set();
|
|
1249
|
+
const walk = (entities) => {
|
|
1250
|
+
if (!Array.isArray(entities)) return;
|
|
1251
|
+
for (const e of entities) {
|
|
1252
|
+
if (!isPlainObject(e)) continue;
|
|
1253
|
+
const comps = e.components;
|
|
1254
|
+
if (isPlainObject(comps)) {
|
|
1255
|
+
for (const [name, field] of [
|
|
1256
|
+
[PrefabInstance.name, "prefab"],
|
|
1257
|
+
[SceneInstance.name, "scene"]
|
|
1258
|
+
]) {
|
|
1259
|
+
const ref = handleOf(comps[name], field);
|
|
1260
|
+
const entry = ref && server.entry(ref);
|
|
1261
|
+
if (entry) refs.add(entry.guid);
|
|
1262
|
+
}
|
|
1263
|
+
}
|
|
1264
|
+
walk(e.children);
|
|
1265
|
+
}
|
|
1266
|
+
};
|
|
1267
|
+
if (isPlainObject(json)) {
|
|
1268
|
+
walk(json.entities);
|
|
1269
|
+
if (isPlainObject(json.root)) walk([json.root]);
|
|
1270
|
+
}
|
|
1271
|
+
await server.whenSettled(refs);
|
|
1272
|
+
}
|
|
1273
|
+
export {
|
|
1274
|
+
ScenePlugin,
|
|
1275
|
+
currentOverrides,
|
|
1276
|
+
defineInstanceKind,
|
|
1277
|
+
hookInstances,
|
|
1278
|
+
instanceEntities,
|
|
1279
|
+
instanceKindOf,
|
|
1280
|
+
instanceKinds,
|
|
1281
|
+
instanceOf,
|
|
1282
|
+
loadInstanceAssets,
|
|
1283
|
+
loadPrefab,
|
|
1284
|
+
mergeOverridesIntoPrefab,
|
|
1285
|
+
prefabInstanceKind,
|
|
1286
|
+
prefabOfInstance,
|
|
1287
|
+
prefabRootComponents,
|
|
1288
|
+
releaseSceneHooks,
|
|
1289
|
+
sceneInstanceKind,
|
|
1290
|
+
sceneInstancesSystem,
|
|
1291
|
+
settleInstances,
|
|
1292
|
+
spawnPrefab,
|
|
1293
|
+
updateInstances,
|
|
1294
|
+
validateInstance
|
|
1295
|
+
};
|