@aethervtt/shard-procgen 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/check.d.ts +12 -0
- package/dist/check.js +18 -0
- package/dist/context.d.ts +61 -0
- package/dist/context.js +96 -0
- package/dist/generator.d.ts +135 -0
- package/dist/generator.js +269 -0
- package/dist/importer.d.ts +18 -0
- package/dist/importer.js +122 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +109 -0
- package/dist/instance.d.ts +28 -0
- package/dist/instance.js +147 -0
- package/dist/job.d.ts +51 -0
- package/dist/job.js +93 -0
- package/dist/mesh-builder.d.ts +27 -0
- package/dist/mesh-builder.js +421 -0
- package/dist/methods.d.ts +44 -0
- package/dist/methods.js +212 -0
- package/dist/outputs.d.ts +22 -0
- package/dist/outputs.js +250 -0
- package/dist/plugin.d.ts +11 -0
- package/dist/plugin.js +30 -0
- package/dist/record.d.ts +18 -0
- package/dist/record.js +66 -0
- package/dist/refs.d.ts +11 -0
- package/dist/refs.js +42 -0
- package/dist/runtime.d.ts +159 -0
- package/dist/runtime.js +553 -0
- package/dist/schema.d.ts +6 -0
- package/dist/schema.js +35 -0
- package/dist/sheet.d.ts +7 -0
- package/dist/sheet.js +85 -0
- package/dist/worker.d.ts +10 -0
- package/dist/worker.js +13 -0
- package/package.json +46 -0
package/dist/importer.js
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import {
|
|
2
|
+
defineAssetSchema,
|
|
3
|
+
defineImportDependency,
|
|
4
|
+
defineImporter
|
|
5
|
+
} from "@aethervtt/shard-assets";
|
|
6
|
+
import { defineSchema, isPlainObject, ShardError } from "@aethervtt/shard-core";
|
|
7
|
+
import { codeHashOf, findGenerator, requestOf, requireGenerator } from "./generator.js";
|
|
8
|
+
import { paramHandles, runJob } from "./runtime.js";
|
|
9
|
+
import { genFileSchema } from "./schema.js";
|
|
10
|
+
const NoSettings = defineSchema(
|
|
11
|
+
"procgen/NoSettings",
|
|
12
|
+
{},
|
|
13
|
+
{ description: "None: the generator, seed, and params are the file." }
|
|
14
|
+
);
|
|
15
|
+
function generatorHash(gen) {
|
|
16
|
+
return `${gen.version}:${codeHashOf(gen)}`;
|
|
17
|
+
}
|
|
18
|
+
const generatorDependency = defineImportDependency("generator:", (id) => {
|
|
19
|
+
const gen = findGenerator(id.slice("generator:".length));
|
|
20
|
+
return gen ? generatorHash(gen) : void 0;
|
|
21
|
+
});
|
|
22
|
+
const generatorSchema = defineAssetSchema("gen.schema.json", genFileSchema);
|
|
23
|
+
function parseGenFile(text, path) {
|
|
24
|
+
let json;
|
|
25
|
+
try {
|
|
26
|
+
json = JSON.parse(text);
|
|
27
|
+
} catch (cause) {
|
|
28
|
+
throw new ShardError("procgen/bad-file", `${path} isn't valid JSON`, { path: "", cause });
|
|
29
|
+
}
|
|
30
|
+
if (!isPlainObject(json)) {
|
|
31
|
+
throw new ShardError("procgen/bad-file", `${path} must be a JSON object`, { path: "" });
|
|
32
|
+
}
|
|
33
|
+
for (const key of Object.keys(json)) {
|
|
34
|
+
if (!["$schema", "generator", "seed", "params"].includes(key)) {
|
|
35
|
+
throw new ShardError("procgen/bad-file", `Unknown field "${key}"`, {
|
|
36
|
+
path: `/${key}`,
|
|
37
|
+
hint: 'A generator file has "generator", "seed", and "params".'
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
if (typeof json.generator !== "string" || json.generator === "") {
|
|
42
|
+
throw new ShardError("procgen/bad-file", '"generator" must name a generator', {
|
|
43
|
+
path: "/generator",
|
|
44
|
+
hint: 'e.g. "star-explorer/Rock" (.agents/generators.md lists them).'
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
const seed = json.seed ?? 0;
|
|
48
|
+
if (typeof seed !== "number" || !Number.isInteger(seed) || seed < 0 || seed > 4294967295) {
|
|
49
|
+
throw new ShardError("procgen/bad-params", '"seed" must be an integer from 0 to 4294967295', {
|
|
50
|
+
path: "/seed"
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
const params = json.params ?? {};
|
|
54
|
+
if (!isPlainObject(params)) {
|
|
55
|
+
throw new ShardError("procgen/bad-params", '"params" must be an object', { path: "/params" });
|
|
56
|
+
}
|
|
57
|
+
return { generator: json.generator, seed, params };
|
|
58
|
+
}
|
|
59
|
+
const GeneratorImporter = defineImporter({
|
|
60
|
+
name: "procgen",
|
|
61
|
+
version: 1,
|
|
62
|
+
extensions: [".gen.json"],
|
|
63
|
+
settings: NoSettings,
|
|
64
|
+
async import(source, ctx) {
|
|
65
|
+
const file = parseGenFile(source.text(), source.path);
|
|
66
|
+
let gen;
|
|
67
|
+
try {
|
|
68
|
+
gen = requireGenerator(file.generator);
|
|
69
|
+
} catch (err) {
|
|
70
|
+
throw new ShardError("procgen/unknown-generator", err.message, {
|
|
71
|
+
path: "/generator",
|
|
72
|
+
hint: err.hint
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
let request;
|
|
76
|
+
try {
|
|
77
|
+
request = requestOf(gen, file.params, file.seed);
|
|
78
|
+
} catch (err) {
|
|
79
|
+
const e = err;
|
|
80
|
+
throw new ShardError(e.code, e.message, {
|
|
81
|
+
path: e.path === "/seed" ? "/seed" : `/params${e.path ?? ""}`,
|
|
82
|
+
hint: e.hint,
|
|
83
|
+
details: e.details
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
ctx.depend(`generator:${gen.name}`, generatorHash(gen));
|
|
87
|
+
const deps = [];
|
|
88
|
+
for (const ref of paramHandles(gen, request.params)) {
|
|
89
|
+
if (!ref.path) continue;
|
|
90
|
+
const asset = await ctx.asset(`/${ref.path}`);
|
|
91
|
+
deps.push({ guid: asset.guid, path: ref.path, type: asset.type, artifact: asset.artifact });
|
|
92
|
+
}
|
|
93
|
+
const result = await runJob({ ...request, deps, chain: [] });
|
|
94
|
+
for (const w of result.warnings) ctx.warn(w.message, w.path);
|
|
95
|
+
const assets = result.assets.map((a) => {
|
|
96
|
+
const out = { label: a.label, type: a.type };
|
|
97
|
+
if (a.bytes) out.bytes = a.bytes;
|
|
98
|
+
if (a.json !== void 0) out.json = a.json;
|
|
99
|
+
const info = { ...a.info ?? {} };
|
|
100
|
+
if (a.label === "") {
|
|
101
|
+
info.generator = gen.name;
|
|
102
|
+
info.seed = request.seed;
|
|
103
|
+
info.ms = Math.round(result.ms * 10) / 10;
|
|
104
|
+
if (result.children.length > 0) info.generated = result.children.length;
|
|
105
|
+
}
|
|
106
|
+
out.info = info;
|
|
107
|
+
return out;
|
|
108
|
+
});
|
|
109
|
+
assets.push({
|
|
110
|
+
label: "Generator",
|
|
111
|
+
type: "Generator",
|
|
112
|
+
json: { generator: gen.name, seed: request.seed, params: request.params }
|
|
113
|
+
});
|
|
114
|
+
return { assets };
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
export {
|
|
118
|
+
GeneratorImporter,
|
|
119
|
+
generatorDependency,
|
|
120
|
+
generatorSchema,
|
|
121
|
+
parseGenFile
|
|
122
|
+
};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export { findNondeterminism, type NondeterministicCall } from './check.js';
|
|
2
|
+
export { type ContextState, createContext, type GenContext, guardNondeterminism, type LoadedDependency, type NoiseApi, } from './context.js';
|
|
3
|
+
export { allGenerators, canonicalParams, codeHashOf, defineGenerator, type Fragment, type FragmentEntity, findGenerator, type Generator, type GeneratorOptions, type GenParams, type GenRequest, guidOf, hashString, identityOf, type MeshResult, type OutputAsset, type OutputSpec, parseProceduralRef, proceduralPath, type RunResult, requestOf, requireGenerator, setGeneratorCodeHashes, type TextureData, } from './generator.js';
|
|
4
|
+
export { GeneratorImporter, type GenFile, parseGenFile } from './importer.js';
|
|
5
|
+
export { Generated, GeneratorInstance, instanceRequest } from './instance.js';
|
|
6
|
+
export { executeJob, type GenJob, type GenResult, type JobDependency, jobError, } from './job.js';
|
|
7
|
+
export { type MeshBuilderApi, meshBuilder } from './mesh-builder.js';
|
|
8
|
+
export { previewOutput, procgenMethods, type RunSummary, resolveGenerator, runGenerator, } from './methods.js';
|
|
9
|
+
export { encodeOutput, type GenOutputAsset, toJson } from './outputs.js';
|
|
10
|
+
export { type ProcgenPluginOptions, procgenPlugin } from './plugin.js';
|
|
11
|
+
export { decodeRecord, encodeRecord, type OutputRecord } from './record.js';
|
|
12
|
+
export { type CacheResult, configureProcgenHost, DataAssetType, GeneratedData, GeneratorAssetType, type GeneratorBinding, GeneratorBindings, generate, mainThreadTurn, type ProcgenHost, type ProcgenOptions, ProcgenResource, ProcgenRuntime, type ProcgenStats, paramHandles, procgen, procgenHost, procgenMainThreadMs, runJob, warmGeneratorWorkers, } from './runtime.js';
|
|
13
|
+
export { genFileSchema } from './schema.js';
|
|
14
|
+
export { contactSheet, drawLabel, parseSeeds } from './sheet.js';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { findNondeterminism } from "./check.js";
|
|
2
|
+
import {
|
|
3
|
+
createContext,
|
|
4
|
+
guardNondeterminism
|
|
5
|
+
} from "./context.js";
|
|
6
|
+
import {
|
|
7
|
+
allGenerators,
|
|
8
|
+
canonicalParams,
|
|
9
|
+
codeHashOf,
|
|
10
|
+
defineGenerator,
|
|
11
|
+
findGenerator,
|
|
12
|
+
guidOf,
|
|
13
|
+
hashString,
|
|
14
|
+
identityOf,
|
|
15
|
+
parseProceduralRef,
|
|
16
|
+
proceduralPath,
|
|
17
|
+
requestOf,
|
|
18
|
+
requireGenerator,
|
|
19
|
+
setGeneratorCodeHashes
|
|
20
|
+
} from "./generator.js";
|
|
21
|
+
import { GeneratorImporter, parseGenFile } from "./importer.js";
|
|
22
|
+
import { Generated, GeneratorInstance, instanceRequest } from "./instance.js";
|
|
23
|
+
import {
|
|
24
|
+
executeJob,
|
|
25
|
+
jobError
|
|
26
|
+
} from "./job.js";
|
|
27
|
+
import { meshBuilder } from "./mesh-builder.js";
|
|
28
|
+
import {
|
|
29
|
+
previewOutput,
|
|
30
|
+
procgenMethods,
|
|
31
|
+
resolveGenerator,
|
|
32
|
+
runGenerator
|
|
33
|
+
} from "./methods.js";
|
|
34
|
+
import { encodeOutput, toJson } from "./outputs.js";
|
|
35
|
+
import { procgenPlugin } from "./plugin.js";
|
|
36
|
+
import { decodeRecord, encodeRecord } from "./record.js";
|
|
37
|
+
import {
|
|
38
|
+
configureProcgenHost,
|
|
39
|
+
DataAssetType,
|
|
40
|
+
GeneratedData,
|
|
41
|
+
GeneratorAssetType,
|
|
42
|
+
GeneratorBindings,
|
|
43
|
+
generate,
|
|
44
|
+
mainThreadTurn,
|
|
45
|
+
ProcgenResource,
|
|
46
|
+
ProcgenRuntime,
|
|
47
|
+
paramHandles,
|
|
48
|
+
procgen,
|
|
49
|
+
procgenHost,
|
|
50
|
+
procgenMainThreadMs,
|
|
51
|
+
runJob,
|
|
52
|
+
warmGeneratorWorkers
|
|
53
|
+
} from "./runtime.js";
|
|
54
|
+
import { genFileSchema } from "./schema.js";
|
|
55
|
+
import { contactSheet, drawLabel, parseSeeds } from "./sheet.js";
|
|
56
|
+
export {
|
|
57
|
+
DataAssetType,
|
|
58
|
+
Generated,
|
|
59
|
+
GeneratedData,
|
|
60
|
+
GeneratorAssetType,
|
|
61
|
+
GeneratorBindings,
|
|
62
|
+
GeneratorImporter,
|
|
63
|
+
GeneratorInstance,
|
|
64
|
+
ProcgenResource,
|
|
65
|
+
ProcgenRuntime,
|
|
66
|
+
allGenerators,
|
|
67
|
+
canonicalParams,
|
|
68
|
+
codeHashOf,
|
|
69
|
+
configureProcgenHost,
|
|
70
|
+
contactSheet,
|
|
71
|
+
createContext,
|
|
72
|
+
decodeRecord,
|
|
73
|
+
defineGenerator,
|
|
74
|
+
drawLabel,
|
|
75
|
+
encodeOutput,
|
|
76
|
+
encodeRecord,
|
|
77
|
+
executeJob,
|
|
78
|
+
findGenerator,
|
|
79
|
+
findNondeterminism,
|
|
80
|
+
genFileSchema,
|
|
81
|
+
generate,
|
|
82
|
+
guardNondeterminism,
|
|
83
|
+
guidOf,
|
|
84
|
+
hashString,
|
|
85
|
+
identityOf,
|
|
86
|
+
instanceRequest,
|
|
87
|
+
jobError,
|
|
88
|
+
mainThreadTurn,
|
|
89
|
+
meshBuilder,
|
|
90
|
+
paramHandles,
|
|
91
|
+
parseGenFile,
|
|
92
|
+
parseProceduralRef,
|
|
93
|
+
parseSeeds,
|
|
94
|
+
previewOutput,
|
|
95
|
+
proceduralPath,
|
|
96
|
+
procgen,
|
|
97
|
+
procgenHost,
|
|
98
|
+
procgenMainThreadMs,
|
|
99
|
+
procgenMethods,
|
|
100
|
+
procgenPlugin,
|
|
101
|
+
requestOf,
|
|
102
|
+
requireGenerator,
|
|
103
|
+
resolveGenerator,
|
|
104
|
+
runGenerator,
|
|
105
|
+
runJob,
|
|
106
|
+
setGeneratorCodeHashes,
|
|
107
|
+
toJson,
|
|
108
|
+
warmGeneratorWorkers
|
|
109
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { type AssetEntry } from '@aethervtt/shard-assets';
|
|
2
|
+
import { type AssetRef, type JsonValue, type World } from '@aethervtt/shard-core';
|
|
3
|
+
import { type Generator, type GenRequest } from './generator.js';
|
|
4
|
+
/**
|
|
5
|
+
* Spawns an `entities` generator's fragment as this entity's children (and merges the fragment
|
|
6
|
+
* root's components into it), like a PrefabInstance. Changing `seed` or `params` regenerates; the
|
|
7
|
+
* children whose path is unchanged keep their entity.
|
|
8
|
+
*/
|
|
9
|
+
export declare const GeneratorInstance: import("@aethervtt/shard-core").ComponentDef<{
|
|
10
|
+
readonly generator: import("@aethervtt/shard-core").FieldType<AssetRef<"Generator"> | null, "object">;
|
|
11
|
+
readonly seed: import("@aethervtt/shard-core").FieldType<number, "u32">;
|
|
12
|
+
readonly params: import("@aethervtt/shard-core").FieldType<JsonValue, "object">;
|
|
13
|
+
readonly overrides: import("@aethervtt/shard-core").FieldType<JsonValue, "object">;
|
|
14
|
+
}>;
|
|
15
|
+
/** On every entity a GeneratorInstance spawned. Such entities are never written to scene files. */
|
|
16
|
+
export declare const Generated: import("@aethervtt/shard-core").TagDef;
|
|
17
|
+
/** The request an instance's value makes, or undefined while its generator file loads. */
|
|
18
|
+
export declare function instanceRequest(world: World, value: {
|
|
19
|
+
generator?: unknown;
|
|
20
|
+
seed?: unknown;
|
|
21
|
+
params?: unknown;
|
|
22
|
+
}): {
|
|
23
|
+
request: GenRequest;
|
|
24
|
+
gen: Generator;
|
|
25
|
+
} | {
|
|
26
|
+
pending: AssetEntry;
|
|
27
|
+
} | undefined;
|
|
28
|
+
export declare const generatorInstanceKind: import("@aethervtt/shard-scene").InstanceKind;
|
package/dist/instance.js
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { assetServer } from "@aethervtt/shard-assets";
|
|
2
|
+
import {
|
|
3
|
+
defineComponent,
|
|
4
|
+
defineTag,
|
|
5
|
+
isPlainObject,
|
|
6
|
+
pointer,
|
|
7
|
+
ShardError,
|
|
8
|
+
t
|
|
9
|
+
} from "@aethervtt/shard-core";
|
|
10
|
+
import { LogResource } from "@aethervtt/shard-runtime";
|
|
11
|
+
import { defineInstanceKind } from "@aethervtt/shard-scene";
|
|
12
|
+
import { findGenerator, requestOf } from "./generator.js";
|
|
13
|
+
import { GeneratorBindings, procgen } from "./runtime.js";
|
|
14
|
+
const GeneratorInstance = defineComponent(
|
|
15
|
+
"procgen/GeneratorInstance",
|
|
16
|
+
{
|
|
17
|
+
generator: t.handle("Generator", {
|
|
18
|
+
description: `The generator, by name ({ "path": "star-explorer/StarSystem" }), or a generator file's #Generator ({ "path": "generators/sol.gen.json#Generator" }), whose seed and params are defaults.`
|
|
19
|
+
}),
|
|
20
|
+
seed: t.u32({
|
|
21
|
+
description: "The seed. 0 uses the generator file's seed (0 for a generator by name)."
|
|
22
|
+
}),
|
|
23
|
+
params: t.json({
|
|
24
|
+
default: {},
|
|
25
|
+
description: "Params, checked against the generator's schema; missing ones take the file's or the defaults."
|
|
26
|
+
}),
|
|
27
|
+
overrides: t.json({
|
|
28
|
+
default: {},
|
|
29
|
+
description: "Changes to generated entities by path relative to this entity, as in scene/PrefabInstance. Saves write the children's runtime changes here."
|
|
30
|
+
})
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
description: "Spawns an entities generator's output as children (procgen/Generated). Patching seed or params regenerates on the pool; children at unchanged paths keep their entity ids. Saves write only this component."
|
|
34
|
+
}
|
|
35
|
+
);
|
|
36
|
+
const Generated = defineTag("procgen/Generated", {
|
|
37
|
+
description: "Spawned by a GeneratorInstance; rebuilt from the generator, never saved.",
|
|
38
|
+
serialize: false
|
|
39
|
+
});
|
|
40
|
+
function instanceRequest(world, value) {
|
|
41
|
+
const ref = value.generator;
|
|
42
|
+
if (!ref) return void 0;
|
|
43
|
+
const server = assetServer(world);
|
|
44
|
+
const entry = server.entry(ref);
|
|
45
|
+
if (!entry) {
|
|
46
|
+
throw new ShardError(
|
|
47
|
+
"procgen/unknown-generator",
|
|
48
|
+
`No generator ${JSON.stringify(ref.path ?? ref.guid)}`,
|
|
49
|
+
{ hint: 'Name a generator ("star-explorer/StarSystem") or a *.gen.json#Generator.' }
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
if (entry.state !== "loaded") {
|
|
53
|
+
if (entry.state === "unloaded") server.request(entry.guid);
|
|
54
|
+
return { pending: entry };
|
|
55
|
+
}
|
|
56
|
+
const binding = world.resource(GeneratorBindings).get(entry);
|
|
57
|
+
const gen = binding && findGenerator(binding.generator);
|
|
58
|
+
if (!gen) {
|
|
59
|
+
throw new ShardError("procgen/unknown-generator", `No generator "${binding?.generator}"`, {
|
|
60
|
+
hint: "Define it with project.generator in scripts/."
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
if (gen.output !== "entities") {
|
|
64
|
+
throw new ShardError(
|
|
65
|
+
"procgen/output-mismatch",
|
|
66
|
+
`GeneratorInstance needs an entities generator; ${gen.name} makes a ${gen.outputType}`,
|
|
67
|
+
{
|
|
68
|
+
hint: 'Use its output as a handle instead (a mesh: render/Mesh3d { "mesh": { "path": "procedural:\u2026" } }).'
|
|
69
|
+
}
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
const params = {
|
|
73
|
+
...binding.params ?? {},
|
|
74
|
+
...isPlainObject(value.params) ? value.params : {}
|
|
75
|
+
};
|
|
76
|
+
const seed = value.seed || binding.seed || 0;
|
|
77
|
+
return { request: requestOf(gen, params, seed), gen };
|
|
78
|
+
}
|
|
79
|
+
const reported = /* @__PURE__ */ new WeakMap();
|
|
80
|
+
const generatorInstanceKind = defineInstanceKind({
|
|
81
|
+
def: GeneratorInstance,
|
|
82
|
+
mark: Generated,
|
|
83
|
+
keepIds: true,
|
|
84
|
+
nested: true,
|
|
85
|
+
source(world, value, entity) {
|
|
86
|
+
let seen = reported.get(world);
|
|
87
|
+
if (!seen) {
|
|
88
|
+
seen = /* @__PURE__ */ new Map();
|
|
89
|
+
reported.set(world, seen);
|
|
90
|
+
}
|
|
91
|
+
try {
|
|
92
|
+
const made = instanceRequest(world, value);
|
|
93
|
+
if (!made) return void 0;
|
|
94
|
+
if ("pending" in made) return made.pending;
|
|
95
|
+
seen.delete(entity);
|
|
96
|
+
return procgen(world).entryFor(made.request);
|
|
97
|
+
} catch (err) {
|
|
98
|
+
const message = err.message;
|
|
99
|
+
if (seen.get(entity) !== message) {
|
|
100
|
+
seen.set(entity, message);
|
|
101
|
+
world.tryResource(LogResource)?.error(err);
|
|
102
|
+
}
|
|
103
|
+
return void 0;
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
validate(world, json, base, errors, options) {
|
|
107
|
+
if (!options.catalog) return;
|
|
108
|
+
const ref = json.generator;
|
|
109
|
+
if (!isPlainObject(ref)) return;
|
|
110
|
+
const entry = assetServer(world).entry(ref);
|
|
111
|
+
if (!entry) return;
|
|
112
|
+
const binding = entry.state === "loaded" ? world.resource(GeneratorBindings).get(entry) : void 0;
|
|
113
|
+
const gen = binding && findGenerator(binding.generator);
|
|
114
|
+
if (!gen) return;
|
|
115
|
+
if (gen.output !== "entities") {
|
|
116
|
+
errors.push(
|
|
117
|
+
new ShardError(
|
|
118
|
+
"procgen/output-mismatch",
|
|
119
|
+
`${gen.name} makes a ${gen.outputType}, not entities`,
|
|
120
|
+
{
|
|
121
|
+
path: pointer(base, "generator"),
|
|
122
|
+
hint: "GeneratorInstance spawns entities generators; put other outputs in a handle field (procedural:\u2026)."
|
|
123
|
+
}
|
|
124
|
+
)
|
|
125
|
+
);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
const params = {
|
|
129
|
+
...binding.params ?? {},
|
|
130
|
+
...isPlainObject(json.params) ? json.params : {}
|
|
131
|
+
};
|
|
132
|
+
for (const e of gen.params.validate(params)) {
|
|
133
|
+
errors.push(
|
|
134
|
+
new ShardError("procgen/bad-params", `${gen.name}: ${e.message}`, {
|
|
135
|
+
path: `${pointer(base, "params")}${e.path ?? ""}`,
|
|
136
|
+
hint: e.hint ?? `${gen.name}'s params are in .agents/generators.md.`
|
|
137
|
+
})
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
export {
|
|
143
|
+
Generated,
|
|
144
|
+
GeneratorInstance,
|
|
145
|
+
generatorInstanceKind,
|
|
146
|
+
instanceRequest
|
|
147
|
+
};
|
package/dist/job.d.ts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { type Artifact } from '@aethervtt/shard-assets';
|
|
2
|
+
import { type JsonValue } from '@aethervtt/shard-core';
|
|
3
|
+
import { type GenRequest } from './generator.js';
|
|
4
|
+
import { type GenOutputAsset } from './outputs.js';
|
|
5
|
+
/** An asset a job's params point at: its artifact (workers rebuild it) or the loaded object. */
|
|
6
|
+
export interface JobDependency {
|
|
7
|
+
guid: string | undefined;
|
|
8
|
+
path: string | undefined;
|
|
9
|
+
type: string;
|
|
10
|
+
artifact?: Artifact;
|
|
11
|
+
object?: unknown;
|
|
12
|
+
}
|
|
13
|
+
/** One generator run, as posted to a worker: all of it is structured-clonable. */
|
|
14
|
+
export interface GenJob extends GenRequest {
|
|
15
|
+
deps: JobDependency[];
|
|
16
|
+
/** Identities of the generators that asked for this one (cycle checks). */
|
|
17
|
+
chain: string[];
|
|
18
|
+
/** The noise kernel module, so a worker samples without fetching the .wasm. */
|
|
19
|
+
noise?: WebAssembly.Module;
|
|
20
|
+
noiseSimd?: boolean;
|
|
21
|
+
/** Guard the clock and Math.random for the whole run (workers), not just its first turn. */
|
|
22
|
+
guardAll?: boolean;
|
|
23
|
+
/** Check entities' components here (default true). Workers leave it to the main thread. */
|
|
24
|
+
checkComponents?: boolean;
|
|
25
|
+
}
|
|
26
|
+
export interface GenResult {
|
|
27
|
+
assets: GenOutputAsset[];
|
|
28
|
+
/** Outputs this one referenced through `ctx.generate`. */
|
|
29
|
+
children: GenRequest[];
|
|
30
|
+
warnings: {
|
|
31
|
+
message: string;
|
|
32
|
+
path?: string;
|
|
33
|
+
}[];
|
|
34
|
+
/** Time in `run` and encoding, in ms. */
|
|
35
|
+
ms: number;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Runs a generator job and encodes its output: the same code inline and on a worker. Throws
|
|
39
|
+
* `procgen/*` errors; anything else the generator throws becomes `procgen/generator-failed`.
|
|
40
|
+
*/
|
|
41
|
+
export declare function executeJob(job: GenJob): Promise<GenResult>;
|
|
42
|
+
/** Plain-object errors cross the worker boundary; this keeps code, hint, and path. */
|
|
43
|
+
export declare function jobError(err: unknown): {
|
|
44
|
+
code: string;
|
|
45
|
+
message: string;
|
|
46
|
+
hint?: string;
|
|
47
|
+
path?: string;
|
|
48
|
+
};
|
|
49
|
+
export type { GenOutputAsset, JsonValue };
|
|
50
|
+
/** The identity of a job's request (for logs and stats). */
|
|
51
|
+
export declare function jobIdentity(job: GenJob): string;
|
package/dist/job.js
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { findAssetType } from "@aethervtt/shard-assets";
|
|
2
|
+
import { ShardError } from "@aethervtt/shard-core";
|
|
3
|
+
import { useNoiseKernel } from "@aethervtt/shard-noise";
|
|
4
|
+
import {
|
|
5
|
+
createContext,
|
|
6
|
+
guardNondeterminism
|
|
7
|
+
} from "./context.js";
|
|
8
|
+
import { identityOf, requireGenerator } from "./generator.js";
|
|
9
|
+
import { encodeOutput } from "./outputs.js";
|
|
10
|
+
function wrap(err, job) {
|
|
11
|
+
if (err instanceof ShardError && err.code.startsWith("procgen/")) return err;
|
|
12
|
+
const message = err?.message ?? String(err);
|
|
13
|
+
return new ShardError(
|
|
14
|
+
"procgen/generator-failed",
|
|
15
|
+
`${job.generator} failed (seed ${job.seed}, params ${JSON.stringify(job.params)}): ${message}`,
|
|
16
|
+
{
|
|
17
|
+
hint: "The error is in the generator; procgen.run with the same seed and params reproduces it.",
|
|
18
|
+
cause: err
|
|
19
|
+
}
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
async function loadDependencies(job) {
|
|
23
|
+
const out = [];
|
|
24
|
+
for (const d of job.deps) {
|
|
25
|
+
if (d.object !== void 0) {
|
|
26
|
+
out.push({ guid: d.guid, path: d.path, type: d.type, object: d.object });
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
const type = findAssetType(d.type);
|
|
30
|
+
if (!type || !d.artifact) {
|
|
31
|
+
throw new ShardError(
|
|
32
|
+
"procgen/dependency-failed",
|
|
33
|
+
`${job.generator} needs ${d.path ?? d.guid}, a ${d.type}, which can't be loaded here`,
|
|
34
|
+
{ hint: "Add the plugin that defines the asset type to the project." }
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
const object = await type.load(d.artifact, {
|
|
38
|
+
guid: d.guid ?? "",
|
|
39
|
+
path: d.path ?? "",
|
|
40
|
+
resolve: () => void 0
|
|
41
|
+
});
|
|
42
|
+
out.push({ guid: d.guid, path: d.path, type: d.type, object });
|
|
43
|
+
}
|
|
44
|
+
return out;
|
|
45
|
+
}
|
|
46
|
+
async function executeJob(job) {
|
|
47
|
+
const gen = requireGenerator(job.generator);
|
|
48
|
+
if (job.noise) useNoiseKernel(job.noise, job.noiseSimd ?? true);
|
|
49
|
+
const deps = await loadDependencies(job);
|
|
50
|
+
const state = { children: [], warnings: [] };
|
|
51
|
+
const params = gen.params.deserialize(job.params);
|
|
52
|
+
const ctx = createContext(gen, job, deps, job.chain, state);
|
|
53
|
+
const start = performance.now();
|
|
54
|
+
let restore = guardNondeterminism(gen.name);
|
|
55
|
+
try {
|
|
56
|
+
let result = gen.run(ctx, params);
|
|
57
|
+
if (!job.guardAll) {
|
|
58
|
+
restore();
|
|
59
|
+
restore = void 0;
|
|
60
|
+
}
|
|
61
|
+
if (result instanceof Promise) result = await result;
|
|
62
|
+
const assets = encodeOutput(gen, result, { checkComponents: job.checkComponents });
|
|
63
|
+
restore?.();
|
|
64
|
+
restore = void 0;
|
|
65
|
+
return {
|
|
66
|
+
assets,
|
|
67
|
+
children: state.children,
|
|
68
|
+
warnings: state.warnings,
|
|
69
|
+
ms: performance.now() - start
|
|
70
|
+
};
|
|
71
|
+
} catch (err) {
|
|
72
|
+
throw wrap(err, job);
|
|
73
|
+
} finally {
|
|
74
|
+
restore?.();
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function jobError(err) {
|
|
78
|
+
const e = err;
|
|
79
|
+
return {
|
|
80
|
+
code: e?.code ?? "procgen/generator-failed",
|
|
81
|
+
message: e?.message ?? String(err),
|
|
82
|
+
...e?.hint ? { hint: e.hint } : {},
|
|
83
|
+
...e?.path ? { path: e.path } : {}
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
function jobIdentity(job) {
|
|
87
|
+
return identityOf(job);
|
|
88
|
+
}
|
|
89
|
+
export {
|
|
90
|
+
executeJob,
|
|
91
|
+
jobError,
|
|
92
|
+
jobIdentity
|
|
93
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type MeshData } from '@aethervtt/shard-mesh';
|
|
2
|
+
import type { MeshResult } from './generator.js';
|
|
3
|
+
/**
|
|
4
|
+
* Mesh building inside a generator. Deterministic across hosts: only +, *, and sqrt (no trig),
|
|
5
|
+
* so the same inputs give the same bytes in Node, Chrome, and WebKit.
|
|
6
|
+
*/
|
|
7
|
+
export interface MeshBuilderApi {
|
|
8
|
+
/**
|
|
9
|
+
* A unit icosphere (radius 1) subdivided `detail` times: 12 · 4^detail vertices (well, 10 · 4^d
|
|
10
|
+
* + 2), shared between faces, with normals.
|
|
11
|
+
*/
|
|
12
|
+
icosphere(detail: number): MeshData;
|
|
13
|
+
/** One of the built-in primitives (the `procedural:` meshes) as data you can change. */
|
|
14
|
+
primitive(name: 'box' | 'cube' | 'sphere' | 'plane' | 'cylinder' | 'cone' | 'capsule' | 'torus', params?: Record<string, number>): MeshData;
|
|
15
|
+
/** Recomputes smooth normals from the triangles (area-weighted). */
|
|
16
|
+
computeNormals(mesh: MeshData): MeshData;
|
|
17
|
+
/**
|
|
18
|
+
* Finishes a mesh: optional normals and tangents, and levels of detail as fractions of the
|
|
19
|
+
* triangle count (`lods: [0.5, 0.2]`), made by vertex clustering.
|
|
20
|
+
*/
|
|
21
|
+
finish(mesh: MeshData, options?: {
|
|
22
|
+
normals?: boolean;
|
|
23
|
+
tangents?: boolean;
|
|
24
|
+
lods?: readonly number[];
|
|
25
|
+
}): MeshResult;
|
|
26
|
+
}
|
|
27
|
+
export declare const meshBuilder: MeshBuilderApi;
|