@aethervtt/shard-project 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/define.d.ts +55 -0
- package/dist/define.js +56 -0
- package/dist/docs.d.ts +26 -0
- package/dist/docs.js +1336 -0
- package/dist/host.d.ts +47 -0
- package/dist/host.js +118 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +61 -0
- package/dist/manifest.d.ts +49 -0
- package/dist/manifest.js +147 -0
- package/dist/reload.d.ts +53 -0
- package/dist/reload.js +247 -0
- package/dist/session.d.ts +73 -0
- package/dist/session.js +144 -0
- package/dist/sourcemap.d.ts +31 -0
- package/dist/sourcemap.js +91 -0
- package/dist/templates.d.ts +9 -0
- package/dist/templates.js +283 -0
- package/package.json +55 -0
package/dist/host.d.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { GpuContext } from '@aethervtt/shard-gpu';
|
|
2
|
+
import type { AudioBackend, InputSource, Platform } from '@aethervtt/shard-platform';
|
|
3
|
+
import { type RenderTarget } from '@aethervtt/shard-render';
|
|
4
|
+
import { App, type Plugin } from '@aethervtt/shard-runtime';
|
|
5
|
+
import { type LoadedSceneHandle } from '@aethervtt/shard-scene';
|
|
6
|
+
import type { ManifestValue } from './manifest.js';
|
|
7
|
+
export interface BuildAppOptions {
|
|
8
|
+
manifest: ManifestValue;
|
|
9
|
+
/** The project plugin (default export of `entry`), once loaded by the host. */
|
|
10
|
+
project?: Plugin;
|
|
11
|
+
gpu?: GpuContext;
|
|
12
|
+
canvas?: HTMLCanvasElement | OffscreenCanvas;
|
|
13
|
+
/** Headless stand-in for the window (CLI screenshots). */
|
|
14
|
+
target?: RenderTarget;
|
|
15
|
+
inputSource?: InputSource;
|
|
16
|
+
/** Where sound goes (`platform.audio`). Without one, audio records voices (headless). */
|
|
17
|
+
audio?: AudioBackend;
|
|
18
|
+
/**
|
|
19
|
+
* The host: saves and settings go to its `storage`, and saved scenes reload from its files.
|
|
20
|
+
* Without one, saves live in memory.
|
|
21
|
+
*/
|
|
22
|
+
platform?: Platform;
|
|
23
|
+
}
|
|
24
|
+
/** An app with the manifest's engine plugins and the project plugin, not yet initialized. */
|
|
25
|
+
export declare function buildApp(options: BuildAppOptions): App;
|
|
26
|
+
/**
|
|
27
|
+
* Loads every string table (`locales/*.strings.json`) so keyed text resolves from the first frame.
|
|
28
|
+
* Call after the asset scan, before loading scenes. Does nothing without text or UI.
|
|
29
|
+
*/
|
|
30
|
+
export declare function loadProjectStrings(app: App): Promise<void>;
|
|
31
|
+
/** Initializes the app and loads the start scene (id = its path). */
|
|
32
|
+
export declare function startProject(app: App, platform: Platform, manifest: ManifestValue): Promise<LoadedSceneHandle>;
|
|
33
|
+
/**
|
|
34
|
+
* Loads the project's `shaders/` folder into the shader library as `project::…`, and with `watch`
|
|
35
|
+
* reloads a file when it changes (the running game picks it up once it compiles). Returns a
|
|
36
|
+
* function that stops watching.
|
|
37
|
+
*/
|
|
38
|
+
export declare function loadProjectShaders(app: App, platform: Platform, options?: {
|
|
39
|
+
watch?: boolean;
|
|
40
|
+
dir?: string;
|
|
41
|
+
}): Promise<() => void>;
|
|
42
|
+
/**
|
|
43
|
+
* Reads the project's baked navmesh tiles (`.shard/cache/nav`) so navmeshes whose geometry hasn't
|
|
44
|
+
* changed load without running Recast, and lets `nav.bake` save there. Call after `app.init()`,
|
|
45
|
+
* before loading scenes. Does nothing without a nav plugin.
|
|
46
|
+
*/
|
|
47
|
+
export declare function loadProjectNavCache(app: App, platform: Platform): Promise<void>;
|
package/dist/host.js
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { animationPlugin } from "@aethervtt/shard-animation";
|
|
2
|
+
import { audioPlugin } from "@aethervtt/shard-audio";
|
|
3
|
+
import { gltfPlugin } from "@aethervtt/shard-gltf";
|
|
4
|
+
import { inputPlugin } from "@aethervtt/shard-input";
|
|
5
|
+
import { loadNavCache, Nav, navGridPlugin, navPlugin } from "@aethervtt/shard-nav";
|
|
6
|
+
import { noisePlugin } from "@aethervtt/shard-noise";
|
|
7
|
+
import { particlesPlugin } from "@aethervtt/shard-particles";
|
|
8
|
+
import { physics2dPlugin, physics3dPlugin } from "@aethervtt/shard-physics";
|
|
9
|
+
import { procgenPlugin } from "@aethervtt/shard-procgen";
|
|
10
|
+
import { forwardPlugin, renderPlugin, Shaders } from "@aethervtt/shard-render";
|
|
11
|
+
import { materialNoisePlugin } from "@aethervtt/shard-render/noise";
|
|
12
|
+
import { App, LogResource } from "@aethervtt/shard-runtime";
|
|
13
|
+
import { savePlugin } from "@aethervtt/shard-save";
|
|
14
|
+
import { loadScene, ScenePlugin } from "@aethervtt/shard-scene";
|
|
15
|
+
import { spritePlugin } from "@aethervtt/shard-sprite";
|
|
16
|
+
import { terrainPlugin } from "@aethervtt/shard-terrain";
|
|
17
|
+
import { LocaleState, loadStringTables, textPlugin } from "@aethervtt/shard-text";
|
|
18
|
+
import { TransformPlugin } from "@aethervtt/shard-transform";
|
|
19
|
+
import { uiPlugin } from "@aethervtt/shard-ui";
|
|
20
|
+
function buildApp(options) {
|
|
21
|
+
const { manifest } = options;
|
|
22
|
+
const names = new Set(manifest.plugins);
|
|
23
|
+
if (names.has("sprite") || names.has("text") || names.has("particles") || names.has("animation"))
|
|
24
|
+
names.add("render/forward");
|
|
25
|
+
if (names.has("render/forward")) {
|
|
26
|
+
names.add("render");
|
|
27
|
+
names.add("core/transform");
|
|
28
|
+
}
|
|
29
|
+
if (names.has("physics3d") || names.has("physics2d") || names.has("audio") || names.has("ui") || names.has("nav") || names.has("nav/grid") || names.has("terrain"))
|
|
30
|
+
names.add("core/transform");
|
|
31
|
+
const app = new App({ seed: manifest.seed });
|
|
32
|
+
app.addPlugin(gltfPlugin);
|
|
33
|
+
if (names.has("core/transform")) app.addPlugin(TransformPlugin);
|
|
34
|
+
if (names.has("render")) {
|
|
35
|
+
app.addPlugin(
|
|
36
|
+
renderPlugin({ gpu: options.gpu, canvas: options.canvas, target: options.target })
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
if (names.has("render/forward")) {
|
|
40
|
+
app.addPlugin(forwardPlugin({ msaa: manifest.window.msaa === 1 ? 1 : 4 }));
|
|
41
|
+
app.addPlugin(materialNoisePlugin);
|
|
42
|
+
}
|
|
43
|
+
if (names.has("sprite")) app.addPlugin(spritePlugin);
|
|
44
|
+
if (names.has("text")) app.addPlugin(textPlugin);
|
|
45
|
+
if (names.has("particles")) app.addPlugin(particlesPlugin);
|
|
46
|
+
if (names.has("animation")) app.addPlugin(animationPlugin);
|
|
47
|
+
if (names.has("physics3d")) app.addPlugin(physics3dPlugin);
|
|
48
|
+
if (names.has("physics2d")) app.addPlugin(physics2dPlugin);
|
|
49
|
+
if (names.has("input")) app.addPlugin(inputPlugin({ source: options.inputSource }));
|
|
50
|
+
if (names.has("audio")) app.addPlugin(audioPlugin({ backend: options.audio }));
|
|
51
|
+
if (names.has("ui")) app.addPlugin(uiPlugin);
|
|
52
|
+
if (names.has("nav")) app.addPlugin(navPlugin);
|
|
53
|
+
else if (names.has("nav/grid")) app.addPlugin(navGridPlugin);
|
|
54
|
+
if (names.has("terrain")) app.addPlugin(terrainPlugin({ workers: options.platform?.workers }));
|
|
55
|
+
app.addPlugin(noisePlugin);
|
|
56
|
+
app.addPlugin(ScenePlugin);
|
|
57
|
+
app.addPlugin(
|
|
58
|
+
procgenPlugin({
|
|
59
|
+
fs: options.platform?.fs,
|
|
60
|
+
storage: options.platform?.storage,
|
|
61
|
+
cacheSize: (manifest.procgen?.cacheSize ?? 256) * 1024 * 1024
|
|
62
|
+
})
|
|
63
|
+
);
|
|
64
|
+
app.addPlugin(
|
|
65
|
+
savePlugin({
|
|
66
|
+
storage: options.platform?.storage,
|
|
67
|
+
fs: options.platform?.fs,
|
|
68
|
+
engine: manifest.engine
|
|
69
|
+
})
|
|
70
|
+
);
|
|
71
|
+
if (options.project) app.addPlugin(options.project);
|
|
72
|
+
return app;
|
|
73
|
+
}
|
|
74
|
+
async function loadProjectStrings(app) {
|
|
75
|
+
if (!app.world.hasResource(LocaleState)) return;
|
|
76
|
+
try {
|
|
77
|
+
await loadStringTables(app.world);
|
|
78
|
+
} catch (err) {
|
|
79
|
+
app.world.tryResource(LogResource)?.error(err);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
async function startProject(app, platform, manifest) {
|
|
83
|
+
await app.init();
|
|
84
|
+
await loadProjectNavCache(app, platform);
|
|
85
|
+
const json = JSON.parse(await platform.fs.readText(manifest.startScene));
|
|
86
|
+
return loadScene(app.world, json, { id: manifest.startScene });
|
|
87
|
+
}
|
|
88
|
+
async function loadProjectShaders(app, platform, options = {}) {
|
|
89
|
+
const shaders = app.world.tryResource(Shaders);
|
|
90
|
+
if (!shaders) return () => {
|
|
91
|
+
};
|
|
92
|
+
const dir = options.dir ?? "shaders";
|
|
93
|
+
try {
|
|
94
|
+
await shaders.loadDir(platform, dir, "project");
|
|
95
|
+
} catch (err) {
|
|
96
|
+
app.world.tryResource(LogResource)?.error(err);
|
|
97
|
+
}
|
|
98
|
+
if (options.watch && platform.fs.watch && await platform.fs.exists(dir)) {
|
|
99
|
+
return shaders.watch(platform, dir, "project");
|
|
100
|
+
}
|
|
101
|
+
return () => {
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
async function loadProjectNavCache(app, platform) {
|
|
105
|
+
if (!app.world.hasResource(Nav)) return;
|
|
106
|
+
try {
|
|
107
|
+
await loadNavCache(app.world, platform.fs);
|
|
108
|
+
} catch (err) {
|
|
109
|
+
app.world.tryResource(LogResource)?.error(err);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
export {
|
|
113
|
+
buildApp,
|
|
114
|
+
loadProjectNavCache,
|
|
115
|
+
loadProjectShaders,
|
|
116
|
+
loadProjectStrings,
|
|
117
|
+
startProject
|
|
118
|
+
};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { defineProject, type ProjectDef, type ProjectOptions } from './define.js';
|
|
2
|
+
export { type ErrorCode, GENERATED_END, GENERATED_START, generateDocs, mergeAgentsMd, renderAssetCatalog, renderComponentCatalog, renderErrorCatalog, renderGeneratorCatalog, } from './docs.js';
|
|
3
|
+
export { type BuildAppOptions, buildApp, loadProjectNavCache, loadProjectShaders, loadProjectStrings, startProject, } from './host.js';
|
|
4
|
+
export { BUILTIN_PLUGINS, loadProject, Manifest, type ManifestValue, manifestJsonSchema, type ProjectInfo, validateManifest, } from './manifest.js';
|
|
5
|
+
export { createProjectReloader, ProjectReloaded, type ProjectReloader, type ReloadReport, } from './reload.js';
|
|
6
|
+
export { ProjectMethodParams, ProjectSession, type ProjectStatus } from './session.js';
|
|
7
|
+
export { inlineSourceMap, locateInBundle, SourceMap, type SourcePosition } from './sourcemap.js';
|
|
8
|
+
export { projectTemplate, type TemplateName, type TemplateOptions } from './templates.js';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { defineProject } from "./define.js";
|
|
2
|
+
import {
|
|
3
|
+
GENERATED_END,
|
|
4
|
+
GENERATED_START,
|
|
5
|
+
generateDocs,
|
|
6
|
+
mergeAgentsMd,
|
|
7
|
+
renderAssetCatalog,
|
|
8
|
+
renderComponentCatalog,
|
|
9
|
+
renderErrorCatalog,
|
|
10
|
+
renderGeneratorCatalog
|
|
11
|
+
} from "./docs.js";
|
|
12
|
+
import {
|
|
13
|
+
buildApp,
|
|
14
|
+
loadProjectNavCache,
|
|
15
|
+
loadProjectShaders,
|
|
16
|
+
loadProjectStrings,
|
|
17
|
+
startProject
|
|
18
|
+
} from "./host.js";
|
|
19
|
+
import {
|
|
20
|
+
BUILTIN_PLUGINS,
|
|
21
|
+
loadProject,
|
|
22
|
+
Manifest,
|
|
23
|
+
manifestJsonSchema,
|
|
24
|
+
validateManifest
|
|
25
|
+
} from "./manifest.js";
|
|
26
|
+
import {
|
|
27
|
+
createProjectReloader,
|
|
28
|
+
ProjectReloaded
|
|
29
|
+
} from "./reload.js";
|
|
30
|
+
import { ProjectMethodParams, ProjectSession } from "./session.js";
|
|
31
|
+
import { inlineSourceMap, locateInBundle, SourceMap } from "./sourcemap.js";
|
|
32
|
+
import { projectTemplate } from "./templates.js";
|
|
33
|
+
export {
|
|
34
|
+
BUILTIN_PLUGINS,
|
|
35
|
+
GENERATED_END,
|
|
36
|
+
GENERATED_START,
|
|
37
|
+
Manifest,
|
|
38
|
+
ProjectMethodParams,
|
|
39
|
+
ProjectReloaded,
|
|
40
|
+
ProjectSession,
|
|
41
|
+
SourceMap,
|
|
42
|
+
buildApp,
|
|
43
|
+
createProjectReloader,
|
|
44
|
+
defineProject,
|
|
45
|
+
generateDocs,
|
|
46
|
+
inlineSourceMap,
|
|
47
|
+
loadProject,
|
|
48
|
+
loadProjectNavCache,
|
|
49
|
+
loadProjectShaders,
|
|
50
|
+
loadProjectStrings,
|
|
51
|
+
locateInBundle,
|
|
52
|
+
manifestJsonSchema,
|
|
53
|
+
mergeAgentsMd,
|
|
54
|
+
projectTemplate,
|
|
55
|
+
renderAssetCatalog,
|
|
56
|
+
renderComponentCatalog,
|
|
57
|
+
renderErrorCatalog,
|
|
58
|
+
renderGeneratorCatalog,
|
|
59
|
+
startProject,
|
|
60
|
+
validateManifest
|
|
61
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { type Infer, type JsonSchema, ShardError } from '@aethervtt/shard-core';
|
|
2
|
+
import type { Platform } from '@aethervtt/shard-platform';
|
|
3
|
+
/** The `shard.json` schema. Defined with the schema system so validation and JSON Schema match. */
|
|
4
|
+
export declare const Manifest: import("@aethervtt/shard-core").ComponentDef<{
|
|
5
|
+
readonly $schema: import("@aethervtt/shard-core").FieldType<string, "object">;
|
|
6
|
+
readonly name: import("@aethervtt/shard-core").FieldType<string, "object">;
|
|
7
|
+
readonly version: import("@aethervtt/shard-core").FieldType<string, "object">;
|
|
8
|
+
readonly engine: import("@aethervtt/shard-core").FieldType<string, "object">;
|
|
9
|
+
readonly entry: import("@aethervtt/shard-core").FieldType<string, "object">;
|
|
10
|
+
readonly startScene: import("@aethervtt/shard-core").FieldType<string, "object">;
|
|
11
|
+
readonly seed: import("@aethervtt/shard-core").FieldType<number, "u32">;
|
|
12
|
+
readonly window: import("@aethervtt/shard-core").FieldType<import("@aethervtt/shard-core").InferFields<{
|
|
13
|
+
width: import("@aethervtt/shard-core").FieldType<number, "u32">;
|
|
14
|
+
height: import("@aethervtt/shard-core").FieldType<number, "u32">;
|
|
15
|
+
msaa: import("@aethervtt/shard-core").FieldType<number, "u8">;
|
|
16
|
+
}>, "object">;
|
|
17
|
+
readonly assetRoots: import("@aethervtt/shard-core").FieldType<string[], "object">;
|
|
18
|
+
readonly procgen: import("@aethervtt/shard-core").FieldType<import("@aethervtt/shard-core").InferFields<{
|
|
19
|
+
cacheSize: import("@aethervtt/shard-core").FieldType<number, "u32">;
|
|
20
|
+
}>, "object">;
|
|
21
|
+
readonly plugins: import("@aethervtt/shard-core").FieldType<string[], "object">;
|
|
22
|
+
}>;
|
|
23
|
+
export type ManifestValue = Infer<typeof Manifest>;
|
|
24
|
+
/** Validates a manifest: the schema plus rules the schema can't express. Collects every error. */
|
|
25
|
+
export declare function validateManifest(json: unknown): ShardError[];
|
|
26
|
+
/** Built-in engine plugins a manifest can name, with what they bring. */
|
|
27
|
+
export declare const BUILTIN_PLUGINS: {
|
|
28
|
+
readonly render: 'GPU device, render graph, views, shaders';
|
|
29
|
+
readonly 'render/forward': 'cameras, meshes, standard material, lights (includes render and core/transform)';
|
|
30
|
+
readonly sprite: 'sprites, atlases, frame animation, tilemaps (includes render/forward)';
|
|
31
|
+
readonly text: 'Text and ScreenText with imported fonts (includes render/forward)';
|
|
32
|
+
readonly particles: 'GPU particle effects from *.particles.json (includes render/forward)';
|
|
33
|
+
readonly animation: 'AnimationPlayer: glTF and .anim.json clips on joints and fields, blending, root motion; Animator graphs from .animgraph.json (includes render/forward)';
|
|
34
|
+
readonly physics3d: 'Rapier 3D rigid bodies, colliders, joints, raycasts (includes core/transform)';
|
|
35
|
+
readonly physics2d: 'Rapier 2D physics in the XY plane (includes core/transform)';
|
|
36
|
+
readonly input: 'keyboard, mouse, gamepad, touch, action maps';
|
|
37
|
+
readonly audio: 'AudioSource, AudioListener, playSound, buses and ducking, spatial audio, voice limits (includes core/transform)';
|
|
38
|
+
readonly ui: 'UiNode trees under a UiRoot: flexbox layout, text, images, buttons, sliders, text fields, world-anchored markers, themes (includes core/transform)';
|
|
39
|
+
readonly nav: 'navigation: NavGrid A* (2D), NavMesh baked by Recast from NavSource geometry, OffMeshLink, path queries, NavAgent steering (includes core/transform)';
|
|
40
|
+
readonly 'nav/grid': 'navigation on NavGrids only, without loading the Recast WASM (includes core/transform)';
|
|
41
|
+
readonly terrain: 'planets: Planet cube-sphere terrain from noise graphs with LOD, geomorphing, oceans, biomes, collider chunks around characters, navmeshes on the surface; terrain.* methods (includes core/transform; add render/forward to draw it, physics3d for colliders)';
|
|
42
|
+
readonly 'core/transform': 'Transform and hierarchy propagation';
|
|
43
|
+
};
|
|
44
|
+
export declare function manifestJsonSchema(): JsonSchema;
|
|
45
|
+
export interface ProjectInfo {
|
|
46
|
+
manifest: ManifestValue;
|
|
47
|
+
}
|
|
48
|
+
/** Reads and validates `shard.json` through the platform (rooted at the project folder). */
|
|
49
|
+
export declare function loadProject(platform: Platform): Promise<ProjectInfo>;
|
package/dist/manifest.js
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { defineSchema, ShardError, t } from "@aethervtt/shard-core";
|
|
2
|
+
const NAMESPACE = /^[a-z][a-z0-9-]*$/;
|
|
3
|
+
const Manifest = defineSchema(
|
|
4
|
+
"project/Manifest",
|
|
5
|
+
{
|
|
6
|
+
$schema: t.string({ description: "Path to the generated schema, for editor validation." }),
|
|
7
|
+
name: t.string({
|
|
8
|
+
required: true,
|
|
9
|
+
description: "Project name: lowercase letters, digits, dashes. Also the namespace of project components."
|
|
10
|
+
}),
|
|
11
|
+
version: t.string({ default: "0.1.0" }),
|
|
12
|
+
engine: t.string({ default: "0.x", description: "Engine version range." }),
|
|
13
|
+
entry: t.string({ default: "scripts/main.ts", description: "The project plugin module." }),
|
|
14
|
+
startScene: t.string({
|
|
15
|
+
default: "scenes/main.scene.json",
|
|
16
|
+
description: "Scene loaded at startup."
|
|
17
|
+
}),
|
|
18
|
+
seed: t.u32({ default: 1, description: "Root seed for GlobalRng. Same seed, same world." }),
|
|
19
|
+
window: t.struct(
|
|
20
|
+
{
|
|
21
|
+
width: t.u32({ default: 1280, min: 1 }),
|
|
22
|
+
height: t.u32({ default: 720, min: 1 }),
|
|
23
|
+
msaa: t.u8({ default: 4, min: 1, max: 4, description: "1 or 4." })
|
|
24
|
+
},
|
|
25
|
+
{ description: "Window (or headless render target) size and anti-aliasing." }
|
|
26
|
+
),
|
|
27
|
+
assetRoots: t.list(t.string, {
|
|
28
|
+
default: ["assets", "materials", "data", "prefabs", "locales", "generators"],
|
|
29
|
+
description: "Folders the asset database imports from. Nothing outside them is imported."
|
|
30
|
+
}),
|
|
31
|
+
procgen: t.struct(
|
|
32
|
+
{
|
|
33
|
+
cacheSize: t.u32({
|
|
34
|
+
default: 256,
|
|
35
|
+
min: 1,
|
|
36
|
+
unit: "MB",
|
|
37
|
+
description: "Generator outputs kept in memory (and in browser storage), in megabytes."
|
|
38
|
+
})
|
|
39
|
+
},
|
|
40
|
+
{ description: "Generators (0042): output cache limits." }
|
|
41
|
+
),
|
|
42
|
+
plugins: t.list(t.string, {
|
|
43
|
+
default: ["render/forward", "input"],
|
|
44
|
+
description: "Engine plugins to enable, by name: render, render/forward, sprite, text, particles, animation, physics3d, physics2d, input, audio, ui, nav, nav/grid, core/transform."
|
|
45
|
+
})
|
|
46
|
+
},
|
|
47
|
+
{ description: "shard.json: the project manifest." }
|
|
48
|
+
);
|
|
49
|
+
function validateManifest(json) {
|
|
50
|
+
const errors = Manifest.validate(json);
|
|
51
|
+
const value = json;
|
|
52
|
+
if (typeof value?.name === "string" && !NAMESPACE.test(value.name)) {
|
|
53
|
+
errors.push(
|
|
54
|
+
new ShardError(
|
|
55
|
+
"project/invalid-name",
|
|
56
|
+
`Project name "${value.name}" isn't a valid namespace`,
|
|
57
|
+
{
|
|
58
|
+
path: "/name",
|
|
59
|
+
hint: 'Use lowercase letters, digits, and dashes, starting with a letter (e.g. "star-explorer").'
|
|
60
|
+
}
|
|
61
|
+
)
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
const msaa = value?.window?.msaa;
|
|
65
|
+
if (msaa !== void 0 && msaa !== 1 && msaa !== 4) {
|
|
66
|
+
errors.push(
|
|
67
|
+
new ShardError("schema/out-of-range", "window.msaa must be 1 or 4", { path: "/window/msaa" })
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
for (const [i, plugin] of (value?.plugins ?? []).entries()) {
|
|
71
|
+
if (typeof plugin === "string" && !(plugin in BUILTIN_PLUGINS)) {
|
|
72
|
+
errors.push(
|
|
73
|
+
new ShardError("project/unknown-plugin", `Unknown engine plugin "${plugin}"`, {
|
|
74
|
+
path: `/plugins/${i}`,
|
|
75
|
+
hint: `Built-in plugins: ${Object.keys(BUILTIN_PLUGINS).join(", ")}.`
|
|
76
|
+
})
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return errors;
|
|
81
|
+
}
|
|
82
|
+
const BUILTIN_PLUGINS = {
|
|
83
|
+
render: "GPU device, render graph, views, shaders",
|
|
84
|
+
"render/forward": "cameras, meshes, standard material, lights (includes render and core/transform)",
|
|
85
|
+
sprite: "sprites, atlases, frame animation, tilemaps (includes render/forward)",
|
|
86
|
+
text: "Text and ScreenText with imported fonts (includes render/forward)",
|
|
87
|
+
particles: "GPU particle effects from *.particles.json (includes render/forward)",
|
|
88
|
+
animation: "AnimationPlayer: glTF and .anim.json clips on joints and fields, blending, root motion; Animator graphs from .animgraph.json (includes render/forward)",
|
|
89
|
+
physics3d: "Rapier 3D rigid bodies, colliders, joints, raycasts (includes core/transform)",
|
|
90
|
+
physics2d: "Rapier 2D physics in the XY plane (includes core/transform)",
|
|
91
|
+
input: "keyboard, mouse, gamepad, touch, action maps",
|
|
92
|
+
audio: "AudioSource, AudioListener, playSound, buses and ducking, spatial audio, voice limits (includes core/transform)",
|
|
93
|
+
ui: "UiNode trees under a UiRoot: flexbox layout, text, images, buttons, sliders, text fields, world-anchored markers, themes (includes core/transform)",
|
|
94
|
+
nav: "navigation: NavGrid A* (2D), NavMesh baked by Recast from NavSource geometry, OffMeshLink, path queries, NavAgent steering (includes core/transform)",
|
|
95
|
+
"nav/grid": "navigation on NavGrids only, without loading the Recast WASM (includes core/transform)",
|
|
96
|
+
terrain: "planets: Planet cube-sphere terrain from noise graphs with LOD, geomorphing, oceans, biomes, collider chunks around characters, navmeshes on the surface; terrain.* methods (includes core/transform; add render/forward to draw it, physics3d for colliders)",
|
|
97
|
+
"core/transform": "Transform and hierarchy propagation"
|
|
98
|
+
};
|
|
99
|
+
function manifestJsonSchema() {
|
|
100
|
+
const schema = Manifest.jsonSchema();
|
|
101
|
+
schema.title = "shard.json";
|
|
102
|
+
schema.properties.name.pattern = NAMESPACE.source;
|
|
103
|
+
return schema;
|
|
104
|
+
}
|
|
105
|
+
async function loadProject(platform) {
|
|
106
|
+
let text;
|
|
107
|
+
try {
|
|
108
|
+
text = await platform.fs.readText("shard.json");
|
|
109
|
+
} catch (cause) {
|
|
110
|
+
throw new ShardError("project/not-found", "No shard.json in the project folder", {
|
|
111
|
+
hint: "Run `shard init <dir>` to create a project, or run from a project folder.",
|
|
112
|
+
cause
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
let json;
|
|
116
|
+
try {
|
|
117
|
+
json = JSON.parse(text);
|
|
118
|
+
} catch (cause) {
|
|
119
|
+
throw new ShardError(
|
|
120
|
+
"project/invalid-json",
|
|
121
|
+
`shard.json is not valid JSON: ${cause.message}`,
|
|
122
|
+
{
|
|
123
|
+
path: "shard.json"
|
|
124
|
+
}
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
const errors = validateManifest(json);
|
|
128
|
+
if (errors.length > 0) {
|
|
129
|
+
throw new ShardError(
|
|
130
|
+
"project/invalid-manifest",
|
|
131
|
+
`shard.json has ${errors.length} error(s); first: ${errors[0].message}`,
|
|
132
|
+
{
|
|
133
|
+
path: errors[0].path,
|
|
134
|
+
hint: errors[0].hint,
|
|
135
|
+
details: errors
|
|
136
|
+
}
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
return { manifest: Manifest.deserialize(json) };
|
|
140
|
+
}
|
|
141
|
+
export {
|
|
142
|
+
BUILTIN_PLUGINS,
|
|
143
|
+
Manifest,
|
|
144
|
+
loadProject,
|
|
145
|
+
manifestJsonSchema,
|
|
146
|
+
validateManifest
|
|
147
|
+
};
|
package/dist/reload.d.ts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { type EventDef } from '@aethervtt/shard-core';
|
|
2
|
+
import type { App, Plugin } from '@aethervtt/shard-runtime';
|
|
3
|
+
export interface ReloadReport {
|
|
4
|
+
ok: boolean;
|
|
5
|
+
ms: number;
|
|
6
|
+
/** Components whose layout changed and whose data was migrated. */
|
|
7
|
+
migrated: string[];
|
|
8
|
+
/** Components the new code no longer defines; kept in the world with their old definition. */
|
|
9
|
+
orphaned: string[];
|
|
10
|
+
systems: {
|
|
11
|
+
added: string[];
|
|
12
|
+
removed: string[];
|
|
13
|
+
changed: string[];
|
|
14
|
+
};
|
|
15
|
+
/** Data files re-imported because their type changed (and those that no longer validate). */
|
|
16
|
+
assets: {
|
|
17
|
+
imported: string[];
|
|
18
|
+
failed: string[];
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Generator outputs whose generator's code changed: generator files re-imported, and loaded
|
|
22
|
+
* runtime outputs regenerating in the background (each swaps in place when ready).
|
|
23
|
+
*/
|
|
24
|
+
procgen: {
|
|
25
|
+
regenerated: string[];
|
|
26
|
+
};
|
|
27
|
+
error?: {
|
|
28
|
+
code: string;
|
|
29
|
+
message: string;
|
|
30
|
+
path?: string;
|
|
31
|
+
hint?: string;
|
|
32
|
+
source?: string;
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
/** Sent after every successful project reload, with its report. */
|
|
36
|
+
export declare const ProjectReloaded: EventDef<ReloadReport>;
|
|
37
|
+
type ModuleLoader = () => Promise<{
|
|
38
|
+
default?: unknown;
|
|
39
|
+
}>;
|
|
40
|
+
/**
|
|
41
|
+
* Hot reload for a project's code. `reload` evaluates the new bundle inside a redefinition scope,
|
|
42
|
+
* migrates component data whose layout changed, swaps the project's systems and observers, and
|
|
43
|
+
* keeps entities and resources. Any failure rolls everything back and the old code keeps running.
|
|
44
|
+
*/
|
|
45
|
+
export declare function createProjectReloader(app: App, options: {
|
|
46
|
+
namespace: string;
|
|
47
|
+
current: Plugin;
|
|
48
|
+
}): {
|
|
49
|
+
readonly current: Plugin;
|
|
50
|
+
reload(load: ModuleLoader): Promise<ReloadReport>;
|
|
51
|
+
};
|
|
52
|
+
export type ProjectReloader = ReturnType<typeof createProjectReloader>;
|
|
53
|
+
export {};
|