@aethervtt/shard-scene 0.0.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components.d.ts +3 -1
- package/dist/components.js +9 -2
- package/dist/instances.js +7 -2
- package/dist/procedural.js +10 -0
- package/dist/scene.d.ts +9 -2
- package/dist/scene.js +28 -0
- package/package.json +8 -8
package/dist/components.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AssetStore } from '@aethervtt/shard-assets';
|
|
2
|
-
import { type Entity, type JsonValue } from '@aethervtt/shard-core';
|
|
2
|
+
import { type Entity, type JsonValue, type Owner } from '@aethervtt/shard-core';
|
|
3
3
|
import type { PrefabFile, SceneEntity, SceneFile } from './format.js';
|
|
4
4
|
/** Marks entities that came from a scene file. */
|
|
5
5
|
export declare const SceneMember: import("@aethervtt/shard-core").ComponentDef<{
|
|
@@ -18,6 +18,8 @@ export interface LoadedScene {
|
|
|
18
18
|
loadedResources: Map<string, JsonValue>;
|
|
19
19
|
/** Guids of every asset the scene referenced, requested at load. */
|
|
20
20
|
assets: Set<string>;
|
|
21
|
+
/** Who it was loaded for (0061); releasing the owner unloads it. */
|
|
22
|
+
owner?: Owner;
|
|
21
23
|
}
|
|
22
24
|
export declare const SceneIndex: import("@aethervtt/shard-core").ResourceDef<Map<string, LoadedScene>>;
|
|
23
25
|
/** Scene files loaded as assets (a glTF's node tree), by guid. */
|
package/dist/components.js
CHANGED
|
@@ -34,15 +34,22 @@ const SceneAssetType = defineAssetType("Scene", {
|
|
|
34
34
|
return out;
|
|
35
35
|
};
|
|
36
36
|
return resolve(artifact.json);
|
|
37
|
-
}
|
|
37
|
+
},
|
|
38
|
+
// A model that failed to load spawns a beveled box in its place (0061).
|
|
39
|
+
fallback: () => ({ version: 1, entities: [MISSING_ENTITY] })
|
|
38
40
|
});
|
|
41
|
+
const MISSING_ENTITY = {
|
|
42
|
+
name: "missing",
|
|
43
|
+
components: { "render/Mesh3d": { mesh: { path: "procedural:missing-box" } } }
|
|
44
|
+
};
|
|
39
45
|
const PrefabAssets = defineResource("scene/PrefabAssets", {
|
|
40
46
|
description: "Prefab assets (resolved entity trees) by guid.",
|
|
41
47
|
init: () => new AssetStore("Prefab")
|
|
42
48
|
});
|
|
43
49
|
const PrefabAssetType = defineAssetType("Prefab", {
|
|
44
50
|
store: PrefabAssets,
|
|
45
|
-
load: (artifact) => artifact.json
|
|
51
|
+
load: (artifact) => artifact.json,
|
|
52
|
+
fallback: () => ({ version: 1, root: MISSING_ENTITY })
|
|
46
53
|
});
|
|
47
54
|
const overridesField = () => t.json({
|
|
48
55
|
default: {},
|
package/dist/instances.js
CHANGED
|
@@ -31,6 +31,7 @@ import {
|
|
|
31
31
|
componentDef,
|
|
32
32
|
equal,
|
|
33
33
|
expandAliases,
|
|
34
|
+
notAuthorable,
|
|
34
35
|
rebase,
|
|
35
36
|
resolveAssets,
|
|
36
37
|
serializeComponents
|
|
@@ -271,6 +272,10 @@ function applyOverrides(flat, overrides, prefix, report, validate) {
|
|
|
271
272
|
);
|
|
272
273
|
continue;
|
|
273
274
|
}
|
|
275
|
+
if (def.hostOnly) {
|
|
276
|
+
report(key, notAuthorable(name, where));
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
274
279
|
if (!def.serializable || def === ChildOf) {
|
|
275
280
|
report(
|
|
276
281
|
key,
|
|
@@ -432,7 +437,7 @@ function stateOf(world) {
|
|
|
432
437
|
let s = perWorld.get(world);
|
|
433
438
|
if (!s) {
|
|
434
439
|
const off = assetServer(world).onEvent((e) => {
|
|
435
|
-
if (e.kind !== "loaded" && e.kind !== "modified") return;
|
|
440
|
+
if (e.kind !== "loaded" && e.kind !== "modified" && e.kind !== "failed") return;
|
|
436
441
|
const type = assetServer(world).entry(e.guid)?.type;
|
|
437
442
|
if (type === "Scene" || type === "Prefab") updateInstances(world);
|
|
438
443
|
});
|
|
@@ -760,7 +765,7 @@ function updateInstances(world) {
|
|
|
760
765
|
server.request(entry.guid);
|
|
761
766
|
continue;
|
|
762
767
|
}
|
|
763
|
-
if (entry.state !== "loaded") continue;
|
|
768
|
+
if (entry.state !== "loaded" && !server.isFallback(entry.guid)) continue;
|
|
764
769
|
const state = s.states.get(entity);
|
|
765
770
|
const o = overridesOf(value.overrides);
|
|
766
771
|
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)) {
|
package/dist/procedural.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ShardError } from "@aethervtt/shard-core";
|
|
2
2
|
import {
|
|
3
|
+
bevelBox,
|
|
3
4
|
box,
|
|
4
5
|
capsule,
|
|
5
6
|
cone,
|
|
@@ -20,6 +21,15 @@ const PROCEDURAL_MESHES = {
|
|
|
20
21
|
torus: {
|
|
21
22
|
params: ["radius", "tube", "radialSegments", "tubularSegments"],
|
|
22
23
|
create: (p) => torus(p)
|
|
24
|
+
},
|
|
25
|
+
// The stand-in for a mesh or model that failed to load (0061).
|
|
26
|
+
"missing-box": {
|
|
27
|
+
params: [],
|
|
28
|
+
create: () => {
|
|
29
|
+
const mesh = bevelBox();
|
|
30
|
+
mesh.missing = true;
|
|
31
|
+
return mesh;
|
|
32
|
+
}
|
|
23
33
|
}
|
|
24
34
|
};
|
|
25
35
|
function parseProcedural(source, params = {}, path) {
|
package/dist/scene.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type AssetRef, type ComponentDef, type Entity, type JsonValue, type ResolvedAsset, type SchemaContext, ShardError, type World } from '@aethervtt/shard-core';
|
|
1
|
+
import { type AssetRef, type ComponentDef, type Entity, type JsonValue, type Owner, type ResolvedAsset, type SchemaContext, ShardError, type World } from '@aethervtt/shard-core';
|
|
2
2
|
import { type SceneAsset, type SceneEntity, type SceneFile } from './format.js';
|
|
3
3
|
export interface LoadedSceneHandle {
|
|
4
4
|
id: string;
|
|
@@ -59,9 +59,14 @@ export declare function validateTree(world: World, file: {
|
|
|
59
59
|
}, flat: readonly FlatEntity[], errors: ShardError[], options: TreeOptions): void;
|
|
60
60
|
/** A specific scene error beats the generic asset-not-found for the same field; drop duplicates. */
|
|
61
61
|
export declare function dedupeErrors(errors: ShardError[]): ShardError[];
|
|
62
|
-
/**
|
|
62
|
+
/**
|
|
63
|
+
* Validates, then spawns the scene. Throws `scene/invalid` (with every error in `details`) if invalid.
|
|
64
|
+
* With `owner` (0061), everything in it belongs to that owner, and a scene that would exceed the
|
|
65
|
+
* owner's entity limit throws `core/owner-quota` before anything spawns.
|
|
66
|
+
*/
|
|
63
67
|
export declare function loadScene(world: World, json: unknown, options?: {
|
|
64
68
|
id?: string;
|
|
69
|
+
owner?: Owner;
|
|
65
70
|
}): LoadedSceneHandle;
|
|
66
71
|
/**
|
|
67
72
|
* Resolves once every asset the scene referenced has loaded or failed (following their
|
|
@@ -91,3 +96,5 @@ export declare function saveScene(world: World, id: string): SceneFile;
|
|
|
91
96
|
/** Scene JSON as written to disk: 2-space indent, trailing newline. */
|
|
92
97
|
export declare function stringifyScene(file: SceneFile): string;
|
|
93
98
|
export type { AssetRef };
|
|
99
|
+
/** A host-only component (ownership, 0061) named in authored data. */
|
|
100
|
+
export declare function notAuthorable(name: string, path: string): ShardError;
|
package/dist/scene.js
CHANGED
|
@@ -352,6 +352,10 @@ function validateTree(world, file, flat, errors, options) {
|
|
|
352
352
|
);
|
|
353
353
|
continue;
|
|
354
354
|
}
|
|
355
|
+
if (def.hostOnly) {
|
|
356
|
+
errors.push(notAuthorable(name, base));
|
|
357
|
+
continue;
|
|
358
|
+
}
|
|
355
359
|
if (!def.serializable || def === ChildOf) {
|
|
356
360
|
errors.push(
|
|
357
361
|
new ShardError("scene/derived-component", `"${name}" can't be written in a scene`, {
|
|
@@ -517,6 +521,7 @@ function loadScene(world, json, options = {}) {
|
|
|
517
521
|
const file = json;
|
|
518
522
|
const flat = [];
|
|
519
523
|
flatten(file.entities, "/entities", void 0, flat);
|
|
524
|
+
if (options.owner) world.owners.ensureCapacity(options.owner, "entities", flat.length);
|
|
520
525
|
const entities = /* @__PURE__ */ new Map();
|
|
521
526
|
for (const f of flat) entities.set(f.path, world.reserveEntity());
|
|
522
527
|
const requested = /* @__PURE__ */ new Set();
|
|
@@ -556,6 +561,12 @@ function loadScene(world, json, options = {}) {
|
|
|
556
561
|
else world.insertResource(def, structuredCloneJson(value));
|
|
557
562
|
scene.loadedResources.set(name, toJson(world.resource(def)));
|
|
558
563
|
}
|
|
564
|
+
if (options.owner) {
|
|
565
|
+
for (const f of flat)
|
|
566
|
+
if (f.parent === void 0) world.owners.adopt(options.owner, entities.get(f.path));
|
|
567
|
+
scene.owner = options.owner;
|
|
568
|
+
forgetOnRelease(world);
|
|
569
|
+
}
|
|
559
570
|
index.set(id, scene);
|
|
560
571
|
return { id, entities };
|
|
561
572
|
}
|
|
@@ -708,6 +719,22 @@ function stringifyScene(file) {
|
|
|
708
719
|
return `${JSON.stringify(file, null, 2)}
|
|
709
720
|
`;
|
|
710
721
|
}
|
|
722
|
+
const forgetting = /* @__PURE__ */ new WeakSet();
|
|
723
|
+
function forgetOnRelease(world) {
|
|
724
|
+
if (forgetting.has(world)) return;
|
|
725
|
+
forgetting.add(world);
|
|
726
|
+
world.owners.onRelease((owner) => {
|
|
727
|
+
const index = world.tryResource(SceneIndex);
|
|
728
|
+
if (!index) return;
|
|
729
|
+
for (const [id, scene] of index) if (scene.owner === owner) index.delete(id);
|
|
730
|
+
});
|
|
731
|
+
}
|
|
732
|
+
function notAuthorable(name, path) {
|
|
733
|
+
return new ShardError("core/owner-not-authorable", `"${name}" can't be written in scene files`, {
|
|
734
|
+
path,
|
|
735
|
+
hint: "Ownership is a grant from host code: the host loads the scene for an owner (world.owners)."
|
|
736
|
+
});
|
|
737
|
+
}
|
|
711
738
|
export {
|
|
712
739
|
ALIASES,
|
|
713
740
|
addSceneReadyCheck,
|
|
@@ -719,6 +746,7 @@ export {
|
|
|
719
746
|
findEntityByPath,
|
|
720
747
|
flatten,
|
|
721
748
|
loadScene,
|
|
749
|
+
notAuthorable,
|
|
722
750
|
pathOfEntity,
|
|
723
751
|
rebase,
|
|
724
752
|
reloadScene,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aethervtt/shard-scene",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -16,12 +16,12 @@
|
|
|
16
16
|
"README.md"
|
|
17
17
|
],
|
|
18
18
|
"dependencies": {
|
|
19
|
-
"@aethervtt/shard-assets": "0.0
|
|
20
|
-
"@aethervtt/shard-core": "0.0
|
|
21
|
-
"@aethervtt/shard-mesh": "0.0
|
|
22
|
-
"@aethervtt/shard-render": "0.0
|
|
23
|
-
"@aethervtt/shard-runtime": "0.0
|
|
24
|
-
"@aethervtt/shard-transform": "0.0
|
|
19
|
+
"@aethervtt/shard-assets": "0.2.0",
|
|
20
|
+
"@aethervtt/shard-core": "0.2.0",
|
|
21
|
+
"@aethervtt/shard-mesh": "0.2.0",
|
|
22
|
+
"@aethervtt/shard-render": "0.2.0",
|
|
23
|
+
"@aethervtt/shard-runtime": "0.2.0",
|
|
24
|
+
"@aethervtt/shard-transform": "0.2.0"
|
|
25
25
|
},
|
|
26
26
|
"repository": {
|
|
27
27
|
"type": "git",
|
|
@@ -33,6 +33,6 @@
|
|
|
33
33
|
"provenance": true
|
|
34
34
|
},
|
|
35
35
|
"shard": {
|
|
36
|
-
"buildSha": "
|
|
36
|
+
"buildSha": "32c22a3944ed23f5d1baa90bf7cc9fae724f5fd8"
|
|
37
37
|
}
|
|
38
38
|
}
|