@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.
@@ -0,0 +1,250 @@
1
+ import {
2
+ findComponent,
3
+ isPlainObject,
4
+ pointer,
5
+ ShardError
6
+ } from "@aethervtt/shard-core";
7
+ import { encodeMesh, Mesh } from "@aethervtt/shard-mesh";
8
+ import { PREFAB_VERSION } from "@aethervtt/shard-scene";
9
+ import { buildMips, writeKtx2 } from "@aethervtt/shard-texture";
10
+ function mismatch(gen, message, path, details) {
11
+ return new ShardError("procgen/output-mismatch", `${gen.name}: ${message}`, {
12
+ path,
13
+ hint: gen.output === "mesh" ? "Return ctx.mesh.finish(mesh) or mesh data ({ positions, normals?, indices? })." : gen.output === "texture" ? 'Return { width, height, mips: [rgba8 bytes] } (Float32Array levels for usage "hdr").' : gen.output === "entities" ? "Return an entity { name, components, children } or a list of them; components by name with JSON values." : `Return a value of ${gen.outputType}.`,
14
+ details
15
+ });
16
+ }
17
+ function round(n) {
18
+ return Math.round(n * 1e4) / 1e4;
19
+ }
20
+ function meshInfo(mesh) {
21
+ const b = mesh.bounds;
22
+ return {
23
+ vertices: mesh.vertexCount,
24
+ triangles: Math.floor(mesh.drawCount / 3),
25
+ bounds: {
26
+ min: [round(b[0]), round(b[1]), round(b[2])],
27
+ max: [round(b[3]), round(b[4]), round(b[5])]
28
+ }
29
+ };
30
+ }
31
+ function checkedMesh(gen, data, what) {
32
+ try {
33
+ return Mesh.create(data);
34
+ } catch (err) {
35
+ throw mismatch(gen, `${what}: ${err.message}`);
36
+ }
37
+ }
38
+ function encodeMeshOutput(gen, value) {
39
+ const result = value;
40
+ const main = result?.kind === "mesh" ? result.mesh : result;
41
+ if (!main || !(main.positions instanceof Float32Array)) {
42
+ throw mismatch(gen, `run returned ${describe(value)}, not a mesh`);
43
+ }
44
+ const mesh = checkedMesh(gen, main, "the mesh");
45
+ const lods = result?.kind === "mesh" ? result.lods ?? [] : [];
46
+ const info = meshInfo(mesh);
47
+ const out = [{ label: "", type: "Mesh", bytes: encodeMesh(mesh), info }];
48
+ const lodInfo = [];
49
+ lods.forEach((lod, i) => {
50
+ const m = checkedMesh(gen, lod, `LOD ${i + 1}`);
51
+ const li = meshInfo(m);
52
+ lodInfo.push(li.triangles);
53
+ out.push({ label: `LOD${i + 1}`, type: "Mesh", bytes: encodeMesh(m), info: li });
54
+ });
55
+ if (lodInfo.length) info.lods = lodInfo;
56
+ return out;
57
+ }
58
+ function encodeTextureOutput(gen, value) {
59
+ const tex = value;
60
+ const w = tex?.width;
61
+ const h = tex?.height;
62
+ if (!tex || !Number.isInteger(w) || !Number.isInteger(h) || !Array.isArray(tex.mips)) {
63
+ throw mismatch(gen, `run returned ${describe(value)}, not a texture`);
64
+ }
65
+ const usage = tex.usage ?? "color";
66
+ const hdr = usage === "hdr";
67
+ const level0 = tex.mips[0];
68
+ const expected = w * h * 4;
69
+ if (!level0 || level0.length !== expected || hdr !== level0 instanceof Float32Array) {
70
+ throw mismatch(
71
+ gen,
72
+ `level 0 must be ${expected} ${hdr ? "floats (Float32Array)" : "bytes (Uint8Array)"} for ${w}\xD7${h} RGBA`
73
+ );
74
+ }
75
+ const chain = tex.mipmaps && tex.mips.length === 1 ? buildMips(
76
+ { width: w, height: h, kind: hdr ? "f32" : "u8", data: level0 },
77
+ { usage, mipmaps: true, maxSize: 16384, flipY: false, premultiplyAlpha: false }
78
+ ) : { width: w, height: h, levels: tex.mips };
79
+ return [
80
+ {
81
+ label: "",
82
+ type: "Texture",
83
+ bytes: writeKtx2(chain, usage),
84
+ info: { width: w, height: h, usage, mips: chain.levels.length }
85
+ }
86
+ ];
87
+ }
88
+ function encodeDataOutput(gen, value) {
89
+ if (gen.dataType) {
90
+ const json = toJson(value);
91
+ const errors = gen.dataType.validate(json);
92
+ if (errors.length > 0) {
93
+ throw mismatch(
94
+ gen,
95
+ `the value isn't a ${gen.outputType}: ${errors[0].message}`,
96
+ errors[0].path,
97
+ errors
98
+ );
99
+ }
100
+ return [
101
+ {
102
+ label: "",
103
+ type: gen.outputType,
104
+ json: gen.dataType.serialize(gen.dataType.deserialize(json))
105
+ }
106
+ ];
107
+ }
108
+ if (value === void 0) throw mismatch(gen, "run returned undefined");
109
+ return [{ label: "", type: "Data", json: toJson(value) }];
110
+ }
111
+ function toJson(value) {
112
+ if (value === null || typeof value !== "object") {
113
+ return typeof value === "number" && !Number.isFinite(value) ? null : value;
114
+ }
115
+ if (ArrayBuffer.isView(value)) return Array.from(value);
116
+ if (Array.isArray(value)) return value.map(toJson);
117
+ const obj = value;
118
+ const keys = Object.keys(obj);
119
+ const isRef = typeof obj.type === "string" && (typeof obj.guid === "string" || typeof obj.path === "string") && keys.every((k) => k === "type" || k === "guid" || k === "path");
120
+ const out = {};
121
+ for (const k of keys) {
122
+ if (isRef && k === "type") continue;
123
+ if (obj[k] !== void 0) out[k] = toJson(obj[k]);
124
+ }
125
+ return out;
126
+ }
127
+ function describe(value) {
128
+ if (value === null) return "null";
129
+ if (Array.isArray(value)) return "an array";
130
+ if (ArrayBuffer.isView(value)) return `a ${value.constructor.name}`;
131
+ return typeof value === "object" ? "an object" : `a ${typeof value}`;
132
+ }
133
+ function encodeEntities(gen, value, check) {
134
+ const fragment = value;
135
+ const root = Array.isArray(fragment) ? { name: gen.name.slice(gen.name.indexOf("/") + 1), children: fragment } : fragment;
136
+ if (!isPlainObject(root)) throw mismatch(gen, `run returned ${describe(value)}, not entities`);
137
+ const errors = [];
138
+ const counts = {};
139
+ let entities = 0;
140
+ const walk = (e, at, isRoot) => {
141
+ const out = {};
142
+ if (typeof e.name !== "string" || e.name === "" || e.name.includes("/")) {
143
+ errors.push(
144
+ new ShardError(
145
+ "procgen/output-mismatch",
146
+ 'Every entity needs a "name" (non-empty, no "/")',
147
+ {
148
+ path: `${at}/name`
149
+ }
150
+ )
151
+ );
152
+ }
153
+ out.name = e.name;
154
+ if (!isRoot) entities++;
155
+ if (e.components !== void 0) {
156
+ const comps = {};
157
+ for (const [name, raw] of Object.entries(e.components)) {
158
+ const where = pointer(`${at}/components`, name);
159
+ if (!check) {
160
+ comps[name] = toJson(raw);
161
+ counts[name] = (counts[name] ?? 0) + 1;
162
+ continue;
163
+ }
164
+ let def;
165
+ try {
166
+ def = findComponent(name);
167
+ } catch {
168
+ def = void 0;
169
+ }
170
+ if (!def?.serializable) {
171
+ errors.push(
172
+ new ShardError("procgen/output-mismatch", `Unknown component "${name}"`, {
173
+ path: where,
174
+ hint: 'Components are by registered name, e.g. "core/Transform" (see .agents/components.md).'
175
+ })
176
+ );
177
+ continue;
178
+ }
179
+ const json2 = toJson(raw);
180
+ for (const err of def.validate(json2)) {
181
+ errors.push(
182
+ new ShardError("procgen/output-mismatch", err.message, {
183
+ path: `${where}${err.path ?? ""}`,
184
+ hint: err.hint
185
+ })
186
+ );
187
+ }
188
+ comps[name] = json2;
189
+ counts[name] = (counts[name] ?? 0) + 1;
190
+ }
191
+ out.components = comps;
192
+ }
193
+ if (e.children !== void 0) {
194
+ if (!Array.isArray(e.children)) {
195
+ errors.push(
196
+ new ShardError("procgen/output-mismatch", '"children" must be a list', {
197
+ path: `${at}/children`
198
+ })
199
+ );
200
+ } else {
201
+ const names = /* @__PURE__ */ new Set();
202
+ out.children = e.children.map((c, i) => {
203
+ if (names.has(c?.name)) {
204
+ errors.push(
205
+ new ShardError("procgen/output-mismatch", `Two children are named "${c.name}"`, {
206
+ path: `${at}/children/${i}/name`,
207
+ hint: "Sibling names must be unique: they form the paths overrides and references use."
208
+ })
209
+ );
210
+ }
211
+ names.add(c?.name);
212
+ return walk(c, `${at}/children/${i}`, false);
213
+ });
214
+ }
215
+ }
216
+ return out;
217
+ };
218
+ const json = walk(root, "/root", true);
219
+ if (errors.length > 0) {
220
+ throw mismatch(gen, `${errors[0].path}: ${errors[0].message}`, errors[0].path, errors);
221
+ }
222
+ return [
223
+ {
224
+ label: "",
225
+ type: "Prefab",
226
+ json: { version: PREFAB_VERSION, root: json },
227
+ info: {
228
+ entities,
229
+ components: Object.fromEntries(
230
+ Object.entries(counts).sort(([a], [b]) => a.localeCompare(b))
231
+ )
232
+ }
233
+ }
234
+ ];
235
+ }
236
+ function encodeOutput(gen, value, options = {}) {
237
+ if (gen.output === "mesh") return encodeMeshOutput(gen, value);
238
+ if (gen.output === "texture") return encodeTextureOutput(gen, value);
239
+ if (gen.output === "entities") return encodeEntities(gen, value, options.checkComponents ?? true);
240
+ return encodeDataOutput(gen, value);
241
+ }
242
+ function checkFragment(gen, asset) {
243
+ const root = asset.json?.root;
244
+ encodeEntities(gen, root, true);
245
+ }
246
+ export {
247
+ checkFragment,
248
+ encodeOutput,
249
+ toJson
250
+ };
@@ -0,0 +1,11 @@
1
+ import type { KeyValueStorage, PlatformFileSystem } from '@aethervtt/shard-platform';
2
+ export interface ProcgenPluginOptions {
3
+ /** Project files: outputs are cached in `.shard/cache/generated/` when writable. */
4
+ fs?: PlatformFileSystem;
5
+ /** Where outputs are cached on hosts that can't write files (IndexedDB in browsers). */
6
+ storage?: KeyValueStorage;
7
+ /** Bytes of outputs kept in memory (and in storage). Default 256 MB (`shard.json` procgen.cacheSize). */
8
+ cacheSize?: number;
9
+ }
10
+ /** Generator outputs for an app: the runtime and its caches, GeneratorInstance, procgen.* methods. */
11
+ export declare function procgenPlugin(options?: ProcgenPluginOptions): import("@aethervtt/shard-runtime").Plugin;
package/dist/plugin.js ADDED
@@ -0,0 +1,30 @@
1
+ import { assetServer } from "@aethervtt/shard-assets";
2
+ import { definePlugin } from "@aethervtt/shard-runtime";
3
+ import { updateInstances } from "@aethervtt/shard-scene";
4
+ import * as importerModule from "./importer.js";
5
+ import * as instanceModule from "./instance.js";
6
+ import { procgenMethods } from "./methods.js";
7
+ import * as refsModule from "./refs.js";
8
+ import * as runtimeModule from "./runtime.js";
9
+ import { procgen } from "./runtime.js";
10
+ function procgenPlugin(options = {}) {
11
+ return definePlugin({
12
+ name: "procgen",
13
+ provides: [importerModule, instanceModule, refsModule, runtimeModule],
14
+ build(app) {
15
+ const runtime = procgen(app.world).configure({
16
+ ...options.fs ? { fs: options.fs } : {},
17
+ ...options.storage ? { storage: options.storage } : {},
18
+ ...options.cacheSize ? { cacheSize: options.cacheSize } : {}
19
+ });
20
+ runtime.server.onEvent((e) => {
21
+ if (e.kind !== "loaded" && e.kind !== "modified") return;
22
+ if (assetServer(app.world).entry(e.guid)?.type === "Generator") updateInstances(app.world);
23
+ });
24
+ app.addMethod(...procgenMethods);
25
+ }
26
+ });
27
+ }
28
+ export {
29
+ procgenPlugin
30
+ };
@@ -0,0 +1,18 @@
1
+ import type { GenRequest } from './generator.js';
2
+ import type { GenOutputAsset } from './outputs.js';
3
+ /** A generator run's output, as cached: content-addressed by its key. */
4
+ export interface OutputRecord {
5
+ key: string;
6
+ assets: GenOutputAsset[];
7
+ children: GenRequest[];
8
+ warnings: {
9
+ message: string;
10
+ path?: string;
11
+ }[];
12
+ ms: number;
13
+ bytes: number;
14
+ }
15
+ export declare function recordBytes(assets: readonly GenOutputAsset[]): number;
16
+ /** One file per record: magic, header length, JSON header, then each asset's bytes (4-aligned). */
17
+ export declare function encodeRecord(record: OutputRecord): Uint8Array;
18
+ export declare function decodeRecord(data: Uint8Array): OutputRecord | undefined;
package/dist/record.js ADDED
@@ -0,0 +1,66 @@
1
+ const MAGIC = 1313294419;
2
+ const encoder = new TextEncoder();
3
+ const decoder = new TextDecoder();
4
+ function recordBytes(assets) {
5
+ let n = 256;
6
+ for (const a of assets) {
7
+ n += a.bytes?.byteLength ?? 0;
8
+ if (a.json !== void 0) n += JSON.stringify(a.json).length;
9
+ }
10
+ return n;
11
+ }
12
+ function encodeRecord(record) {
13
+ const blobs = [];
14
+ let offset = 0;
15
+ const assets = record.assets.map((a) => {
16
+ const { bytes, ...rest } = a;
17
+ if (!bytes) return rest;
18
+ const at2 = offset;
19
+ blobs.push(bytes);
20
+ offset += bytes.byteLength + 3 & ~3;
21
+ return { ...rest, bytes: [at2, bytes.byteLength] };
22
+ });
23
+ const header = encoder.encode(
24
+ JSON.stringify({
25
+ key: record.key,
26
+ assets,
27
+ children: record.children,
28
+ warnings: record.warnings,
29
+ ms: record.ms
30
+ })
31
+ );
32
+ const head = 8 + header.byteLength + 3 & ~3;
33
+ const out = new Uint8Array(head + offset);
34
+ const view = new DataView(out.buffer);
35
+ view.setUint32(0, MAGIC, true);
36
+ view.setUint32(4, header.byteLength, true);
37
+ out.set(header, 8);
38
+ let at = head;
39
+ for (const b of blobs) {
40
+ out.set(b, at);
41
+ at += b.byteLength + 3 & ~3;
42
+ }
43
+ return out;
44
+ }
45
+ function decodeRecord(data) {
46
+ if (data.byteLength < 8) return void 0;
47
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
48
+ if (view.getUint32(0, true) !== MAGIC) return void 0;
49
+ const length = view.getUint32(4, true);
50
+ const head = 8 + length + 3 & ~3;
51
+ const json = JSON.parse(decoder.decode(data.subarray(8, 8 + length)));
52
+ const assets = json.assets.map((a) => {
53
+ if (!a.bytes) return a;
54
+ const [at, n] = a.bytes;
55
+ return {
56
+ ...a,
57
+ bytes: new Uint8Array(data.buffer, data.byteOffset + head + at, n)
58
+ };
59
+ });
60
+ return { ...json, assets, bytes: recordBytes(assets) };
61
+ }
62
+ export {
63
+ decodeRecord,
64
+ encodeRecord,
65
+ recordBytes
66
+ };
package/dist/refs.d.ts ADDED
@@ -0,0 +1,11 @@
1
+ export declare const refParser: (spec: string) => import("./generator.js").GenRequest;
2
+ /**
3
+ * `procedural:<generator>?params&seed=N` in scenes and prefabs: an output of a project or engine
4
+ * generator. Equal canonical params (and seed) are one asset.
5
+ */
6
+ export declare const generatorSource: import("@aethervtt/shard-scene").ProceduralSource;
7
+ /**
8
+ * The catalog knows generators by name (`star-explorer/Rock`, a `Generator` asset) and outputs by
9
+ * `procedural:` path, without anything imported.
10
+ */
11
+ export declare const generatorResolver: import("@aethervtt/shard-assets").AssetResolver;
package/dist/refs.js ADDED
@@ -0,0 +1,42 @@
1
+ import { defineAssetResolver } from "@aethervtt/shard-assets";
2
+ import { defineProceduralSource } from "@aethervtt/shard-scene";
3
+ import { findGenerator, parseProceduralRef } from "./generator.js";
4
+ import { procgen, setRefParser } from "./runtime.js";
5
+ const GENERATOR_NAME = /^[a-z][a-z0-9-]*\/[A-Za-z][A-Za-z0-9]*$/;
6
+ function label(spec) {
7
+ const hash = spec.indexOf("#");
8
+ return hash === -1 ? "" : spec.slice(hash + 1);
9
+ }
10
+ const refParser = setRefParser((spec) => parseProceduralRef(spec));
11
+ const generatorSource = defineProceduralSource({
12
+ handles: (name) => name.includes("/"),
13
+ check(spec, path) {
14
+ const request = parseProceduralRef(spec, path);
15
+ const sub = label(spec);
16
+ return { type: sub === "" ? findGenerator(request.generator).outputType : "Mesh" };
17
+ },
18
+ resolve(world, spec) {
19
+ const entry = procgen(world).entryFor(parseProceduralRef(spec), label(spec));
20
+ return { guid: entry.guid, path: entry.path, type: entry.type };
21
+ }
22
+ });
23
+ const generatorResolver = defineAssetResolver((server, ref) => {
24
+ if (ref.startsWith("procedural:")) {
25
+ const spec = ref.slice("procedural:".length);
26
+ const name2 = spec.split(/[?#]/)[0];
27
+ if (!findGenerator(name2)) return void 0;
28
+ try {
29
+ return procgen(server.world).entryFor(parseProceduralRef(spec), label(spec));
30
+ } catch {
31
+ return void 0;
32
+ }
33
+ }
34
+ const name = ref.startsWith("generator:") ? ref.slice("generator:".length) : ref;
35
+ if (!GENERATOR_NAME.test(name) || !findGenerator(name)) return void 0;
36
+ return server.virtual(`generator:${name}`, name, "Generator", () => ({ generator: name }));
37
+ });
38
+ export {
39
+ generatorResolver,
40
+ generatorSource,
41
+ refParser
42
+ };
@@ -0,0 +1,159 @@
1
+ import { type AssetEntry, type AssetServer, AssetStore } from '@aethervtt/shard-assets';
2
+ import { type AssetRef, type Fields, type JsonValue, type World } from '@aethervtt/shard-core';
3
+ import type { KeyValueStorage, PlatformFileSystem, Workers } from '@aethervtt/shard-platform';
4
+ import { type Generator, type GenParams, type GenRequest, identityOf, type OutputAsset, type OutputSpec } from './generator.js';
5
+ import { type GenJob, type GenResult } from './job.js';
6
+ import { type OutputRecord } from './record.js';
7
+ /**
8
+ * What a `Generator` asset names: a generator, plus defaults for its seed and params (a
9
+ * `*.gen.json` file's `#Generator` sub-asset). Generators by name (`star-explorer/Rock`) are
10
+ * `Generator` assets with just the name.
11
+ */
12
+ export interface GeneratorBinding {
13
+ generator: string;
14
+ seed?: number;
15
+ params?: Record<string, JsonValue>;
16
+ }
17
+ export declare const GeneratorBindings: import("@aethervtt/shard-core").ResourceDef<AssetStore<GeneratorBinding, "Generator">>;
18
+ export declare const GeneratorAssetType: import("@aethervtt/shard-assets").AssetTypeDef<GeneratorBinding>;
19
+ /** Untyped `data` outputs, by guid. */
20
+ export declare const GeneratedData: import("@aethervtt/shard-core").ResourceDef<AssetStore<JsonValue, "Data">>;
21
+ export declare const DataAssetType: import("@aethervtt/shard-assets").AssetTypeDef<JsonValue>;
22
+ export interface ProcgenHost {
23
+ /** The worker pool generators run on. */
24
+ workers?: Workers;
25
+ /**
26
+ * URL of a module exporting `runGeneratorJob` with every generator defined (the project bundle
27
+ * plus `@aethervtt/shard-procgen/worker`, built self-contained by the host). Without it, jobs run inline.
28
+ */
29
+ workerModule?: string | Promise<string>;
30
+ }
31
+ /** Tells procgen where jobs run (workers and the worker bundle). Hosts call it at startup. */
32
+ export declare function configureProcgenHost(options: ProcgenHost): void;
33
+ export declare function procgenHost(): Readonly<ProcgenHost>;
34
+ /**
35
+ * Starts every worker and loads the worker module in it, so the first jobs don't pay for thread
36
+ * startup. Resolves once all are ready; hosts can call it without waiting.
37
+ */
38
+ export declare function warmGeneratorWorkers(): Promise<void>;
39
+ /**
40
+ * Milliseconds of main-thread procgen work so far (the steps `mainThreadTurn` ran): sample it
41
+ * each frame to see what generation costs the frame.
42
+ */
43
+ export declare function procgenMainThreadMs(): number;
44
+ /**
45
+ * Waits for a turn on the main thread: steps run back to back until about a quarter of a millisecond has
46
+ * passed, then yield to the event loop, so a burst of finished jobs spreads over frames.
47
+ */
48
+ export declare function mainThreadTurn(): Promise<void>;
49
+ /**
50
+ * Runs a job: on the worker pool when the host gave a worker module and every dependency travels
51
+ * as an artifact, otherwise inline (after an await, so callers never block on it). At most two
52
+ * jobs per worker are posted at once; the rest wait their turn.
53
+ */
54
+ export declare function runJob(job: GenJob): Promise<GenResult & {
55
+ packed?: Uint8Array;
56
+ }>;
57
+ export interface ProcgenOptions {
58
+ /** Project files: records are written to `<cacheDir>/generated/` when it's writable. */
59
+ fs?: PlatformFileSystem;
60
+ /** Where records go on hosts that can't write files (browsers: IndexedDB). */
61
+ storage?: KeyValueStorage;
62
+ /** Default `.shard/cache`. */
63
+ cacheDir?: string;
64
+ /** Bytes of records kept in memory, and in `storage`. Default 256 MB. */
65
+ cacheSize?: number;
66
+ }
67
+ /** What happened to one output the last time it was made. */
68
+ export type CacheResult = 'memory' | 'disk' | 'run' | 'shared';
69
+ export interface ProcgenStats {
70
+ runs: number;
71
+ memoryHits: number;
72
+ diskHits: number;
73
+ failures: number;
74
+ /** Main-thread ms spent turning finished jobs into assets (bench: per frame). */
75
+ mainMs: number;
76
+ }
77
+ /** Handles in a request's params, in field order. */
78
+ export declare function paramHandles(gen: Generator, params: JsonValue): {
79
+ guid?: string;
80
+ path?: string;
81
+ }[];
82
+ /**
83
+ * Generator outputs for one world: each request is a virtual asset `gen:<identity>` (with sub-assets
84
+ * like `gen:<identity>/LOD1`), made from a content-addressed record that's looked up in memory,
85
+ * then on disk (or platform storage), and generated on the pool when it's in neither.
86
+ */
87
+ export declare class ProcgenRuntime {
88
+ readonly world: World;
89
+ readonly server: AssetServer;
90
+ options: Required<Pick<ProcgenOptions, 'cacheDir' | 'cacheSize'>> & ProcgenOptions;
91
+ readonly stats: ProcgenStats;
92
+ private readonly memory;
93
+ private memoryBytes;
94
+ private readonly stored;
95
+ private storedBytes;
96
+ private readonly inflight;
97
+ private readonly live;
98
+ private readonly requests;
99
+ /** Imported dependencies' artifacts and hashes, by guid (valid while the version matches). */
100
+ private readonly artifacts;
101
+ private pendingDeps;
102
+ constructor(world: World, options?: ProcgenOptions);
103
+ configure(options: ProcgenOptions): this;
104
+ /** The output entry of a request (label '' or a sub-asset like 'LOD1'), registered lazily. */
105
+ entryFor(request: GenRequest, label?: string): AssetEntry;
106
+ /** Generates (or finds cached) an output and resolves with its ref once it's loaded. */
107
+ generate<const P extends Fields, const O extends OutputSpec>(gen: Generator<P, O>, params?: GenParams<P>, seed?: number): Promise<AssetRef<OutputAsset<O>>>;
108
+ /** What the last make of an output did: cache result, key, and time. */
109
+ lastResult(guid: string): {
110
+ hit: CacheResult | undefined;
111
+ key: string | undefined;
112
+ ms: number;
113
+ } | undefined;
114
+ /** The cached record behind a loaded output, if it's still in memory. */
115
+ recordOf(guid: string): OutputRecord | undefined;
116
+ /** `produce`, with failures logged (a GeneratorInstance or scene waiting on it shows why). */
117
+ private produceLogged;
118
+ /** Makes the object of `gen:<identity>[/label]`: the virtual asset's `create`. */
119
+ private produce;
120
+ private requestOfPath;
121
+ /** The dependencies of a request: loaded, with their artifacts (for workers) and hashes. */
122
+ private dependencies;
123
+ private recordFor;
124
+ private remember;
125
+ private storedPath;
126
+ private readStored;
127
+ private writeStored;
128
+ private regenerate;
129
+ private regenerateForDeps;
130
+ /**
131
+ * Regenerates loaded outputs whose generator's code hash changed (or whose generator was
132
+ * redefined) and those whose dependencies reloaded. They swap in place when ready; returns their
133
+ * paths.
134
+ */
135
+ regenerateStale(): string[];
136
+ /** Jobs running now, for procgen.describe. */
137
+ inFlight(): {
138
+ generator: string;
139
+ key: string;
140
+ ms: number;
141
+ }[];
142
+ cacheStats(): {
143
+ records: number;
144
+ bytes: number;
145
+ limit: number;
146
+ live: number;
147
+ };
148
+ }
149
+ /** Set by the package index (keeps a module cycle out of the runtime). */
150
+ export declare function setRefParser(parse: (spec: string) => GenRequest): (spec: string) => GenRequest;
151
+ export declare const ProcgenResource: import("@aethervtt/shard-core").ResourceDef<ProcgenRuntime>;
152
+ /** The world's procgen runtime, created (memory cache only) on first use. */
153
+ export declare function procgen(world: World): ProcgenRuntime;
154
+ /**
155
+ * Makes a generator's output at runtime (one planet per star) and resolves with a ref to it once
156
+ * it's loaded: cached in memory and on disk by the hash of its inputs, generated on the worker pool.
157
+ */
158
+ export declare function generate<const P extends Fields, const O extends OutputSpec>(world: World, gen: Generator<P, O>, params?: GenParams<P>, seed?: number): Promise<AssetRef<OutputAsset<O>>>;
159
+ export { identityOf };