@forgeax/engine-render 0.1.29 → 0.1.31
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 +248 -21
- package/dist/assembly/atmosphere-shader-prewarm.d.ts +1 -1
- package/dist/assembly/atmosphere-shader-prewarm.d.ts.map +1 -1
- package/dist/assembly/device-feature-admission.d.ts +4 -3
- package/dist/assembly/device-feature-admission.d.ts.map +1 -1
- package/dist/assembly/dynamic-geometry-host.d.ts +6 -0
- package/dist/assembly/dynamic-geometry-host.d.ts.map +1 -1
- package/dist/assembly/host-contract.d.ts +6 -0
- package/dist/assembly/host-contract.d.ts.map +1 -1
- package/dist/assembly/material/assembly.d.ts +26 -3
- package/dist/assembly/material/assembly.d.ts.map +1 -1
- package/dist/assembly/material/pipeline-helpers.d.ts +24 -0
- package/dist/assembly/material/pipeline-helpers.d.ts.map +1 -0
- package/dist/assembly/material-shader-policy.d.ts.map +1 -1
- package/dist/assembly/recovery/device-loss-fanout.d.ts.map +1 -1
- package/dist/assembly/recovery/recovery-attempt.d.ts +1 -6
- package/dist/assembly/recovery/recovery-attempt.d.ts.map +1 -1
- package/dist/assembly/recovery/renderer-recover.d.ts +1 -0
- package/dist/assembly/recovery/renderer-recover.d.ts.map +1 -1
- package/dist/assembly/renderer-facade.d.ts.map +1 -1
- package/dist/assembly/shader-prewarm-policy.d.ts +1 -1
- package/dist/assembly/shader-prewarm-policy.d.ts.map +1 -1
- package/dist/assembly/webgpu-ready.d.ts +1 -1
- package/dist/assembly/webgpu-ready.d.ts.map +1 -1
- package/dist/assembly/webgpu-renderer.d.ts.map +1 -1
- package/dist/capture/scene-projection.d.ts +41 -0
- package/dist/capture/scene-projection.d.ts.map +1 -0
- package/dist/{chunk-BQVQOQJH.mjs → chunk-7MQ3XBSP.mjs} +20 -14
- package/dist/chunk-7MQ3XBSP.mjs.map +1 -0
- package/dist/{chunk-S4DNKQYQ.mjs → chunk-C3DDSXYK.mjs} +86 -33
- package/dist/chunk-C3DDSXYK.mjs.map +1 -0
- package/dist/{chunk-BTWH7BP4.mjs → chunk-FUUO4UND.mjs} +73 -29
- package/dist/chunk-FUUO4UND.mjs.map +1 -0
- package/dist/{chunk-NRNXBLIJ.mjs → chunk-JRBDO2KB.mjs} +46 -6
- package/dist/chunk-JRBDO2KB.mjs.map +1 -0
- package/dist/{chunk-NICXSHAT.mjs → chunk-MDQJPY3T.mjs} +8 -6
- package/dist/chunk-MDQJPY3T.mjs.map +1 -0
- package/dist/{chunk-JQTSEI3Z.mjs → chunk-NO4WZCAH.mjs} +3 -3
- package/dist/chunk-NO4WZCAH.mjs.map +1 -0
- package/dist/{chunk-FUODTTIA.mjs → chunk-PSEQ6QPH.mjs} +621 -108
- package/dist/chunk-PSEQ6QPH.mjs.map +1 -0
- package/dist/{chunk-33XZFTQU.mjs → chunk-S5UE2T5P.mjs} +48 -6
- package/dist/chunk-S5UE2T5P.mjs.map +1 -0
- package/dist/components/directional-light.d.ts +2 -2
- package/dist/components/dynamic-resolution.d.ts +15 -0
- package/dist/components/dynamic-resolution.d.ts.map +1 -0
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/light-helpers.d.ts.map +1 -1
- package/dist/components/reflection-probe.d.ts +1 -0
- package/dist/components/reflection-probe.d.ts.map +1 -1
- package/dist/components/skylight.d.ts +4 -1
- package/dist/components/skylight.d.ts.map +1 -1
- package/dist/construct-renderer.mjs +2172 -796
- package/dist/construct-renderer.mjs.map +1 -1
- package/dist/device/device-scope.d.ts +2 -0
- package/dist/device/device-scope.d.ts.map +1 -1
- package/dist/device/gpu-residency.d.ts +2 -2
- package/dist/device/gpu-residency.d.ts.map +1 -1
- package/dist/dynamic-geometry.d.ts.map +1 -1
- package/dist/environment/background.d.ts +3 -2
- package/dist/environment/background.d.ts.map +1 -1
- package/dist/environment/ibl.d.ts +11 -0
- package/dist/environment/ibl.d.ts.map +1 -0
- package/dist/environment/storage.d.ts +14 -0
- package/dist/environment/storage.d.ts.map +1 -0
- package/dist/errors/gpu-driven.d.ts +2 -2
- package/dist/errors/gpu-driven.d.ts.map +1 -1
- package/dist/errors/render.d.ts +35 -1
- package/dist/errors/render.d.ts.map +1 -1
- package/dist/extract/camera.d.ts.map +1 -1
- package/dist/extract/contracts.d.ts +7 -0
- package/dist/extract/contracts.d.ts.map +1 -1
- package/dist/extract/gpu-driven.d.ts +18 -1
- package/dist/extract/gpu-driven.d.ts.map +1 -1
- package/dist/gpu-driven/batch-topology.d.ts.map +1 -1
- package/dist/gpu-driven/material-artifacts.d.ts +24 -0
- package/dist/gpu-driven/material-artifacts.d.ts.map +1 -0
- package/dist/gpu-driven/material-bindings.d.ts +7 -0
- package/dist/gpu-driven/material-bindings.d.ts.map +1 -0
- package/dist/gpu-driven/prepared-draw.d.ts +2 -0
- package/dist/gpu-driven/prepared-draw.d.ts.map +1 -1
- package/dist/gpu-driven/production-raster.d.ts +27 -6
- package/dist/gpu-driven/production-raster.d.ts.map +1 -1
- package/dist/gpu-resource.d.ts +5 -2
- package/dist/gpu-resource.d.ts.map +1 -1
- package/dist/gpu-scene-schema.d.ts.map +1 -1
- package/dist/gpu-scene.d.ts.map +1 -1
- package/dist/hdrp-buffers.d.ts +3 -0
- package/dist/hdrp-buffers.d.ts.map +1 -1
- package/dist/ibl/IblPipelineCache.d.ts +2 -0
- package/dist/ibl/IblPipelineCache.d.ts.map +1 -1
- package/dist/ibl/skylight-bind-group.d.ts +3 -1
- package/dist/ibl/skylight-bind-group.d.ts.map +1 -1
- package/dist/index.d.ts +83 -81
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +7 -5
- package/dist/index.mjs.map +1 -1
- package/dist/inspection-types.d.ts +16 -1
- package/dist/inspection-types.d.ts.map +1 -1
- package/dist/internal.mjs +6 -6
- package/dist/material-row.d.ts +10 -0
- package/dist/material-row.d.ts.map +1 -1
- package/dist/pbr-pipeline.d.ts +3 -1
- package/dist/pbr-pipeline.d.ts.map +1 -1
- package/dist/pipeline/render-extent.d.ts +51 -0
- package/dist/pipeline/render-extent.d.ts.map +1 -0
- package/dist/pipeline/standard-build-context.d.ts +7 -0
- package/dist/pipeline/standard-build-context.d.ts.map +1 -0
- package/dist/pipeline/standard-forward-lane.d.ts +3 -2
- package/dist/pipeline/standard-forward-lane.d.ts.map +1 -1
- package/dist/pipeline/standard-pipeline.d.ts.map +1 -1
- package/dist/pipeline/standard-post.d.ts +1 -1
- package/dist/pipeline/standard-post.d.ts.map +1 -1
- package/dist/pipeline/standard-profile.d.ts +2 -1
- package/dist/pipeline/standard-profile.d.ts.map +1 -1
- package/dist/pipeline-builder.d.ts +3 -1
- package/dist/pipeline-builder.d.ts.map +1 -1
- package/dist/plugin.d.ts.map +1 -1
- package/dist/post-process-errors.d.ts +9 -2
- package/dist/post-process-errors.d.ts.map +1 -1
- package/dist/prepare/extended-lighting/resources.d.ts +3 -3
- package/dist/record/frame-lighting.d.ts.map +1 -1
- package/dist/record/frame-snapshot.d.ts +3 -8
- package/dist/record/frame-snapshot.d.ts.map +1 -1
- package/dist/record/frame.d.ts +3 -2
- package/dist/record/frame.d.ts.map +1 -1
- package/dist/record/main-pass-geometry.d.ts +7 -2
- package/dist/record/main-pass-geometry.d.ts.map +1 -1
- package/dist/record/main-pass-material.d.ts +6 -3
- package/dist/record/main-pass-material.d.ts.map +1 -1
- package/dist/record/main-pass.d.ts +2 -0
- package/dist/record/main-pass.d.ts.map +1 -1
- package/dist/record/prepared-material-bindings.d.ts.map +1 -1
- package/dist/record/recovery-pipeline.d.ts +9 -1
- package/dist/record/recovery-pipeline.d.ts.map +1 -1
- package/dist/record/render-context.d.ts +34 -3
- package/dist/record/render-context.d.ts.map +1 -1
- package/dist/record/shadow-pass.d.ts +2 -2
- package/dist/record/shadow-pass.d.ts.map +1 -1
- package/dist/record/typed-frame-graph.d.ts +5 -2
- package/dist/record/typed-frame-graph.d.ts.map +1 -1
- package/dist/recovery/render-system-candidate.d.ts.map +1 -1
- package/dist/reflection/background.d.ts +14 -0
- package/dist/reflection/background.d.ts.map +1 -0
- package/dist/reflection/filter.d.ts.map +1 -1
- package/dist/reflection/projection.d.ts +1 -0
- package/dist/reflection/projection.d.ts.map +1 -1
- package/dist/reflection/record-owner.d.ts +4 -2
- package/dist/reflection/record-owner.d.ts.map +1 -1
- package/dist/render-contract.d.ts +47 -32
- package/dist/render-contract.d.ts.map +1 -1
- package/dist/render-graph-primitives.d.ts.map +1 -1
- package/dist/render-pipeline.d.ts +17 -1
- package/dist/render-pipeline.d.ts.map +1 -1
- package/dist/render-system-extract-tail.d.ts +9 -2
- package/dist/render-system-extract-tail.d.ts.map +1 -1
- package/dist/render-system-extract.d.ts +29 -2
- package/dist/render-system-extract.d.ts.map +1 -1
- package/dist/render-system.d.ts.map +1 -1
- package/dist/scene/render-scene.d.ts +2 -0
- package/dist/scene/render-scene.d.ts.map +1 -1
- package/dist/ssao-config.d.ts +8 -6
- package/dist/ssao-config.d.ts.map +1 -1
- package/dist/ssr/graph.d.ts.map +1 -1
- package/dist/temporal/gpu.d.ts.map +1 -1
- package/dist/temporal/index.mjs +5 -5
- package/dist/temporal/standard-scene-data.d.ts +2 -1
- package/dist/temporal/standard-scene-data.d.ts.map +1 -1
- package/dist/temporal/target-coverage-attachment.d.ts +15 -0
- package/dist/temporal/target-coverage-attachment.d.ts.map +1 -0
- package/dist/temporal/temporal-view.d.ts +6 -2
- package/dist/temporal/temporal-view.d.ts.map +1 -1
- package/dist/temporal/view.d.ts +6 -0
- package/dist/temporal/view.d.ts.map +1 -1
- package/dist/typed-render-graph-primitives.d.ts +6 -0
- package/dist/typed-render-graph-primitives.d.ts.map +1 -1
- package/dist/volume/component.d.ts +1 -0
- package/dist/volume/component.d.ts.map +1 -1
- package/package.json +21 -21
- package/src/__tests__/capture-scene-projection.unit.test.ts +24 -0
- package/src/__tests__/device-feature-admission.unit.test.ts +15 -24
- package/src/__tests__/device-loss-fanout.unit.test.ts +43 -0
- package/src/__tests__/dynamic-geometry-batch.unit.test.ts +157 -0
- package/src/__tests__/dynamic-geometry.unit.test.ts +36 -0
- package/src/__tests__/dynamic-resolution-plugin.integration.test.ts +81 -0
- package/src/__tests__/factory-contract.integration.test.ts +164 -1
- package/src/__tests__/frame-observation.contract.test.ts +21 -0
- package/src/__tests__/gpu-driven-baseline.characterization.test.ts +1 -1
- package/src/__tests__/gpu-driven-batch-topology-pbr.unit.test.ts +20 -1
- package/src/__tests__/gpu-driven-lod-cache.unit.test.ts +0 -3
- package/src/__tests__/gpu-driven-mesh-world-identity.unit.test.ts +0 -3
- package/src/__tests__/gpu-driven-pbr.dawn.test.ts +11 -6
- package/src/__tests__/gpu-driven-production.integration.test.ts +13 -25
- package/src/__tests__/gpu-driven-view.unit.test.ts +1 -0
- package/src/__tests__/gpu-pass-timing-browser-runner.ts +15 -0
- package/src/__tests__/gpu-pass-timing.browser.test.ts +14 -1
- package/src/__tests__/gpu-resource-scope.unit.test.ts +71 -0
- package/src/__tests__/gpu-scene.unit.test.ts +55 -1
- package/src/__tests__/materials-standard-contract.unit.test.ts +5 -1
- package/src/__tests__/mesh-submission-lifetime.unit.test.ts +8 -0
- package/src/__tests__/morph-neutral-admission.unit.test.ts +19 -0
- package/src/__tests__/physical-clearcoat.integration.test.ts +6 -1
- package/src/__tests__/post-process-error-policy-owner.unit.test.ts +6 -5
- package/src/__tests__/prepared-gpu-driven-pbr.integration.test.ts +216 -5
- package/src/__tests__/reflection-fallback-async-publication.unit.test.ts +68 -1
- package/src/__tests__/reflection-probe-authoring.unit.test.ts +12 -10
- package/src/__tests__/reflection-probe-box.dawn.test.ts +71 -0
- package/src/__tests__/reflection-probe-filter.unit.test.ts +9 -0
- package/src/__tests__/reflection-probe-orientation.dawn.test.ts +294 -0
- package/src/__tests__/reflection-probe-record-owner.characterization.unit.test.ts +5 -15
- package/src/__tests__/reflection-probe-updates.unit.test.ts +129 -0
- package/src/__tests__/render-error-code-owner.test-d.ts +3 -0
- package/src/__tests__/render-error-detail-ssot.test-d.ts +5 -1
- package/src/__tests__/render-extent.unit.test.ts +63 -0
- package/src/__tests__/render-scene-projection.unit.test.ts +3 -2
- package/src/__tests__/renderer-factory-material-contract.unit.test.ts +173 -9
- package/src/__tests__/shader-manifest-fixture.ts +6 -0
- package/src/__tests__/skinned-shadow-caster.test.ts +8 -1
- package/src/__tests__/specular-environment-scale.dawn.test.ts +4 -2
- package/src/__tests__/ssao-config.unit.test.ts +7 -0
- package/src/__tests__/ssao-surface.dawn.test.ts +252 -0
- package/src/__tests__/ssr-cube-silhouette.dawn.test.ts +270 -0
- package/src/__tests__/ssr-gpu-dispatch.browser.test.ts +51 -43
- package/src/__tests__/ssr-gpu-dispatch.dawn.test.ts +4 -0
- package/src/__tests__/ssr-gpu-dispatch.ts +10 -5
- package/src/__tests__/ssr-graph-topology.unit.test.ts +2 -2
- package/src/__tests__/ssr-hiz.browser.test.ts +1 -1
- package/src/__tests__/ssr-hiz.dawn.test.ts +1 -1
- package/src/__tests__/ssr-spatial.integration.test.ts +1 -1
- package/src/__tests__/standard-cluster-variant-resolution.unit.test.ts +40 -0
- package/src/__tests__/standard-pbr-artifact-assembly.unit.test.ts +238 -1
- package/src/__tests__/standard-profile-oracle.unit.test.ts +27 -0
- package/src/__tests__/taa-coverage-edge.dawn.test.ts +6 -0
- package/src/__tests__/taa-neighborhood-equivalence.dawn.test.ts +5 -1
- package/src/__tests__/taa-stability-feedback.ts +1 -0
- package/src/__tests__/temporal-reset-matrix.unit.test.ts +2 -2
- package/src/__tests__/temporal-view.unit.test.ts +17 -0
- package/src/__tests__/transmission-prewarm.unit.test.ts +57 -0
- package/src/__tests__/volumetric-fog-stage-readback.dawn.test.ts +62 -13
- package/src/__tests__/volumetric-fog-topology.unit.test.ts +7 -7
- package/src/assembly/atmosphere-shader-prewarm.ts +3 -2
- package/src/assembly/device-feature-admission.ts +9 -4
- package/src/assembly/dynamic-geometry-host.ts +236 -163
- package/src/assembly/host-contract.ts +9 -0
- package/src/assembly/material/assembly.ts +350 -6
- package/src/assembly/material/pipeline-helpers.ts +100 -0
- package/src/assembly/material-shader-policy.ts +39 -18
- package/src/assembly/recovery/device-loss-fanout.ts +5 -0
- package/src/assembly/recovery/recovery-attempt.ts +2 -29
- package/src/assembly/recovery/renderer-recover.ts +6 -9
- package/src/assembly/renderer-facade.ts +22 -1
- package/src/assembly/shader-prewarm-policy.ts +14 -1
- package/src/assembly/webgpu-ready.ts +28 -19
- package/src/assembly/webgpu-renderer.ts +95 -95
- package/src/capture/scene-projection.ts +37 -0
- package/src/components/__tests__/dynamic-resolution-schema.unit.test.ts +65 -0
- package/src/components/__tests__/light-radius.integration.test.ts +59 -0
- package/src/components/directional-light.ts +3 -3
- package/src/components/dynamic-resolution.ts +67 -0
- package/src/components/index.ts +1 -0
- package/src/components/light-helpers.ts +6 -1
- package/src/components/reflection-probe.ts +1 -0
- package/src/components/skylight.ts +4 -1
- package/src/device/device-scope.ts +13 -7
- package/src/device/gpu-residency.ts +11 -11
- package/src/dynamic-geometry.ts +13 -8
- package/src/environment/background.ts +30 -12
- package/src/environment/ibl.ts +211 -0
- package/src/environment/storage.ts +63 -0
- package/src/errors/gpu-driven.ts +10 -3
- package/src/errors/render.ts +64 -0
- package/src/extract/camera.ts +12 -1
- package/src/extract/contracts.ts +10 -0
- package/src/extract/gpu-driven.ts +72 -17
- package/src/gpu-driven/batch-topology.ts +33 -11
- package/src/gpu-driven/material-artifacts.ts +179 -0
- package/src/gpu-driven/material-bindings.ts +41 -0
- package/src/gpu-driven/prepared-draw.ts +13 -2
- package/src/gpu-driven/production-raster.ts +681 -164
- package/src/gpu-resource.ts +21 -2
- package/src/gpu-scene-schema.ts +11 -2
- package/src/gpu-scene.ts +14 -3
- package/src/hdrp-buffers.ts +69 -0
- package/src/ibl/IblPipelineCache.ts +5 -0
- package/src/ibl/skylight-bind-group.ts +27 -17
- package/src/index.ts +92 -81
- package/src/inspection-types.ts +16 -1
- package/src/material-row.ts +60 -11
- package/src/pbr-pipeline.ts +13 -2
- package/src/pipeline/render-extent.ts +111 -0
- package/src/pipeline/standard-build-context.ts +7 -0
- package/src/pipeline/standard-forward-lane.ts +41 -24
- package/src/pipeline/standard-pipeline.ts +47 -18
- package/src/pipeline/standard-post.ts +4 -0
- package/src/pipeline/standard-profile.ts +2 -1
- package/src/pipeline-builder.ts +12 -3
- package/src/plugin.ts +4 -0
- package/src/post-process-errors.ts +26 -18
- package/src/prepare/__tests__/extended-lighting-capability.unit.test.ts +2 -2
- package/src/prepare/extended-lighting/resources.ts +3 -3
- package/src/record/__tests__/main-pass-geometry.unit.test.ts +68 -0
- package/src/record/__tests__/material-bg-cache.unit.test.ts +45 -0
- package/src/record/__tests__/material-slot-plan.unit.test.ts +9 -0
- package/src/record/__tests__/recovery-pipeline.unit.test.ts +47 -1
- package/src/record/__tests__/sprite-material-abi.unit.test.ts +7 -3
- package/src/record/__tests__/temporal-payload.unit.test.ts +2 -0
- package/src/record/frame-lighting.ts +3 -1
- package/src/record/frame-snapshot.ts +13 -11
- package/src/record/frame.ts +150 -74
- package/src/record/main-pass-geometry.ts +60 -9
- package/src/record/main-pass-material.ts +47 -19
- package/src/record/main-pass.ts +63 -4
- package/src/record/prepared-material-bindings.ts +4 -0
- package/src/record/recovery-pipeline.ts +56 -0
- package/src/record/render-context.ts +34 -3
- package/src/record/shadow-pass.ts +187 -50
- package/src/record/typed-frame-graph.ts +60 -3
- package/src/record/view-ubo.ts +1 -1
- package/src/recovery/render-system-candidate.ts +54 -11
- package/src/reflection/background.ts +151 -0
- package/src/reflection/filter.ts +3 -0
- package/src/reflection/projection.ts +16 -6
- package/src/reflection/record-owner.ts +282 -86
- package/src/render-contract.ts +50 -32
- package/src/render-graph-primitives.ts +28 -3
- package/src/render-pipeline.ts +20 -1
- package/src/render-system-extract-tail.ts +23 -3
- package/src/render-system-extract.ts +127 -42
- package/src/render-system.ts +35 -7
- package/src/scene/render-scene.ts +8 -0
- package/src/ssao-config.ts +25 -8
- package/src/ssr/graph.ts +78 -58
- package/src/temporal/gpu.ts +5 -0
- package/src/temporal/standard-scene-data.ts +7 -1
- package/src/temporal/target-coverage-attachment.ts +72 -0
- package/src/temporal/temporal-view.ts +10 -4
- package/src/temporal/view.ts +12 -2
- package/src/transmission/__tests__/standard-transmission.dawn.test.ts +24 -9
- package/src/typed-render-graph-primitives.ts +38 -0
- package/src/volume/component.ts +1 -0
- package/src/volume/passes.ts +3 -3
- package/dist/chunk-33XZFTQU.mjs.map +0 -1
- package/dist/chunk-BQVQOQJH.mjs.map +0 -1
- package/dist/chunk-BTWH7BP4.mjs.map +0 -1
- package/dist/chunk-FUODTTIA.mjs.map +0 -1
- package/dist/chunk-JQTSEI3Z.mjs.map +0 -1
- package/dist/chunk-NICXSHAT.mjs.map +0 -1
- package/dist/chunk-NRNXBLIJ.mjs.map +0 -1
- package/dist/chunk-S4DNKQYQ.mjs.map +0 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { RhiAdapter, RhiDevice } from '@forgeax/engine-rhi';
|
|
2
2
|
import { EXTENDED_LIGHTING_REQUIRED_SAMPLED_TEXTURES } from '../prepare/extended-lighting/resources';
|
|
3
|
+
import { STANDARD_PBR_REQUIRED_SAMPLED_TEXTURES } from './shader-prewarm-policy';
|
|
3
4
|
|
|
4
5
|
const COMPRESSION_FEATURES: GPUFeatureName[] = [
|
|
5
6
|
'texture-compression-bc',
|
|
@@ -7,13 +8,17 @@ const COMPRESSION_FEATURES: GPUFeatureName[] = [
|
|
|
7
8
|
'texture-compression-astc',
|
|
8
9
|
];
|
|
9
10
|
|
|
10
|
-
// The Standard transmission fragment ABI needs
|
|
11
|
+
// The Standard transmission fragment ABI needs 18 sampled textures across its
|
|
11
12
|
// material, IBL, clustered-light, and SSAO bindings. Ordinary Standard draws
|
|
12
13
|
// use the non-transmission variant and remain valid on the WebGPU minimum of
|
|
13
|
-
// 16. The extended-lighting topology reserves
|
|
14
|
+
// 16. The extended-lighting topology reserves 21, so a device descriptor must
|
|
14
15
|
// request that larger limit when the adapter can provide it; adapter limits are
|
|
15
16
|
// not automatically inherited by requestDevice().
|
|
16
|
-
export
|
|
17
|
+
export { STANDARD_PBR_REQUIRED_SAMPLED_TEXTURES };
|
|
18
|
+
|
|
19
|
+
export function transmissionBackdropAvailable(limit: number | undefined): boolean {
|
|
20
|
+
return limit === undefined || limit >= STANDARD_PBR_REQUIRED_SAMPLED_TEXTURES;
|
|
21
|
+
}
|
|
17
22
|
/**
|
|
18
23
|
* The complete Standard physical root can declare all optional texture slots.
|
|
19
24
|
* Its material group therefore needs one more sampled-texture entry than the
|
|
@@ -21,7 +26,7 @@ export const STANDARD_PBR_REQUIRED_SAMPLED_TEXTURES = 17;
|
|
|
21
26
|
* provide it; lower-capability devices remain fail-closed at material
|
|
22
27
|
* pipeline admission instead of receiving an invalid WebGPU layout.
|
|
23
28
|
*/
|
|
24
|
-
export const STANDARD_PHYSICAL_REQUIRED_SAMPLED_TEXTURES =
|
|
29
|
+
export const STANDARD_PHYSICAL_REQUIRED_SAMPLED_TEXTURES = 22;
|
|
25
30
|
|
|
26
31
|
export interface DeviceFeatureAdmissionOptions {
|
|
27
32
|
readonly gpuPassTiming?: object | undefined;
|
|
@@ -2,7 +2,7 @@ import type { EntityHandle, World } from '@forgeax/engine-ecs';
|
|
|
2
2
|
import { FixedTime } from '@forgeax/engine-ecs';
|
|
3
3
|
import { AssetGuid } from '@forgeax/engine-pack/guid';
|
|
4
4
|
import { Transform } from '@forgeax/engine-scene';
|
|
5
|
-
import { err, type Handle, type Result } from '@forgeax/engine-types';
|
|
5
|
+
import { err, type Handle, ok, type Result } from '@forgeax/engine-types';
|
|
6
6
|
import { MeshFilter } from '../components/mesh-filter';
|
|
7
7
|
import { MeshRenderer } from '../components/mesh-renderer';
|
|
8
8
|
import type { GpuResidencyCache } from '../device/gpu-residency';
|
|
@@ -26,6 +26,15 @@ export interface DynamicGeometryHostImplementation {
|
|
|
26
26
|
candidate: DynamicGeometryCandidate,
|
|
27
27
|
ordering: DynamicGeometryOrdering,
|
|
28
28
|
): Result<DynamicGeometryCandidate, DynamicGeometryError>;
|
|
29
|
+
/**
|
|
30
|
+
* Atomically swap distinct entities in one attached World. Returned failures
|
|
31
|
+
* retain old bindings; an uncertain rollback throws to require paired rebuild.
|
|
32
|
+
* Invoke inside PhysicsWorld.admitDerivedShapeCandidates commitGeometry.
|
|
33
|
+
*/
|
|
34
|
+
acceptDynamicGeometryCandidates(
|
|
35
|
+
candidates: readonly DynamicGeometryCandidate[],
|
|
36
|
+
ordering: DynamicGeometryOrdering,
|
|
37
|
+
): Result<readonly DynamicGeometryCandidate[], DynamicGeometryError>;
|
|
29
38
|
dynamicGeometryReceipt(candidate: DynamicGeometryCandidate): DynamicGeometryReceipt | undefined;
|
|
30
39
|
cancelDynamicGeometry(candidate: DynamicGeometryCandidate): Result<void, DynamicGeometryError>;
|
|
31
40
|
retireDynamicGeometry(candidate: DynamicGeometryCandidate): Result<void, DynamicGeometryError>;
|
|
@@ -270,7 +279,188 @@ export function createDynamicGeometryHost(options: {
|
|
|
270
279
|
else void completion.then(finish, finish);
|
|
271
280
|
return completion;
|
|
272
281
|
};
|
|
273
|
-
|
|
282
|
+
const acceptCandidate = (
|
|
283
|
+
candidate: DynamicGeometryCandidate,
|
|
284
|
+
ordering: DynamicGeometryOrdering,
|
|
285
|
+
deferredSupersession?: Set<HostCandidateRecord>,
|
|
286
|
+
): Result<DynamicGeometryCandidate, DynamicGeometryError> => {
|
|
287
|
+
synchronizeGeneration();
|
|
288
|
+
const world = candidate.world as World;
|
|
289
|
+
const hostRecordById = hostCandidates.get(candidate.candidateId);
|
|
290
|
+
const hostRecord = candidateRecord(candidate);
|
|
291
|
+
if (hostRecord === undefined && hostRecordById !== undefined)
|
|
292
|
+
return err(
|
|
293
|
+
new DynamicGeometryError(
|
|
294
|
+
'dynamic-geometry-receipt-mismatch',
|
|
295
|
+
'admission uses the exact host candidate owner and lifecycle credential',
|
|
296
|
+
'discard the altered credential and use the value returned by preparation',
|
|
297
|
+
{ candidateId: candidate.candidateId },
|
|
298
|
+
),
|
|
299
|
+
);
|
|
300
|
+
if (hostRecord !== undefined && candidate.state !== 'prepared')
|
|
301
|
+
return err(
|
|
302
|
+
new DynamicGeometryError(
|
|
303
|
+
'dynamic-geometry-candidate-state',
|
|
304
|
+
'admission consumes a prepared candidate exactly once',
|
|
305
|
+
'retain the accepted or published candidate receipt instead of admitting it again',
|
|
306
|
+
{ candidateId: candidate.candidateId },
|
|
307
|
+
),
|
|
308
|
+
);
|
|
309
|
+
if (ordering === undefined || !attachedWorlds.has(world) || ordering.world !== candidate.world)
|
|
310
|
+
return err(
|
|
311
|
+
new DynamicGeometryError(
|
|
312
|
+
'dynamic-geometry-world-not-attached',
|
|
313
|
+
'candidate and fixed-step ordering belong to the same attached World',
|
|
314
|
+
'attach the World and submit its PhysicsWorld ordering before accepting geometry',
|
|
315
|
+
{ candidateId: candidate.candidateId },
|
|
316
|
+
),
|
|
317
|
+
);
|
|
318
|
+
if (!candidate.gpuReady)
|
|
319
|
+
return err(
|
|
320
|
+
new DynamicGeometryError(
|
|
321
|
+
'dynamic-geometry-gpu-not-ready',
|
|
322
|
+
'candidate has standard GPU residency before acceptance',
|
|
323
|
+
'prepare with an existing MeshAsset shared handle and repair residency failures',
|
|
324
|
+
{ candidateId: candidate.candidateId },
|
|
325
|
+
),
|
|
326
|
+
);
|
|
327
|
+
if (
|
|
328
|
+
hostRecord === undefined ||
|
|
329
|
+
!hasRenderableBinding(world, candidate.entity) ||
|
|
330
|
+
!hasMaterialIdentity(world, candidate.entity, candidate.mesh, candidate.materialIdentity)
|
|
331
|
+
)
|
|
332
|
+
return err(
|
|
333
|
+
new DynamicGeometryError(
|
|
334
|
+
'dynamic-geometry-invalid',
|
|
335
|
+
'the candidate is bound to a live ECS MeshFilter and MeshRenderer',
|
|
336
|
+
'keep the candidate MeshFilter binding intact until the consuming draw',
|
|
337
|
+
{ candidateId: candidate.candidateId },
|
|
338
|
+
),
|
|
339
|
+
);
|
|
340
|
+
if (meshHandleOf(world, candidate.entity) !== hostRecord.previousMeshHandle)
|
|
341
|
+
return err(
|
|
342
|
+
new DynamicGeometryError(
|
|
343
|
+
'dynamic-geometry-invalid',
|
|
344
|
+
'the old MeshFilter binding remains visible until candidate acceptance',
|
|
345
|
+
'do not replace the live ECS binding while geometry is prepared',
|
|
346
|
+
{ candidateId: candidate.candidateId },
|
|
347
|
+
),
|
|
348
|
+
);
|
|
349
|
+
const currentFixedStep = fixedStepOf(world);
|
|
350
|
+
if (
|
|
351
|
+
currentFixedStep === undefined ||
|
|
352
|
+
candidate.fixedStep === undefined ||
|
|
353
|
+
candidate.fixedStep > currentFixedStep ||
|
|
354
|
+
ordering.fixedStep !== currentFixedStep
|
|
355
|
+
)
|
|
356
|
+
return err(
|
|
357
|
+
new DynamicGeometryError(
|
|
358
|
+
'dynamic-geometry-ordering-required',
|
|
359
|
+
'ordering matches the current World tick, no earlier than preparation',
|
|
360
|
+
'accept a prepared candidate during the current fixed-step admission',
|
|
361
|
+
{
|
|
362
|
+
candidateId: candidate.candidateId,
|
|
363
|
+
actual: {
|
|
364
|
+
candidate: candidate.fixedStep,
|
|
365
|
+
currentFixedStep,
|
|
366
|
+
ordering: ordering.fixedStep,
|
|
367
|
+
},
|
|
368
|
+
},
|
|
369
|
+
),
|
|
370
|
+
);
|
|
371
|
+
if (candidate.physicsEntity !== undefined) {
|
|
372
|
+
const publication = physicsPublicationOf(world, candidate.physicsEntity, true);
|
|
373
|
+
if (
|
|
374
|
+
publication === undefined ||
|
|
375
|
+
publication.fixedStep !== currentFixedStep ||
|
|
376
|
+
publication.revision !== candidate.revision
|
|
377
|
+
)
|
|
378
|
+
return err(
|
|
379
|
+
new DynamicGeometryError(
|
|
380
|
+
'dynamic-geometry-ordering-required',
|
|
381
|
+
'candidate physicsEntity has the active paired PhysicsWorld admission',
|
|
382
|
+
'accept geometry inside admitDerivedShapeCandidate commitGeometry, before physics step',
|
|
383
|
+
{ candidateId: candidate.candidateId, actual: publication },
|
|
384
|
+
),
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
// One ECS MeshFilter has one visible geometry owner. Once this
|
|
388
|
+
// candidate is accepted, any older prepared/accepted candidate for the
|
|
389
|
+
// same entity can no longer become a receipt-bearing draw. Cancel it
|
|
390
|
+
// after the new ECS swap so its candidate-only residency is no longer
|
|
391
|
+
// protected as the live draw owner. Published candidates stay
|
|
392
|
+
// receipt-bound and are retired independently.
|
|
393
|
+
const superseded = [...hostCandidates.values()].filter(
|
|
394
|
+
(record) =>
|
|
395
|
+
record.candidate.candidateId !== candidate.candidateId &&
|
|
396
|
+
record.world === world &&
|
|
397
|
+
record.entity === candidate.entity &&
|
|
398
|
+
!record.published,
|
|
399
|
+
);
|
|
400
|
+
const accepted = lifecycle.accept(candidate, ordering);
|
|
401
|
+
if (!accepted.ok) return accepted;
|
|
402
|
+
const swapped = world.set(candidate.entity as EntityHandle, MeshFilter, {
|
|
403
|
+
assetHandle: hostRecord.meshHandle,
|
|
404
|
+
});
|
|
405
|
+
if (!swapped.ok) {
|
|
406
|
+
lifecycle.cancel(accepted.value);
|
|
407
|
+
hostCandidates.delete(candidate.candidateId);
|
|
408
|
+
cleanupCandidateResidency(hostRecord);
|
|
409
|
+
const released = releaseMeshLeases(hostRecord);
|
|
410
|
+
if (released !== undefined) return err(released);
|
|
411
|
+
return err(
|
|
412
|
+
new DynamicGeometryError(
|
|
413
|
+
'dynamic-geometry-invalid',
|
|
414
|
+
'the candidate MeshFilter swap commits through the ECS write barrier',
|
|
415
|
+
'restore the live entity and retry from a fresh candidate',
|
|
416
|
+
{ candidateId: candidate.candidateId, actual: swapped.error },
|
|
417
|
+
),
|
|
418
|
+
);
|
|
419
|
+
}
|
|
420
|
+
hostRecord.candidate = accepted.value;
|
|
421
|
+
hostRecord.accepted = true;
|
|
422
|
+
if (deferredSupersession === undefined) options.onTopologyChanged?.();
|
|
423
|
+
// Keep the World handle leased through the final GPU submission; its slot
|
|
424
|
+
// must not be reused while a retired frame still references the old mesh.
|
|
425
|
+
// Cleanup happens only after the new ECS MeshFilter is visible. Before
|
|
426
|
+
// the swap, hasMeshBinding intentionally protects the older candidate's
|
|
427
|
+
// GPU copy because it is still the live draw owner.
|
|
428
|
+
if (deferredSupersession !== undefined) {
|
|
429
|
+
for (const record of superseded) deferredSupersession.add(record);
|
|
430
|
+
return accepted;
|
|
431
|
+
}
|
|
432
|
+
for (const record of superseded) {
|
|
433
|
+
const completion = cleanupCandidateResidency(record);
|
|
434
|
+
const cancelled = lifecycle.cancel(record.candidate, completion);
|
|
435
|
+
if (!cancelled.ok && cancelled.error.code !== 'dynamic-geometry-candidate-not-found') {
|
|
436
|
+
const restored = world.set(candidate.entity as EntityHandle, MeshFilter, {
|
|
437
|
+
assetHandle: hostRecord.previousMeshHandle,
|
|
438
|
+
});
|
|
439
|
+
lifecycle.cancel(accepted.value);
|
|
440
|
+
hostCandidates.delete(candidate.candidateId);
|
|
441
|
+
cleanupCandidateResidency(hostRecord);
|
|
442
|
+
releasePreviousMeshLease(hostRecord);
|
|
443
|
+
return err(
|
|
444
|
+
new DynamicGeometryError(
|
|
445
|
+
'dynamic-geometry-invalid',
|
|
446
|
+
'candidate supersession cancels the prior credential atomically',
|
|
447
|
+
'keep the prior ECS binding and candidate lifecycle intact when supersession fails',
|
|
448
|
+
{
|
|
449
|
+
candidateId: candidate.candidateId,
|
|
450
|
+
actual: {
|
|
451
|
+
cancelled: cancelled.error,
|
|
452
|
+
restored: restored.ok ? undefined : restored.error,
|
|
453
|
+
},
|
|
454
|
+
},
|
|
455
|
+
),
|
|
456
|
+
);
|
|
457
|
+
}
|
|
458
|
+
hostCandidates.delete(record.candidate.candidateId);
|
|
459
|
+
scheduleCandidateCleanup(record);
|
|
460
|
+
}
|
|
461
|
+
return accepted;
|
|
462
|
+
};
|
|
463
|
+
const host: DynamicGeometryHostImplementation = {
|
|
274
464
|
prepareDynamicGeometry(input) {
|
|
275
465
|
const generation = synchronizeGeneration();
|
|
276
466
|
const world = input.world as World;
|
|
@@ -464,182 +654,64 @@ export function createDynamicGeometryHost(options: {
|
|
|
464
654
|
});
|
|
465
655
|
return prepared;
|
|
466
656
|
},
|
|
467
|
-
acceptDynamicGeometry(candidate, ordering)
|
|
657
|
+
acceptDynamicGeometry: (candidate, ordering) => acceptCandidate(candidate, ordering),
|
|
658
|
+
acceptDynamicGeometryCandidates(candidates, ordering) {
|
|
468
659
|
synchronizeGeneration();
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
const hostRecord = candidateRecord(candidate);
|
|
472
|
-
if (hostRecord === undefined && hostRecordById !== undefined)
|
|
473
|
-
return err(
|
|
474
|
-
new DynamicGeometryError(
|
|
475
|
-
'dynamic-geometry-receipt-mismatch',
|
|
476
|
-
'admission uses the exact host candidate owner and lifecycle credential',
|
|
477
|
-
'discard the altered credential and use the value returned by preparation',
|
|
478
|
-
{ candidateId: candidate.candidateId },
|
|
479
|
-
),
|
|
480
|
-
);
|
|
481
|
-
if (hostRecord !== undefined && candidate.state !== 'prepared')
|
|
482
|
-
return err(
|
|
483
|
-
new DynamicGeometryError(
|
|
484
|
-
'dynamic-geometry-candidate-state',
|
|
485
|
-
'admission consumes a prepared candidate exactly once',
|
|
486
|
-
'retain the accepted or published candidate receipt instead of admitting it again',
|
|
487
|
-
{ candidateId: candidate.candidateId },
|
|
488
|
-
),
|
|
489
|
-
);
|
|
490
|
-
if (
|
|
491
|
-
ordering === undefined ||
|
|
492
|
-
!attachedWorlds.has(world) ||
|
|
493
|
-
ordering.world !== candidate.world
|
|
494
|
-
)
|
|
495
|
-
return err(
|
|
496
|
-
new DynamicGeometryError(
|
|
497
|
-
'dynamic-geometry-world-not-attached',
|
|
498
|
-
'candidate and fixed-step ordering belong to the same attached World',
|
|
499
|
-
'attach the World and submit its PhysicsWorld ordering before accepting geometry',
|
|
500
|
-
{ candidateId: candidate.candidateId },
|
|
501
|
-
),
|
|
502
|
-
);
|
|
503
|
-
if (!candidate.gpuReady)
|
|
504
|
-
return err(
|
|
505
|
-
new DynamicGeometryError(
|
|
506
|
-
'dynamic-geometry-gpu-not-ready',
|
|
507
|
-
'candidate has standard GPU residency before acceptance',
|
|
508
|
-
'prepare with an existing MeshAsset shared handle and repair residency failures',
|
|
509
|
-
{ candidateId: candidate.candidateId },
|
|
510
|
-
),
|
|
511
|
-
);
|
|
660
|
+
// The existing lifecycle's candidate budget bounds this group; there is
|
|
661
|
+
// no second queue or independently tunable batch allocation limit.
|
|
512
662
|
if (
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
663
|
+
candidates.length === 0 ||
|
|
664
|
+
candidates.length > hostCandidates.size ||
|
|
665
|
+
new Set(candidates.map((candidate) => candidate.entity)).size !== candidates.length ||
|
|
666
|
+
candidates.some(
|
|
667
|
+
(candidate) =>
|
|
668
|
+
candidate.world !== ordering?.world || candidateRecord(candidate) === undefined,
|
|
669
|
+
)
|
|
516
670
|
)
|
|
517
671
|
return err(
|
|
518
672
|
new DynamicGeometryError(
|
|
519
673
|
'dynamic-geometry-invalid',
|
|
520
|
-
'
|
|
521
|
-
'
|
|
522
|
-
{ candidateId: candidate.candidateId },
|
|
674
|
+
'a geometry group contains owned candidates for distinct entities in one World',
|
|
675
|
+
'prepare one candidate per entity and accept the nonempty group in its paired physics admission',
|
|
523
676
|
),
|
|
524
677
|
);
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
678
|
+
const accepted: DynamicGeometryCandidate[] = [];
|
|
679
|
+
const superseded = new Set<HostCandidateRecord>();
|
|
680
|
+
const rollback = (): void => {
|
|
681
|
+
const failures: DynamicGeometryError[] = [];
|
|
682
|
+
for (const candidate of [...accepted].reverse()) {
|
|
683
|
+
const result = host.cancelDynamicGeometry(candidate);
|
|
684
|
+
if (!result.ok) failures.push(result.error);
|
|
685
|
+
}
|
|
686
|
+
// A returned failure promises old state. A throw deliberately routes
|
|
687
|
+
// the paired PhysicsWorld into rebuild-required when that proof fails.
|
|
688
|
+
if (failures.length > 0)
|
|
689
|
+
throw new DynamicGeometryError(
|
|
528
690
|
'dynamic-geometry-invalid',
|
|
529
|
-
'
|
|
530
|
-
'
|
|
531
|
-
{
|
|
532
|
-
),
|
|
533
|
-
);
|
|
534
|
-
const currentFixedStep = fixedStepOf(world);
|
|
535
|
-
if (
|
|
536
|
-
currentFixedStep === undefined ||
|
|
537
|
-
candidate.fixedStep === undefined ||
|
|
538
|
-
candidate.fixedStep > currentFixedStep ||
|
|
539
|
-
ordering.fixedStep !== currentFixedStep
|
|
540
|
-
)
|
|
541
|
-
return err(
|
|
542
|
-
new DynamicGeometryError(
|
|
543
|
-
'dynamic-geometry-ordering-required',
|
|
544
|
-
'ordering matches the current World tick, no earlier than preparation',
|
|
545
|
-
'accept a prepared candidate during the current fixed-step admission',
|
|
546
|
-
{
|
|
547
|
-
candidateId: candidate.candidateId,
|
|
548
|
-
actual: {
|
|
549
|
-
candidate: candidate.fixedStep,
|
|
550
|
-
currentFixedStep,
|
|
551
|
-
ordering: ordering.fixedStep,
|
|
552
|
-
},
|
|
553
|
-
},
|
|
554
|
-
),
|
|
555
|
-
);
|
|
556
|
-
if (candidate.physicsEntity !== undefined) {
|
|
557
|
-
const publication = physicsPublicationOf(world, candidate.physicsEntity, true);
|
|
558
|
-
if (
|
|
559
|
-
publication === undefined ||
|
|
560
|
-
publication.fixedStep !== currentFixedStep ||
|
|
561
|
-
publication.revision !== candidate.revision
|
|
562
|
-
)
|
|
563
|
-
return err(
|
|
564
|
-
new DynamicGeometryError(
|
|
565
|
-
'dynamic-geometry-ordering-required',
|
|
566
|
-
'candidate physicsEntity has the active paired PhysicsWorld admission',
|
|
567
|
-
'accept geometry inside admitDerivedShapeCandidate commitGeometry, before physics step',
|
|
568
|
-
{ candidateId: candidate.candidateId, actual: publication },
|
|
569
|
-
),
|
|
691
|
+
'a failed geometry group restores every previous ECS binding',
|
|
692
|
+
'rebuild the paired World and Renderer before rendering or querying physics',
|
|
693
|
+
{ actual: failures },
|
|
570
694
|
);
|
|
695
|
+
};
|
|
696
|
+
for (const candidate of candidates) {
|
|
697
|
+
const result = acceptCandidate(candidate, ordering, superseded);
|
|
698
|
+
if (!result.ok) {
|
|
699
|
+
rollback();
|
|
700
|
+
return err(result.error);
|
|
701
|
+
}
|
|
702
|
+
accepted.push(result.value);
|
|
571
703
|
}
|
|
572
|
-
//
|
|
573
|
-
//
|
|
574
|
-
// same entity can no longer become a receipt-bearing draw. Cancel it
|
|
575
|
-
// after the new ECS swap so its candidate-only residency is no longer
|
|
576
|
-
// protected as the live draw owner. Published candidates stay
|
|
577
|
-
// receipt-bound and are retired independently.
|
|
578
|
-
const superseded = [...hostCandidates.values()].filter(
|
|
579
|
-
(record) =>
|
|
580
|
-
record.candidate.candidateId !== candidate.candidateId &&
|
|
581
|
-
record.world === world &&
|
|
582
|
-
record.entity === candidate.entity &&
|
|
583
|
-
!record.published,
|
|
584
|
-
);
|
|
585
|
-
const accepted = lifecycle.accept(candidate, ordering);
|
|
586
|
-
if (!accepted.ok) return accepted;
|
|
587
|
-
const swapped = world.set(candidate.entity as EntityHandle, MeshFilter, {
|
|
588
|
-
assetHandle: hostRecord.meshHandle,
|
|
589
|
-
});
|
|
590
|
-
if (!swapped.ok) {
|
|
591
|
-
lifecycle.cancel(accepted.value);
|
|
592
|
-
hostCandidates.delete(candidate.candidateId);
|
|
593
|
-
cleanupCandidateResidency(hostRecord);
|
|
594
|
-
const released = releaseMeshLeases(hostRecord);
|
|
595
|
-
if (released !== undefined) return err(released);
|
|
596
|
-
return err(
|
|
597
|
-
new DynamicGeometryError(
|
|
598
|
-
'dynamic-geometry-invalid',
|
|
599
|
-
'the candidate MeshFilter swap commits through the ECS write barrier',
|
|
600
|
-
'restore the live entity and retry from a fresh candidate',
|
|
601
|
-
{ candidateId: candidate.candidateId, actual: swapped.error },
|
|
602
|
-
),
|
|
603
|
-
);
|
|
604
|
-
}
|
|
605
|
-
hostRecord.candidate = accepted.value;
|
|
606
|
-
hostRecord.accepted = true;
|
|
607
|
-
options.onTopologyChanged?.();
|
|
608
|
-
// Keep the World handle leased through the final GPU submission; its slot
|
|
609
|
-
// must not be reused while a retired frame still references the old mesh.
|
|
610
|
-
// Cleanup happens only after the new ECS MeshFilter is visible. Before
|
|
611
|
-
// the swap, hasMeshBinding intentionally protects the older candidate's
|
|
612
|
-
// GPU copy because it is still the live draw owner.
|
|
704
|
+
// Keep all previous unpublished credentials and leases until every ECS
|
|
705
|
+
// swap succeeds, so a kth-member rejection can still draw the old group.
|
|
613
706
|
for (const record of superseded) {
|
|
614
707
|
const completion = cleanupCandidateResidency(record);
|
|
615
|
-
const
|
|
616
|
-
if (!
|
|
617
|
-
|
|
618
|
-
assetHandle: hostRecord.previousMeshHandle,
|
|
619
|
-
});
|
|
620
|
-
lifecycle.cancel(accepted.value);
|
|
621
|
-
hostCandidates.delete(candidate.candidateId);
|
|
622
|
-
cleanupCandidateResidency(hostRecord);
|
|
623
|
-
releasePreviousMeshLease(hostRecord);
|
|
624
|
-
return err(
|
|
625
|
-
new DynamicGeometryError(
|
|
626
|
-
'dynamic-geometry-invalid',
|
|
627
|
-
'candidate supersession cancels the prior credential atomically',
|
|
628
|
-
'keep the prior ECS binding and candidate lifecycle intact when supersession fails',
|
|
629
|
-
{
|
|
630
|
-
candidateId: candidate.candidateId,
|
|
631
|
-
actual: {
|
|
632
|
-
cancelled: cancelled.error,
|
|
633
|
-
restored: restored.ok ? undefined : restored.error,
|
|
634
|
-
},
|
|
635
|
-
},
|
|
636
|
-
),
|
|
637
|
-
);
|
|
638
|
-
}
|
|
639
|
-
hostCandidates.delete(record.candidate.candidateId);
|
|
708
|
+
const result = lifecycle.cancel(record.candidate, completion);
|
|
709
|
+
if (!result.ok && result.error.code !== 'dynamic-geometry-candidate-not-found')
|
|
710
|
+
throw result.error;
|
|
640
711
|
scheduleCandidateCleanup(record);
|
|
641
712
|
}
|
|
642
|
-
|
|
713
|
+
options.onTopologyChanged?.();
|
|
714
|
+
return ok(Object.freeze(accepted));
|
|
643
715
|
},
|
|
644
716
|
dynamicGeometryReceipt(candidate) {
|
|
645
717
|
synchronizeGeneration();
|
|
@@ -837,6 +909,7 @@ export function createDynamicGeometryHost(options: {
|
|
|
837
909
|
return receipts;
|
|
838
910
|
},
|
|
839
911
|
};
|
|
912
|
+
return host;
|
|
840
913
|
}
|
|
841
914
|
|
|
842
915
|
/**
|
|
@@ -72,6 +72,15 @@ export interface RendererHostImplementation {
|
|
|
72
72
|
candidate: DynamicGeometryCandidate,
|
|
73
73
|
ordering: DynamicGeometryOrdering,
|
|
74
74
|
): Result<DynamicGeometryCandidate, DynamicGeometryError>;
|
|
75
|
+
/**
|
|
76
|
+
* Atomically swap distinct entities in one attached World. Returned failures
|
|
77
|
+
* retain old bindings; an uncertain rollback throws to require paired rebuild.
|
|
78
|
+
* Invoke inside PhysicsWorld.admitDerivedShapeCandidates commitGeometry.
|
|
79
|
+
*/
|
|
80
|
+
acceptDynamicGeometryCandidates(
|
|
81
|
+
candidates: readonly DynamicGeometryCandidate[],
|
|
82
|
+
ordering: DynamicGeometryOrdering,
|
|
83
|
+
): Result<readonly DynamicGeometryCandidate[], DynamicGeometryError>;
|
|
75
84
|
dynamicGeometryReceipt(candidate: DynamicGeometryCandidate): DynamicGeometryReceipt | undefined;
|
|
76
85
|
cancelDynamicGeometry(candidate: DynamicGeometryCandidate): Result<void, DynamicGeometryError>;
|
|
77
86
|
retireDynamicGeometry(candidate: DynamicGeometryCandidate): Result<void, DynamicGeometryError>;
|