@forgeax/engine-render 0.1.26 → 0.1.28
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/README.md +215 -0
- package/dist/assembly/dynamic-geometry-host.d.ts +31 -0
- package/dist/assembly/dynamic-geometry-host.d.ts.map +1 -0
- package/dist/assembly/host-contract.d.ts +11 -0
- package/dist/assembly/host-contract.d.ts.map +1 -1
- package/dist/assembly/renderer-facade.d.ts.map +1 -1
- package/dist/assembly/webgpu-renderer.d.ts.map +1 -1
- package/dist/authoring.mjs +2 -2
- package/dist/{chunk-FJ6P52EE.mjs → chunk-ILQ5MEZF.mjs} +3 -17
- package/dist/chunk-ILQ5MEZF.mjs.map +1 -0
- package/dist/{chunk-HKXTW355.mjs → chunk-IQMKJLNU.mjs} +3 -3
- package/dist/{chunk-HKXTW355.mjs.map → chunk-IQMKJLNU.mjs.map} +1 -1
- package/dist/{chunk-KM2NOX2I.mjs → chunk-J52NMQKR.mjs} +462 -6
- package/dist/chunk-J52NMQKR.mjs.map +1 -0
- package/dist/{chunk-ZZ4YQ474.mjs → chunk-J6KACNOH.mjs} +2 -2
- package/dist/{chunk-ZZ4YQ474.mjs.map → chunk-J6KACNOH.mjs.map} +1 -1
- package/dist/{chunk-ADHHVYLW.mjs → chunk-JAASCMXA.mjs} +3 -3
- package/dist/{chunk-ADHHVYLW.mjs.map → chunk-JAASCMXA.mjs.map} +1 -1
- package/dist/{chunk-TMIASV2N.mjs → chunk-MBU4ZVE7.mjs} +20 -12
- package/dist/chunk-MBU4ZVE7.mjs.map +1 -0
- package/dist/{chunk-E34VL5VI.mjs → chunk-MYJ5RPJO.mjs} +3 -3
- package/dist/{chunk-E34VL5VI.mjs.map → chunk-MYJ5RPJO.mjs.map} +1 -1
- package/dist/{chunk-JDWARUOI.mjs → chunk-RBDBZ5R7.mjs} +17 -3
- package/dist/chunk-RBDBZ5R7.mjs.map +1 -0
- package/dist/{chunk-YUQXSEHG.mjs → chunk-VSL23LZE.mjs} +26 -12
- package/dist/chunk-VSL23LZE.mjs.map +1 -0
- package/dist/construct-renderer.mjs +862 -106
- package/dist/construct-renderer.mjs.map +1 -1
- package/dist/device/gpu-residency.d.ts +10 -5
- package/dist/device/gpu-residency.d.ts.map +1 -1
- package/dist/device/mesh-residency-lifetime.d.ts +21 -0
- package/dist/device/mesh-residency-lifetime.d.ts.map +1 -0
- package/dist/dynamic-geometry.d.ts +125 -0
- package/dist/dynamic-geometry.d.ts.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +7 -6
- package/dist/index.mjs.map +1 -1
- package/dist/internal.mjs +7 -7
- package/dist/record/frame.d.ts +1 -1
- package/dist/record/frame.d.ts.map +1 -1
- package/dist/record/main-pass-geometry.d.ts.map +1 -1
- package/dist/record/render-context.d.ts +2 -0
- package/dist/record/render-context.d.ts.map +1 -1
- package/dist/record/typed-frame-graph.d.ts.map +1 -1
- package/dist/render-contract.d.ts +12 -0
- package/dist/render-contract.d.ts.map +1 -1
- package/dist/render-system-extract-tail.d.ts.map +1 -1
- package/dist/render-system-extract.d.ts +6 -0
- package/dist/render-system-extract.d.ts.map +1 -1
- package/dist/render-system.d.ts +7 -0
- package/dist/render-system.d.ts.map +1 -1
- package/dist/temporal/index.mjs +4 -4
- package/package.json +20 -20
- package/src/__tests__/dynamic-geometry-host.unit.test.ts +1123 -0
- package/src/__tests__/dynamic-geometry.unit.test.ts +233 -0
- package/src/__tests__/factory-contract.integration.test.ts +6 -14
- package/src/__tests__/mesh-submission-lifetime.unit.test.ts +53 -0
- package/src/__tests__/renderer-factory-material-contract.unit.test.ts +1 -1
- package/src/assembly/dynamic-geometry-host.ts +839 -0
- package/src/assembly/host-contract.ts +26 -0
- package/src/assembly/renderer-facade.ts +17 -0
- package/src/assembly/webgpu-renderer.ts +49 -2
- package/src/components/sprite-animation.ts +1 -1
- package/src/device/gpu-residency.ts +62 -62
- package/src/device/mesh-residency-lifetime.ts +73 -0
- package/src/dynamic-geometry.ts +736 -0
- package/src/index.ts +16 -0
- package/src/record/__tests__/standard-pbr-ubo-layout.unit.test.ts +68 -1
- package/src/record/frame.ts +2 -0
- package/src/record/main-pass-geometry.ts +13 -4
- package/src/record/main-pass-sprite-draws.ts +2 -0
- package/src/record/render-context.ts +2 -0
- package/src/record/typed-frame-graph.ts +2 -9
- package/src/render-contract.ts +24 -0
- package/src/render-system-extract-tail.ts +3 -2
- package/src/render-system-extract.ts +30 -1
- package/src/render-system.ts +42 -0
- package/dist/chunk-FJ6P52EE.mjs.map +0 -1
- package/dist/chunk-JDWARUOI.mjs.map +0 -1
- package/dist/chunk-KM2NOX2I.mjs.map +0 -1
- package/dist/chunk-TMIASV2N.mjs.map +0 -1
- package/dist/chunk-YUQXSEHG.mjs.map +0 -1
|
@@ -1,9 +1,465 @@
|
|
|
1
|
-
import { CUBE_CAMERA_FACE_ORDER, createVisibilityBudget, PointShapeValue, pointShapeFromU32 } from './chunk-
|
|
2
|
-
import { PointsLinesBudgetExceededError, PointsLinesStyleUnsupportedError, PointsLinesMaterialUnsupportedError, PointsLinesInvalidStyleError, PointsLinesTopologyMismatchError } from './chunk-
|
|
3
|
-
import {
|
|
1
|
+
import { CUBE_CAMERA_FACE_ORDER, createVisibilityBudget, PointShapeValue, pointShapeFromU32 } from './chunk-JAASCMXA.mjs';
|
|
2
|
+
import { PointsLinesBudgetExceededError, PointsLinesStyleUnsupportedError, PointsLinesMaterialUnsupportedError, PointsLinesInvalidStyleError, PointsLinesTopologyMismatchError } from './chunk-J6KACNOH.mjs';
|
|
3
|
+
import { ok, err } from '@forgeax/engine-types';
|
|
4
4
|
import { mat4 } from '@forgeax/engine-math';
|
|
5
5
|
import { validateR32FloatReceipt } from '@forgeax/engine-rhi';
|
|
6
6
|
|
|
7
|
+
var DynamicGeometryError = class extends Error {
|
|
8
|
+
code;
|
|
9
|
+
expected;
|
|
10
|
+
hint;
|
|
11
|
+
detail;
|
|
12
|
+
constructor(code, expected, hint, detail = {}) {
|
|
13
|
+
super(`${code}: ${expected}`);
|
|
14
|
+
this.name = "DynamicGeometryError";
|
|
15
|
+
this.code = code;
|
|
16
|
+
this.expected = expected;
|
|
17
|
+
this.hint = hint;
|
|
18
|
+
this.detail = Object.freeze({ code, ...detail });
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
function sameCandidateCredential(candidate, committed) {
|
|
22
|
+
return candidate.candidateId === committed.candidateId && candidate.owner === committed.owner && candidate.world === committed.world && candidate.generation === committed.generation && candidate.entity === committed.entity && candidate.physicsEntity === committed.physicsEntity && candidate.gpuReady === committed.gpuReady && candidate.meshBytes === committed.meshBytes && candidate.revision === committed.revision && candidate.topologyRevision === committed.topologyRevision && candidate.materialIdentity === committed.materialIdentity && candidate.meshHandle === committed.meshHandle && candidate.fixedStep === committed.fixedStep;
|
|
23
|
+
}
|
|
24
|
+
function cloneTypedArray(value) {
|
|
25
|
+
return new value.constructor(value);
|
|
26
|
+
}
|
|
27
|
+
function cloneMesh(mesh) {
|
|
28
|
+
const attributes = Object.fromEntries(
|
|
29
|
+
Object.entries(mesh.attributes).map(([key, value]) => [
|
|
30
|
+
key,
|
|
31
|
+
value instanceof ArrayBuffer ? value.slice(0) : value === void 0 ? void 0 : cloneTypedArray(value)
|
|
32
|
+
])
|
|
33
|
+
);
|
|
34
|
+
return Object.freeze({
|
|
35
|
+
...mesh,
|
|
36
|
+
vertices: new Float32Array(mesh.vertices),
|
|
37
|
+
...mesh.indices === void 0 ? {} : { indices: cloneTypedArray(mesh.indices) },
|
|
38
|
+
attributes,
|
|
39
|
+
...mesh.aabb === void 0 ? {} : { aabb: new Float32Array(mesh.aabb) },
|
|
40
|
+
submeshes: Object.freeze(mesh.submeshes.map((submesh) => Object.freeze({ ...submesh }))),
|
|
41
|
+
materialSlots: Object.freeze(mesh.materialSlots.map((slot) => Object.freeze({ ...slot })))
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
function cloneCandidate(candidate) {
|
|
45
|
+
return Object.freeze({ ...candidate, mesh: cloneMesh(candidate.mesh) });
|
|
46
|
+
}
|
|
47
|
+
function validateMesh(mesh) {
|
|
48
|
+
if (mesh.kind !== "mesh") return "MeshAsset.kind must be mesh";
|
|
49
|
+
if (!(mesh.vertices instanceof Float32Array) || mesh.vertices.length === 0 || mesh.vertices.length % 3 !== 0) {
|
|
50
|
+
return "MeshAsset.vertices must be a non-empty Float32Array with xyz triples";
|
|
51
|
+
}
|
|
52
|
+
if (mesh.indices !== void 0 && !(mesh.indices instanceof Uint16Array || mesh.indices instanceof Uint32Array)) {
|
|
53
|
+
return "MeshAsset.indices must be Uint16Array or Uint32Array";
|
|
54
|
+
}
|
|
55
|
+
if (mesh.submeshes.length === 0)
|
|
56
|
+
return "MeshAsset.submeshes must contain at least one topology range";
|
|
57
|
+
if (mesh.materialSlots.length < mesh.submeshes.length) {
|
|
58
|
+
return "MeshAsset.materialSlots must cover every submesh";
|
|
59
|
+
}
|
|
60
|
+
if (mesh.vertices.some((value) => !Number.isFinite(value)))
|
|
61
|
+
return "MeshAsset.vertices must be finite";
|
|
62
|
+
return void 0;
|
|
63
|
+
}
|
|
64
|
+
function meshByteSize(mesh) {
|
|
65
|
+
let bytes = mesh.vertices.byteLength + (mesh.indices?.byteLength ?? 0);
|
|
66
|
+
for (const value of Object.values(mesh.attributes)) {
|
|
67
|
+
if (value instanceof ArrayBuffer) bytes += value.byteLength;
|
|
68
|
+
else if (value !== void 0 && ArrayBuffer.isView(value)) bytes += value.byteLength;
|
|
69
|
+
}
|
|
70
|
+
if (mesh.aabb !== void 0) bytes += mesh.aabb.byteLength;
|
|
71
|
+
return bytes;
|
|
72
|
+
}
|
|
73
|
+
function createDynamicGeometryLifecycle(maxCandidates = 32, maxMeshBytes = 64 * 1024 * 1024) {
|
|
74
|
+
const records = /* @__PURE__ */ new Map();
|
|
75
|
+
const owner = {};
|
|
76
|
+
let sequence = 0;
|
|
77
|
+
let currentGeneration = 0;
|
|
78
|
+
let disposed = false;
|
|
79
|
+
let historyInvalidations = 0;
|
|
80
|
+
let latestRevision;
|
|
81
|
+
let latestFrameId;
|
|
82
|
+
let retired = 0;
|
|
83
|
+
let invalidated = 0;
|
|
84
|
+
let meshBytes = 0;
|
|
85
|
+
const latestRevisionByWorld = /* @__PURE__ */ new WeakMap();
|
|
86
|
+
const latestTopologyRevisionByWorld = /* @__PURE__ */ new WeakMap();
|
|
87
|
+
const publishedReceipts = /* @__PURE__ */ new Map();
|
|
88
|
+
const failure2 = (code, expected, hint, detail = {}) => err(new DynamicGeometryError(code, expected, hint, detail));
|
|
89
|
+
const current = (candidate) => {
|
|
90
|
+
const record = records.get(candidate.candidateId);
|
|
91
|
+
if (record === void 0 || candidate.owner !== owner || record.candidate.owner !== candidate.owner || record.candidate.world !== candidate.world || record.candidate.generation !== candidate.generation)
|
|
92
|
+
return void 0;
|
|
93
|
+
return record;
|
|
94
|
+
};
|
|
95
|
+
return {
|
|
96
|
+
prepare(input, generation) {
|
|
97
|
+
if (disposed)
|
|
98
|
+
return failure2("dynamic-geometry-disposed", "renderer is alive", "rebuild the Renderer");
|
|
99
|
+
if (input.world === null || typeof input.world !== "object") {
|
|
100
|
+
return failure2(
|
|
101
|
+
"dynamic-geometry-world-not-attached",
|
|
102
|
+
"candidate belongs to an attached World",
|
|
103
|
+
"attach the World before preparing geometry"
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
if (!Number.isInteger(input.revision) || input.revision < 0) {
|
|
107
|
+
return failure2(
|
|
108
|
+
"dynamic-geometry-invalid",
|
|
109
|
+
"geometry revision is a non-negative integer",
|
|
110
|
+
"advance the consumer revision monotonically",
|
|
111
|
+
{ revision: input.revision }
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
if (generation !== currentGeneration && currentGeneration !== 0) {
|
|
115
|
+
return failure2(
|
|
116
|
+
"dynamic-geometry-generation-mismatch",
|
|
117
|
+
"candidate uses the active renderer generation",
|
|
118
|
+
"discard stale generation work and prepare again",
|
|
119
|
+
{ generation, actual: currentGeneration }
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
if (records.size >= maxCandidates) {
|
|
123
|
+
return failure2(
|
|
124
|
+
"dynamic-geometry-budget-exceeded",
|
|
125
|
+
"candidate count remains within the bounded renderer budget",
|
|
126
|
+
"cancel or retire an earlier candidate before retrying",
|
|
127
|
+
{ actual: records.size }
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
const invalid = validateMesh(input.mesh);
|
|
131
|
+
if (invalid !== void 0)
|
|
132
|
+
return failure2(
|
|
133
|
+
"dynamic-geometry-invalid",
|
|
134
|
+
"MeshAsset satisfies the standard geometry contract",
|
|
135
|
+
invalid
|
|
136
|
+
);
|
|
137
|
+
const candidateBytes = meshByteSize(input.mesh);
|
|
138
|
+
if (!Number.isSafeInteger(candidateBytes) || candidateBytes < 0 || meshBytes > maxMeshBytes - candidateBytes) {
|
|
139
|
+
return failure2(
|
|
140
|
+
"dynamic-geometry-budget-exceeded",
|
|
141
|
+
"candidate mesh bytes remain within the bounded renderer resource budget",
|
|
142
|
+
"cancel or retire an earlier candidate before retrying",
|
|
143
|
+
{ actual: { meshBytes, candidateBytes, maxMeshBytes } }
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
const candidateId = `mesh:${generation}:${++sequence}:${input.revision}`;
|
|
147
|
+
const candidate = Object.freeze({
|
|
148
|
+
candidateId,
|
|
149
|
+
generation,
|
|
150
|
+
owner,
|
|
151
|
+
world: input.world,
|
|
152
|
+
...input.entity === void 0 ? {} : { entity: input.entity },
|
|
153
|
+
...input.physicsEntity === void 0 ? {} : { physicsEntity: input.physicsEntity },
|
|
154
|
+
gpuReady: input.meshHandle !== void 0,
|
|
155
|
+
meshBytes: candidateBytes,
|
|
156
|
+
revision: input.revision,
|
|
157
|
+
topologyRevision: input.topologyRevision ?? input.revision,
|
|
158
|
+
...input.materialIdentity === void 0 ? {} : { materialIdentity: input.materialIdentity },
|
|
159
|
+
...input.meshHandle === void 0 ? {} : { meshHandle: input.meshHandle },
|
|
160
|
+
...input.fixedStep === void 0 ? {} : { fixedStep: input.fixedStep },
|
|
161
|
+
mesh: cloneMesh(input.mesh),
|
|
162
|
+
state: "prepared"
|
|
163
|
+
});
|
|
164
|
+
records.set(candidateId, {
|
|
165
|
+
world: input.world,
|
|
166
|
+
candidate,
|
|
167
|
+
historyInvalidated: false
|
|
168
|
+
});
|
|
169
|
+
meshBytes += candidateBytes;
|
|
170
|
+
currentGeneration = generation;
|
|
171
|
+
return ok(cloneCandidate(candidate));
|
|
172
|
+
},
|
|
173
|
+
accept(candidate, ordering) {
|
|
174
|
+
if (disposed)
|
|
175
|
+
return failure2("dynamic-geometry-disposed", "renderer is alive", "rebuild the Renderer");
|
|
176
|
+
const record = current(candidate);
|
|
177
|
+
if (record === void 0)
|
|
178
|
+
return failure2(
|
|
179
|
+
"dynamic-geometry-candidate-not-found",
|
|
180
|
+
"candidate is owned by this renderer",
|
|
181
|
+
"discard the stale credential"
|
|
182
|
+
);
|
|
183
|
+
if (candidate.generation !== currentGeneration) {
|
|
184
|
+
return failure2(
|
|
185
|
+
"dynamic-geometry-generation-mismatch",
|
|
186
|
+
"candidate generation matches the active renderer generation",
|
|
187
|
+
"prepare against the current renderer generation",
|
|
188
|
+
{ candidateId: candidate.candidateId }
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
if (record.candidate.state !== "prepared") {
|
|
192
|
+
return failure2(
|
|
193
|
+
"dynamic-geometry-candidate-state",
|
|
194
|
+
"candidate is prepared exactly once before acceptance",
|
|
195
|
+
"retain the accepted or published receipt",
|
|
196
|
+
{ candidateId: candidate.candidateId }
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
if (!sameCandidateCredential(candidate, record.candidate)) {
|
|
200
|
+
return failure2(
|
|
201
|
+
"dynamic-geometry-receipt-mismatch",
|
|
202
|
+
"acceptance uses the exact prepared candidate credential",
|
|
203
|
+
"discard the altered credential and use the value returned by prepare",
|
|
204
|
+
{ candidateId: candidate.candidateId }
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
if (ordering !== void 0) {
|
|
208
|
+
if (ordering.world !== record.world || !Number.isInteger(ordering.fixedStep) || ordering.fixedStep < 0) {
|
|
209
|
+
return failure2(
|
|
210
|
+
"dynamic-geometry-ordering-required",
|
|
211
|
+
"acceptance ordering names the candidate World and a non-negative fixed step",
|
|
212
|
+
"submit the PhysicsWorld publication ordering for the same attached World",
|
|
213
|
+
{ candidateId: candidate.candidateId, actual: ordering }
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
const newest = [...records.values()].filter(
|
|
218
|
+
(entry) => entry.candidate.candidateId !== candidate.candidateId && entry.world === record.world && entry.candidate.entity === record.candidate.entity && (entry.candidate.state === "prepared" || entry.candidate.state === "accepted")
|
|
219
|
+
).map((entry) => entry.candidate.revision).concat(latestRevisionByWorld.get(record.world)?.get(record.candidate.entity) ?? -1).sort((a, b) => b - a)[0];
|
|
220
|
+
if (newest !== void 0 && record.candidate.revision <= newest) {
|
|
221
|
+
return failure2(
|
|
222
|
+
"dynamic-geometry-stale",
|
|
223
|
+
"accepted geometry revision is newer than the active candidate",
|
|
224
|
+
"advance topology revision before accepting",
|
|
225
|
+
{ candidateId: candidate.candidateId }
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
const accepted = Object.freeze({
|
|
229
|
+
...record.candidate,
|
|
230
|
+
...ordering === void 0 ? {} : { fixedStep: ordering.fixedStep },
|
|
231
|
+
state: "accepted"
|
|
232
|
+
});
|
|
233
|
+
const previousTopologies = [...records.values()].filter(
|
|
234
|
+
(entry) => entry.candidate.candidateId !== candidate.candidateId && entry.world === record.world && entry.candidate.entity === record.candidate.entity
|
|
235
|
+
).map((entry) => entry.candidate.topologyRevision);
|
|
236
|
+
const previousLatestTopology = latestTopologyRevisionByWorld.get(record.world)?.get(record.candidate.entity);
|
|
237
|
+
const newestTopology = previousTopologies.concat(previousLatestTopology === void 0 ? [] : [previousLatestTopology]).sort((a, b) => b - a)[0];
|
|
238
|
+
if (newestTopology !== void 0 && accepted.topologyRevision < newestTopology) {
|
|
239
|
+
return failure2(
|
|
240
|
+
"dynamic-geometry-stale",
|
|
241
|
+
"accepted topology revision is not older than the active geometry history",
|
|
242
|
+
"advance topology revision before accepting a replacement candidate",
|
|
243
|
+
{ candidateId: candidate.candidateId }
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
if (newestTopology !== void 0 && accepted.topologyRevision > newestTopology) {
|
|
247
|
+
historyInvalidations += 1;
|
|
248
|
+
for (const previous of records.values()) {
|
|
249
|
+
if (previous.world !== record.world || previous.candidate.entity !== record.candidate.entity || previous.candidate.candidateId === candidate.candidateId || previous.candidate.topologyRevision >= accepted.topologyRevision)
|
|
250
|
+
continue;
|
|
251
|
+
if (!previous.historyInvalidated) {
|
|
252
|
+
previous.historyInvalidated = true;
|
|
253
|
+
invalidated += 1;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
record.candidate = accepted;
|
|
258
|
+
return ok(cloneCandidate(accepted));
|
|
259
|
+
},
|
|
260
|
+
cancel(candidate, completion) {
|
|
261
|
+
const record = current(candidate);
|
|
262
|
+
if (record === void 0)
|
|
263
|
+
return failure2(
|
|
264
|
+
"dynamic-geometry-candidate-not-found",
|
|
265
|
+
"candidate is owned by this renderer",
|
|
266
|
+
"discard the stale credential"
|
|
267
|
+
);
|
|
268
|
+
if (!sameCandidateCredential(candidate, record.candidate))
|
|
269
|
+
return failure2(
|
|
270
|
+
"dynamic-geometry-receipt-mismatch",
|
|
271
|
+
"cancellation uses the exact prepared candidate credential",
|
|
272
|
+
"discard the altered credential and use the value returned by prepare",
|
|
273
|
+
{ candidateId: candidate.candidateId }
|
|
274
|
+
);
|
|
275
|
+
if (record.candidate.state !== "prepared" && record.candidate.state !== "accepted") {
|
|
276
|
+
return failure2(
|
|
277
|
+
"dynamic-geometry-candidate-state",
|
|
278
|
+
"only prepared or accepted geometry can be cancelled once",
|
|
279
|
+
"retire published geometry or await the existing terminal cleanup"
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
record.candidate = Object.freeze({ ...record.candidate, state: "cancelled" });
|
|
283
|
+
const release = () => {
|
|
284
|
+
if (records.get(candidate.candidateId) !== record) return;
|
|
285
|
+
records.delete(candidate.candidateId);
|
|
286
|
+
meshBytes = Math.max(0, meshBytes - record.candidate.meshBytes);
|
|
287
|
+
};
|
|
288
|
+
if (completion === void 0) release();
|
|
289
|
+
else void completion.then(release, release);
|
|
290
|
+
return ok(void 0);
|
|
291
|
+
},
|
|
292
|
+
retire(candidate) {
|
|
293
|
+
const record = current(candidate);
|
|
294
|
+
if (record === void 0)
|
|
295
|
+
return failure2(
|
|
296
|
+
"dynamic-geometry-candidate-not-found",
|
|
297
|
+
"retirement uses a published candidate owned by this renderer",
|
|
298
|
+
"discard the stale credential and do not evict any unrelated mesh",
|
|
299
|
+
{ candidateId: candidate.candidateId }
|
|
300
|
+
);
|
|
301
|
+
if (!sameCandidateCredential(candidate, record.candidate))
|
|
302
|
+
return failure2(
|
|
303
|
+
"dynamic-geometry-receipt-mismatch",
|
|
304
|
+
"retirement uses the exact published candidate credential",
|
|
305
|
+
"discard the altered credential and use the value returned by publish",
|
|
306
|
+
{ candidateId: candidate.candidateId }
|
|
307
|
+
);
|
|
308
|
+
if (record.candidate.state !== "published") {
|
|
309
|
+
return failure2(
|
|
310
|
+
"dynamic-geometry-candidate-state",
|
|
311
|
+
"only published geometry can enter receipt-bound retirement",
|
|
312
|
+
"publish the candidate from a submitted frame first"
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
record.candidate = Object.freeze({ ...record.candidate, state: "retired" });
|
|
316
|
+
publishedReceipts.delete(candidate.candidateId);
|
|
317
|
+
retired += 1;
|
|
318
|
+
return ok(void 0);
|
|
319
|
+
},
|
|
320
|
+
finalizeRetirement(candidate) {
|
|
321
|
+
const record = current(candidate);
|
|
322
|
+
if (record === void 0)
|
|
323
|
+
return failure2(
|
|
324
|
+
"dynamic-geometry-candidate-not-found",
|
|
325
|
+
"retirement finalization uses a candidate retained by this lifecycle",
|
|
326
|
+
"discard the stale credential or finish the current receipt-bound retirement"
|
|
327
|
+
);
|
|
328
|
+
if (!sameCandidateCredential(candidate, record.candidate))
|
|
329
|
+
return failure2(
|
|
330
|
+
"dynamic-geometry-receipt-mismatch",
|
|
331
|
+
"retirement finalization uses the exact published candidate credential",
|
|
332
|
+
"discard the altered credential and use the value returned by publication",
|
|
333
|
+
{ candidateId: candidate.candidateId }
|
|
334
|
+
);
|
|
335
|
+
if (record.candidate.state !== "retired")
|
|
336
|
+
return failure2(
|
|
337
|
+
"dynamic-geometry-candidate-state",
|
|
338
|
+
"only a retired candidate can release its deferred budget",
|
|
339
|
+
"retire the published candidate before finalizing its receipt-bound cleanup",
|
|
340
|
+
{ candidateId: candidate.candidateId }
|
|
341
|
+
);
|
|
342
|
+
records.delete(candidate.candidateId);
|
|
343
|
+
meshBytes = Math.max(0, meshBytes - record.candidate.meshBytes);
|
|
344
|
+
return ok(void 0);
|
|
345
|
+
},
|
|
346
|
+
publishFrame(frame, worlds, fixedStep, canPublish) {
|
|
347
|
+
if (disposed || frame.deviceGeneration !== currentGeneration) return [];
|
|
348
|
+
const receipts = [];
|
|
349
|
+
for (const record of records.values()) {
|
|
350
|
+
const canRepeatPublished = record.candidate.state === "published" && // Attached Renderer publication revalidates the entity binding in
|
|
351
|
+
// `canPublish`, so a history-invalidated candidate can still record
|
|
352
|
+
// a real draw for its live entity. Detached lifecycle publication
|
|
353
|
+
// remains one-shot once that history is invalidated.
|
|
354
|
+
(canPublish !== void 0 || !record.historyInvalidated);
|
|
355
|
+
if (record.candidate.state !== "accepted" && !canRepeatPublished) continue;
|
|
356
|
+
if (worlds !== void 0 && !worlds.includes(record.world)) continue;
|
|
357
|
+
if (record.candidate.fixedStep !== void 0 && (fixedStep === void 0 || record.candidate.fixedStep > fixedStep)) {
|
|
358
|
+
continue;
|
|
359
|
+
}
|
|
360
|
+
if (canPublish !== void 0 && !canPublish(record.candidate)) continue;
|
|
361
|
+
record.candidate = Object.freeze({ ...record.candidate, state: "published" });
|
|
362
|
+
const receipt = Object.freeze({
|
|
363
|
+
candidateId: record.candidate.candidateId,
|
|
364
|
+
generation: record.candidate.generation,
|
|
365
|
+
revision: record.candidate.revision,
|
|
366
|
+
topologyRevision: record.candidate.topologyRevision,
|
|
367
|
+
...record.candidate.fixedStep === void 0 ? {} : { fixedStep: record.candidate.fixedStep },
|
|
368
|
+
frameId: frame.frameId,
|
|
369
|
+
deviceGeneration: frame.deviceGeneration,
|
|
370
|
+
recordStageConsumed: canPublish !== void 0,
|
|
371
|
+
...fixedStep === void 0 ? {} : { publicationFixedStep: fixedStep },
|
|
372
|
+
frame: Object.freeze({ ...frame })
|
|
373
|
+
});
|
|
374
|
+
receipts.push(receipt);
|
|
375
|
+
publishedReceipts.set(record.candidate.candidateId, receipt);
|
|
376
|
+
latestRevision = record.candidate.revision;
|
|
377
|
+
latestFrameId = frame.frameId;
|
|
378
|
+
if (!latestRevisionByWorld.has(record.world))
|
|
379
|
+
latestRevisionByWorld.set(record.world, /* @__PURE__ */ new Map());
|
|
380
|
+
latestRevisionByWorld.get(record.world)?.set(record.candidate.entity, record.candidate.revision);
|
|
381
|
+
if (!latestTopologyRevisionByWorld.has(record.world))
|
|
382
|
+
latestTopologyRevisionByWorld.set(record.world, /* @__PURE__ */ new Map());
|
|
383
|
+
latestTopologyRevisionByWorld.get(record.world)?.set(record.candidate.entity, record.candidate.topologyRevision);
|
|
384
|
+
}
|
|
385
|
+
return receipts;
|
|
386
|
+
},
|
|
387
|
+
invalidateWorld(world, completion) {
|
|
388
|
+
for (const [id, record] of records) {
|
|
389
|
+
if (record.world !== world) continue;
|
|
390
|
+
if (record.candidate.state === "cancelled" || record.candidate.state === "retired")
|
|
391
|
+
continue;
|
|
392
|
+
publishedReceipts.delete(id);
|
|
393
|
+
record.candidate = Object.freeze({ ...record.candidate, state: "cancelled" });
|
|
394
|
+
const release = () => {
|
|
395
|
+
if (records.get(id) !== record) return;
|
|
396
|
+
records.delete(id);
|
|
397
|
+
meshBytes = Math.max(0, meshBytes - record.candidate.meshBytes);
|
|
398
|
+
};
|
|
399
|
+
if (completion === void 0) release();
|
|
400
|
+
else void completion.then(release, release);
|
|
401
|
+
invalidated += 1;
|
|
402
|
+
}
|
|
403
|
+
latestRevisionByWorld.delete(world);
|
|
404
|
+
latestTopologyRevisionByWorld.delete(world);
|
|
405
|
+
},
|
|
406
|
+
invalidateGeneration(generation) {
|
|
407
|
+
if (generation === currentGeneration) return;
|
|
408
|
+
currentGeneration = generation;
|
|
409
|
+
const invalidatedWorlds = /* @__PURE__ */ new Set();
|
|
410
|
+
for (const [id, record] of records) {
|
|
411
|
+
if (record.candidate.generation !== generation) {
|
|
412
|
+
records.delete(id);
|
|
413
|
+
publishedReceipts.delete(id);
|
|
414
|
+
meshBytes = Math.max(0, meshBytes - record.candidate.meshBytes);
|
|
415
|
+
invalidatedWorlds.add(record.world);
|
|
416
|
+
invalidated += 1;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
for (const world of invalidatedWorlds) {
|
|
420
|
+
latestRevisionByWorld.delete(world);
|
|
421
|
+
latestTopologyRevisionByWorld.delete(world);
|
|
422
|
+
}
|
|
423
|
+
},
|
|
424
|
+
receipt(candidate) {
|
|
425
|
+
const receipt = publishedReceipts.get(candidate.candidateId);
|
|
426
|
+
const record = records.get(candidate.candidateId);
|
|
427
|
+
if (record === void 0 || !sameCandidateCredential(candidate, record.candidate)) {
|
|
428
|
+
return void 0;
|
|
429
|
+
}
|
|
430
|
+
return receipt?.generation === candidate.generation ? receipt : void 0;
|
|
431
|
+
},
|
|
432
|
+
inspect() {
|
|
433
|
+
let prepared = 0;
|
|
434
|
+
let accepted = 0;
|
|
435
|
+
let published = 0;
|
|
436
|
+
for (const record of records.values()) {
|
|
437
|
+
if (record.candidate.state === "prepared") prepared += 1;
|
|
438
|
+
else if (record.candidate.state === "accepted") accepted += 1;
|
|
439
|
+
else if (record.candidate.state === "published") published += 1;
|
|
440
|
+
}
|
|
441
|
+
return Object.freeze({
|
|
442
|
+
generation: currentGeneration,
|
|
443
|
+
prepared,
|
|
444
|
+
accepted,
|
|
445
|
+
published,
|
|
446
|
+
retired,
|
|
447
|
+
...latestRevision === void 0 ? {} : { latestRevision },
|
|
448
|
+
...latestFrameId === void 0 ? {} : { latestFrameId },
|
|
449
|
+
historyInvalidations,
|
|
450
|
+
invalidated,
|
|
451
|
+
meshBytes,
|
|
452
|
+
maxMeshBytes
|
|
453
|
+
});
|
|
454
|
+
},
|
|
455
|
+
dispose() {
|
|
456
|
+
disposed = true;
|
|
457
|
+
records.clear();
|
|
458
|
+
publishedReceipts.clear();
|
|
459
|
+
meshBytes = 0;
|
|
460
|
+
}
|
|
461
|
+
};
|
|
462
|
+
}
|
|
7
463
|
function invalidStyle(input, component, field, value, expected) {
|
|
8
464
|
return err(new PointsLinesInvalidStyleError({ entity: input.entity, component, field, value, expected }));
|
|
9
465
|
}
|
|
@@ -998,6 +1454,6 @@ function resolveSsrAdmissionGeneration(previousGeneration, nextGeneration) {
|
|
|
998
1454
|
};
|
|
999
1455
|
}
|
|
1000
1456
|
|
|
1001
|
-
export { DEFAULT_REFLECTION_PROBE_LIMITS, LOD_OCCLUSION_INSPECTION_MAX_BYTES, LOD_OCCLUSION_INSPECTION_SCHEMA, ReflectionProbeProjection, SSR_FORMAT_PROFILE, SSR_FORMAT_STAGES, admitPointsLines, admitReflectionProbe, admitSsrM0, advanceProbeFilter, boxProjectReflectionDirection, buildCubeCameraFaceViews, buildReflectionProbeTable, commitProbeFilterStep, consumeLodOcclusionInspection, createProbeFilterState, deriveReflectionFallbackProjection, estimateReflectionProbeBytes, inspectLodOcclusion, inspectPointShadow, inspectReflectionFallback, inspectVolumetricFog, probeFilterIsSteady, projectSsrDependencies, reflectionFallbackCoverage, reflectionFallbackProjectionSignature, reflectionFallbackSourceKey, reflectionProbeTableBytes, resolveReflectionFallbackSource, resolveSsrAdmissionGeneration, selectReflectionProbe, serializeLodOcclusionInspection, validateReflectionProbeInput, zeroSsrAdmissionWork };
|
|
1002
|
-
//# sourceMappingURL=chunk-
|
|
1003
|
-
//# sourceMappingURL=chunk-
|
|
1457
|
+
export { DEFAULT_REFLECTION_PROBE_LIMITS, DynamicGeometryError, LOD_OCCLUSION_INSPECTION_MAX_BYTES, LOD_OCCLUSION_INSPECTION_SCHEMA, ReflectionProbeProjection, SSR_FORMAT_PROFILE, SSR_FORMAT_STAGES, admitPointsLines, admitReflectionProbe, admitSsrM0, advanceProbeFilter, boxProjectReflectionDirection, buildCubeCameraFaceViews, buildReflectionProbeTable, commitProbeFilterStep, consumeLodOcclusionInspection, createDynamicGeometryLifecycle, createProbeFilterState, deriveReflectionFallbackProjection, estimateReflectionProbeBytes, inspectLodOcclusion, inspectPointShadow, inspectReflectionFallback, inspectVolumetricFog, probeFilterIsSteady, projectSsrDependencies, reflectionFallbackCoverage, reflectionFallbackProjectionSignature, reflectionFallbackSourceKey, reflectionProbeTableBytes, resolveReflectionFallbackSource, resolveSsrAdmissionGeneration, sameCandidateCredential, selectReflectionProbe, serializeLodOcclusionInspection, validateReflectionProbeInput, zeroSsrAdmissionWork };
|
|
1458
|
+
//# sourceMappingURL=chunk-J52NMQKR.mjs.map
|
|
1459
|
+
//# sourceMappingURL=chunk-J52NMQKR.mjs.map
|