@vgai/engine 0.5.4 → 0.5.6
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/dist/adapter/adapter-surface.d.ts +30 -0
- package/dist/adapter/adapter-surface.d.ts.map +1 -0
- package/dist/adapter/adapter-surface.js +18 -0
- package/dist/adapter/authoring.d.ts +597 -0
- package/dist/adapter/authoring.d.ts.map +1 -0
- package/dist/adapter/authoring.js +22 -0
- package/dist/adapter/colyseus-networking-adapter.d.ts +77 -0
- package/dist/adapter/colyseus-networking-adapter.d.ts.map +1 -0
- package/dist/adapter/colyseus-networking-adapter.js +76 -0
- package/dist/adapter/first-party-systems.d.ts +23 -0
- package/dist/adapter/first-party-systems.d.ts.map +1 -0
- package/dist/adapter/first-party-systems.js +196 -0
- package/dist/adapter/host-context.d.ts +194 -0
- package/dist/adapter/host-context.d.ts.map +1 -0
- package/dist/adapter/host-context.js +80 -0
- package/dist/adapter/index.d.ts +33 -0
- package/dist/adapter/index.d.ts.map +1 -0
- package/dist/adapter/index.js +25 -0
- package/dist/adapter/ingest/game-contract.d.ts +53 -0
- package/dist/adapter/ingest/game-contract.d.ts.map +1 -0
- package/dist/adapter/ingest/game-contract.js +30 -0
- package/dist/adapter/ingest/scene-capture.d.ts +121 -0
- package/dist/adapter/ingest/scene-capture.d.ts.map +1 -0
- package/dist/adapter/ingest/scene-capture.js +270 -0
- package/dist/adapter/ingest/structural-ids.d.ts +64 -0
- package/dist/adapter/ingest/structural-ids.d.ts.map +1 -0
- package/dist/adapter/ingest/structural-ids.js +110 -0
- package/dist/adapter/ingest/upstream-pin.d.ts +44 -0
- package/dist/adapter/ingest/upstream-pin.d.ts.map +1 -0
- package/dist/adapter/ingest/upstream-pin.js +47 -0
- package/dist/adapter/loop-gate-report.d.ts +34 -0
- package/dist/adapter/loop-gate-report.d.ts.map +1 -0
- package/dist/adapter/loop-gate-report.js +37 -0
- package/dist/adapter/rapier-physics-adapter.d.ts +69 -0
- package/dist/adapter/rapier-physics-adapter.d.ts.map +1 -0
- package/dist/adapter/rapier-physics-adapter.js +135 -0
- package/dist/adapter/root-adapter.d.ts +195 -0
- package/dist/adapter/root-adapter.d.ts.map +1 -0
- package/dist/adapter/root-adapter.js +25 -0
- package/dist/adapter/setup-three-root-adapter.d.ts +87 -0
- package/dist/adapter/setup-three-root-adapter.d.ts.map +1 -0
- package/dist/adapter/setup-three-root-adapter.js +791 -0
- package/dist/adapter/system-adapter.d.ts +453 -0
- package/dist/adapter/system-adapter.d.ts.map +1 -0
- package/dist/adapter/system-adapter.js +13 -0
- package/dist/adapter/transform.d.ts +18 -0
- package/dist/adapter/transform.d.ts.map +1 -0
- package/dist/adapter/transform.js +1 -0
- package/dist/ai/navigation.d.ts +78 -0
- package/dist/ai/navigation.d.ts.map +1 -0
- package/dist/ai/navigation.js +152 -0
- package/dist/animation/anim-graph-types.d.ts +25 -0
- package/dist/animation/anim-graph-types.d.ts.map +1 -0
- package/dist/animation/anim-graph-types.js +1 -0
- package/dist/animation/animation-clock.d.ts +213 -0
- package/dist/animation/animation-clock.d.ts.map +1 -0
- package/dist/animation/animation-clock.js +301 -0
- package/dist/animation/blend-node.d.ts +22 -0
- package/dist/animation/blend-node.d.ts.map +1 -0
- package/dist/animation/blend-node.js +97 -0
- package/dist/animation/camera-ownership.d.ts +249 -0
- package/dist/animation/camera-ownership.d.ts.map +1 -0
- package/dist/animation/camera-ownership.js +169 -0
- package/dist/animation/cinematic-cues.d.ts +200 -0
- package/dist/animation/cinematic-cues.d.ts.map +1 -0
- package/dist/animation/cinematic-cues.js +213 -0
- package/dist/animation/clip-map.d.ts +12 -0
- package/dist/animation/clip-map.d.ts.map +1 -0
- package/dist/animation/clip-map.js +31 -0
- package/dist/animation/cubic-spline-interpolant.d.ts +54 -0
- package/dist/animation/cubic-spline-interpolant.d.ts.map +1 -0
- package/dist/animation/cubic-spline-interpolant.js +111 -0
- package/dist/animation/theatre-clock-binding.d.ts +89 -0
- package/dist/animation/theatre-clock-binding.d.ts.map +1 -0
- package/dist/animation/theatre-clock-binding.js +29 -0
- package/dist/animation/theatre-director.d.ts +277 -0
- package/dist/animation/theatre-director.d.ts.map +1 -0
- package/dist/animation/theatre-director.js +248 -0
- package/dist/animation/theatre-object-binding.d.ts +305 -0
- package/dist/animation/theatre-object-binding.d.ts.map +1 -0
- package/dist/animation/theatre-object-binding.js +351 -0
- package/dist/animation/xstate-animation-binding.d.ts +119 -0
- package/dist/animation/xstate-animation-binding.d.ts.map +1 -0
- package/dist/animation/xstate-animation-binding.js +349 -0
- package/dist/animation/xstate-animation-meta.d.ts +266 -0
- package/dist/animation/xstate-animation-meta.d.ts.map +1 -0
- package/dist/animation/xstate-animation-meta.js +252 -0
- package/dist/asset-formats/camera.d.ts +15 -0
- package/dist/asset-formats/camera.d.ts.map +1 -0
- package/dist/asset-formats/camera.js +15 -0
- package/dist/asset-formats/collider.d.ts +21 -0
- package/dist/asset-formats/collider.d.ts.map +1 -0
- package/dist/asset-formats/collider.js +53 -0
- package/dist/asset-formats/index.d.ts +46 -0
- package/dist/asset-formats/index.d.ts.map +1 -0
- package/dist/asset-formats/index.js +36 -0
- package/dist/asset-formats/instances.d.ts +17 -0
- package/dist/asset-formats/instances.d.ts.map +1 -0
- package/dist/asset-formats/instances.js +31 -0
- package/dist/asset-formats/light.d.ts +21 -0
- package/dist/asset-formats/light.d.ts.map +1 -0
- package/dist/asset-formats/light.js +35 -0
- package/dist/asset-formats/material.d.ts +129 -0
- package/dist/asset-formats/material.d.ts.map +1 -0
- package/dist/asset-formats/material.js +88 -0
- package/dist/asset-formats/mesh.d.ts +22 -0
- package/dist/asset-formats/mesh.d.ts.map +1 -0
- package/dist/asset-formats/mesh.js +64 -0
- package/dist/asset-formats/parse.d.ts +20 -0
- package/dist/asset-formats/parse.d.ts.map +1 -0
- package/dist/asset-formats/parse.js +34 -0
- package/dist/asset-formats/particles.d.ts +1390 -0
- package/dist/asset-formats/particles.d.ts.map +1 -0
- package/dist/asset-formats/particles.js +319 -0
- package/dist/asset-formats/render-env.d.ts +387 -0
- package/dist/asset-formats/render-env.d.ts.map +1 -0
- package/dist/asset-formats/render-env.js +300 -0
- package/dist/asset-formats/tuples.d.ts +12 -0
- package/dist/asset-formats/tuples.d.ts.map +1 -0
- package/dist/asset-formats/tuples.js +14 -0
- package/dist/asset-loaders.d.ts +97 -0
- package/dist/asset-loaders.d.ts.map +1 -0
- package/dist/asset-loaders.js +209 -0
- package/dist/asset-parse-error.d.ts +24 -0
- package/dist/asset-parse-error.d.ts.map +1 -0
- package/dist/asset-parse-error.js +29 -0
- package/dist/asset-registry.d.ts +38 -0
- package/dist/asset-registry.d.ts.map +1 -0
- package/dist/asset-registry.js +66 -0
- package/dist/assets.d.ts +26 -0
- package/dist/assets.d.ts.map +1 -0
- package/dist/assets.js +55 -0
- package/dist/audio/wav-encode.d.ts +38 -0
- package/dist/audio/wav-encode.d.ts.map +1 -0
- package/dist/audio/wav-encode.js +97 -0
- package/dist/canvas-react/engine-bridge.d.ts +45 -0
- package/dist/canvas-react/engine-bridge.d.ts.map +1 -0
- package/dist/canvas-react/engine-bridge.js +45 -0
- package/dist/canvas-react/index.d.ts +42 -0
- package/dist/canvas-react/index.d.ts.map +1 -0
- package/dist/canvas-react/index.js +41 -0
- package/dist/canvas-react/pixi-primitive.d.ts +85 -0
- package/dist/canvas-react/pixi-primitive.d.ts.map +1 -0
- package/dist/canvas-react/pixi-primitive.js +181 -0
- package/dist/canvas-react/pixi-react-adapter.d.ts +71 -0
- package/dist/canvas-react/pixi-react-adapter.d.ts.map +1 -0
- package/dist/canvas-react/pixi-react-adapter.js +229 -0
- package/dist/canvas-react/pixi-react-root-factory.d.ts +56 -0
- package/dist/canvas-react/pixi-react-root-factory.d.ts.map +1 -0
- package/dist/canvas-react/pixi-react-root-factory.js +73 -0
- package/dist/canvas-react/world-context.d.ts +88 -0
- package/dist/canvas-react/world-context.d.ts.map +1 -0
- package/dist/canvas-react/world-context.js +212 -0
- package/dist/config.d.ts +9 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +8 -0
- package/dist/core/frame-pacing.d.ts +90 -0
- package/dist/core/frame-pacing.d.ts.map +1 -0
- package/dist/core/frame-pacing.js +89 -0
- package/dist/core/game-loop.d.ts +62 -0
- package/dist/core/game-loop.d.ts.map +1 -0
- package/dist/core/game-loop.js +189 -0
- package/dist/core/seeded-random.d.ts +69 -0
- package/dist/core/seeded-random.d.ts.map +1 -0
- package/dist/core/seeded-random.js +121 -0
- package/dist/core/sim-clock.d.ts +199 -0
- package/dist/core/sim-clock.d.ts.map +1 -0
- package/dist/core/sim-clock.js +291 -0
- package/dist/core/system-runner.d.ts +96 -0
- package/dist/core/system-runner.d.ts.map +1 -0
- package/dist/core/system-runner.js +253 -0
- package/dist/core/types.d.ts +111 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +25 -0
- package/dist/data/data-asset.d.ts +101 -0
- package/dist/data/data-asset.d.ts.map +1 -0
- package/dist/data/data-asset.js +122 -0
- package/dist/data/data-check-core.d.ts +109 -0
- package/dist/data/data-check-core.d.ts.map +1 -0
- package/dist/data/data-check-core.js +177 -0
- package/dist/data/data-ref.d.ts +79 -0
- package/dist/data/data-ref.d.ts.map +1 -0
- package/dist/data/data-ref.js +130 -0
- package/dist/data/vite-plugin-data.d.ts +79 -0
- package/dist/data/vite-plugin-data.d.ts.map +1 -0
- package/dist/data/vite-plugin-data.js +217 -0
- package/dist/defaults.d.ts +233 -0
- package/dist/defaults.d.ts.map +1 -0
- package/dist/defaults.js +137 -0
- package/dist/dev/chrome-trace.d.ts +45 -0
- package/dist/dev/chrome-trace.d.ts.map +1 -0
- package/dist/dev/chrome-trace.js +114 -0
- package/dist/dev/console-bridge.d.ts +22 -0
- package/dist/dev/console-bridge.d.ts.map +1 -0
- package/dist/dev/console-bridge.js +71 -0
- package/dist/dev/debug-draw.d.ts +24 -0
- package/dist/dev/debug-draw.d.ts.map +1 -0
- package/dist/dev/debug-draw.js +73 -0
- package/dist/dev/instruments.d.ts +133 -0
- package/dist/dev/instruments.d.ts.map +1 -0
- package/dist/dev/instruments.js +231 -0
- package/dist/dev/logger.d.ts +30 -0
- package/dist/dev/logger.d.ts.map +1 -0
- package/dist/dev/logger.js +90 -0
- package/dist/dev/performance-profiler.d.ts +111 -0
- package/dist/dev/performance-profiler.d.ts.map +1 -0
- package/dist/dev/performance-profiler.js +215 -0
- package/dist/dev/render-debug-adapter.d.ts +57 -0
- package/dist/dev/render-debug-adapter.d.ts.map +1 -0
- package/dist/dev/render-debug-adapter.js +141 -0
- package/dist/dev/render-memory.d.ts +53 -0
- package/dist/dev/render-memory.d.ts.map +1 -0
- package/dist/dev/render-memory.js +190 -0
- package/dist/dev/webgl-frame-capture.d.ts +134 -0
- package/dist/dev/webgl-frame-capture.d.ts.map +1 -0
- package/dist/dev/webgl-frame-capture.js +270 -0
- package/dist/dev/webgl-gpu-timer.d.ts +8 -0
- package/dist/dev/webgl-gpu-timer.d.ts.map +1 -0
- package/dist/dev/webgl-gpu-timer.js +52 -0
- package/dist/ecs/scene-index.d.ts +179 -0
- package/dist/ecs/scene-index.d.ts.map +1 -0
- package/dist/ecs/scene-index.js +331 -0
- package/dist/ecs/scene-query.d.ts +6 -0
- package/dist/ecs/scene-query.d.ts.map +1 -0
- package/dist/ecs/scene-query.js +40 -0
- package/dist/ecs/user-data.d.ts +193 -0
- package/dist/ecs/user-data.d.ts.map +1 -0
- package/dist/ecs/user-data.js +145 -0
- package/dist/index.d.ts +35 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +29 -0
- package/dist/input/input-manager.d.ts +944 -0
- package/dist/input/input-manager.d.ts.map +1 -0
- package/dist/input/input-manager.js +2209 -0
- package/dist/input/input-types.d.ts +174 -0
- package/dist/input/input-types.d.ts.map +1 -0
- package/dist/input/input-types.js +1 -0
- package/dist/input/prompt-labels.d.ts +21 -0
- package/dist/input/prompt-labels.d.ts.map +1 -0
- package/dist/input/prompt-labels.js +121 -0
- package/dist/input/rebind-controller.d.ts +59 -0
- package/dist/input/rebind-controller.d.ts.map +1 -0
- package/dist/input/rebind-controller.js +85 -0
- package/dist/input/schema.d.ts +16 -0
- package/dist/input/schema.d.ts.map +1 -0
- package/dist/input/schema.js +228 -0
- package/dist/loader.d.ts +76 -0
- package/dist/loader.d.ts.map +1 -0
- package/dist/loader.js +114 -0
- package/dist/manifest/editor-port.d.ts +53 -0
- package/dist/manifest/editor-port.d.ts.map +1 -0
- package/dist/manifest/editor-port.js +67 -0
- package/dist/manifest/filename.d.ts +14 -0
- package/dist/manifest/filename.d.ts.map +1 -0
- package/dist/manifest/filename.js +43 -0
- package/dist/manifest/index.d.ts +6 -0
- package/dist/manifest/index.d.ts.map +1 -0
- package/dist/manifest/index.js +9 -0
- package/dist/manifest/load-file.d.ts +7 -0
- package/dist/manifest/load-file.d.ts.map +1 -0
- package/dist/manifest/load-file.js +23 -0
- package/dist/manifest/load.d.ts +161 -0
- package/dist/manifest/load.d.ts.map +1 -0
- package/dist/manifest/load.js +269 -0
- package/dist/manifest/locate.d.ts +24 -0
- package/dist/manifest/locate.d.ts.map +1 -0
- package/dist/manifest/locate.js +48 -0
- package/dist/manifest/schema.d.ts +293 -0
- package/dist/manifest/schema.d.ts.map +1 -0
- package/dist/manifest/schema.js +633 -0
- package/dist/physics/collider-dimensions.d.ts +63 -0
- package/dist/physics/collider-dimensions.d.ts.map +1 -0
- package/dist/physics/collider-dimensions.js +98 -0
- package/dist/physics/collision-system.d.ts +41 -0
- package/dist/physics/collision-system.d.ts.map +1 -0
- package/dist/physics/collision-system.js +64 -0
- package/dist/physics/physics-registry.d.ts +45 -0
- package/dist/physics/physics-registry.d.ts.map +1 -0
- package/dist/physics/physics-registry.js +65 -0
- package/dist/physics/transform-writer.d.ts +16 -0
- package/dist/physics/transform-writer.d.ts.map +1 -0
- package/dist/physics/transform-writer.js +38 -0
- package/dist/pixi/authoring.d.ts +93 -0
- package/dist/pixi/authoring.d.ts.map +1 -0
- package/dist/pixi/authoring.js +207 -0
- package/dist/pixi/index.d.ts +16 -0
- package/dist/pixi/index.d.ts.map +1 -0
- package/dist/pixi/index.js +15 -0
- package/dist/pixi/ingest-iframe.d.ts +82 -0
- package/dist/pixi/ingest-iframe.d.ts.map +1 -0
- package/dist/pixi/ingest-iframe.js +166 -0
- package/dist/pixi/ingest.d.ts +97 -0
- package/dist/pixi/ingest.d.ts.map +1 -0
- package/dist/pixi/ingest.js +40 -0
- package/dist/pixi/physics-registry.d.ts +24 -0
- package/dist/pixi/physics-registry.d.ts.map +1 -0
- package/dist/pixi/physics-registry.js +38 -0
- package/dist/pixi/scene-capture.d.ts +43 -0
- package/dist/pixi/scene-capture.d.ts.map +1 -0
- package/dist/pixi/scene-capture.js +92 -0
- package/dist/pixi/system-adapters.d.ts +16 -0
- package/dist/pixi/system-adapters.d.ts.map +1 -0
- package/dist/pixi/system-adapters.js +37 -0
- package/dist/react/unmanaged-root-detector.d.ts +18 -0
- package/dist/react/unmanaged-root-detector.d.ts.map +1 -0
- package/dist/react/unmanaged-root-detector.js +70 -0
- package/dist/react/use-data.d.ts +60 -0
- package/dist/react/use-data.d.ts.map +1 -0
- package/dist/react/use-data.js +101 -0
- package/dist/react/use-selection.d.ts +54 -0
- package/dist/react/use-selection.d.ts.map +1 -0
- package/dist/react/use-selection.js +71 -0
- package/dist/react/world-state.d.ts +154 -0
- package/dist/react/world-state.d.ts.map +1 -0
- package/dist/react/world-state.js +220 -0
- package/dist/render/auto-batcher.d.ts +34 -0
- package/dist/render/auto-batcher.d.ts.map +1 -0
- package/dist/render/auto-batcher.js +140 -0
- package/dist/render/instance-mesh.d.ts +5 -0
- package/dist/render/instance-mesh.d.ts.map +1 -0
- package/dist/render/instance-mesh.js +19 -0
- package/dist/render/light-camera-factory.d.ts +24 -0
- package/dist/render/light-camera-factory.d.ts.map +1 -0
- package/dist/render/light-camera-factory.js +71 -0
- package/dist/render/lod.d.ts +13 -0
- package/dist/render/lod.d.ts.map +1 -0
- package/dist/render/lod.js +16 -0
- package/dist/render/material-factory.d.ts +15 -0
- package/dist/render/material-factory.d.ts.map +1 -0
- package/dist/render/material-factory.js +186 -0
- package/dist/render/mesh-shadow.d.ts +11 -0
- package/dist/render/mesh-shadow.d.ts.map +1 -0
- package/dist/render/mesh-shadow.js +19 -0
- package/dist/render/particles-factory.d.ts +99 -0
- package/dist/render/particles-factory.d.ts.map +1 -0
- package/dist/render/particles-factory.js +361 -0
- package/dist/render/render-batch-system.d.ts +33 -0
- package/dist/render/render-batch-system.d.ts.map +1 -0
- package/dist/render/render-batch-system.js +223 -0
- package/dist/render/render-features.d.ts +47 -0
- package/dist/render/render-features.d.ts.map +1 -0
- package/dist/render/render-features.js +105 -0
- package/dist/render/render-settings.d.ts +35 -0
- package/dist/render/render-settings.d.ts.map +1 -0
- package/dist/render/render-settings.js +51 -0
- package/dist/render/spark-renderer-lifecycle.d.ts +18 -0
- package/dist/render/spark-renderer-lifecycle.d.ts.map +1 -0
- package/dist/render/spark-renderer-lifecycle.js +57 -0
- package/dist/render/viewport-shading.d.ts +17 -0
- package/dist/render/viewport-shading.d.ts.map +1 -0
- package/dist/render/viewport-shading.js +87 -0
- package/dist/runtime/create-runtime.d.ts +259 -0
- package/dist/runtime/create-runtime.d.ts.map +1 -0
- package/dist/runtime/create-runtime.js +666 -0
- package/dist/runtime/debug-bridge.d.ts +288 -0
- package/dist/runtime/debug-bridge.d.ts.map +1 -0
- package/dist/runtime/debug-bridge.js +277 -0
- package/dist/runtime/debug-registry.d.ts +287 -0
- package/dist/runtime/debug-registry.d.ts.map +1 -0
- package/dist/runtime/debug-registry.js +528 -0
- package/dist/runtime/dev-layers.d.ts +34 -0
- package/dist/runtime/dev-layers.d.ts.map +1 -0
- package/dist/runtime/dev-layers.js +40 -0
- package/dist/runtime/frame-selector-cache.d.ts +41 -0
- package/dist/runtime/frame-selector-cache.d.ts.map +1 -0
- package/dist/runtime/frame-selector-cache.js +65 -0
- package/dist/runtime/game.d.ts +576 -0
- package/dist/runtime/game.d.ts.map +1 -0
- package/dist/runtime/game.js +1003 -0
- package/dist/runtime/gameplay-rng-trap.d.ts +47 -0
- package/dist/runtime/gameplay-rng-trap.d.ts.map +1 -0
- package/dist/runtime/gameplay-rng-trap.js +95 -0
- package/dist/runtime/input-router.d.ts +78 -0
- package/dist/runtime/input-router.d.ts.map +1 -0
- package/dist/runtime/input-router.js +171 -0
- package/dist/runtime/mount-game.d.ts +126 -0
- package/dist/runtime/mount-game.d.ts.map +1 -0
- package/dist/runtime/mount-game.js +159 -0
- package/dist/runtime/mount-manifest.d.ts +174 -0
- package/dist/runtime/mount-manifest.d.ts.map +1 -0
- package/dist/runtime/mount-manifest.js +304 -0
- package/dist/runtime/presentation.d.ts +85 -0
- package/dist/runtime/presentation.d.ts.map +1 -0
- package/dist/runtime/presentation.js +92 -0
- package/dist/runtime/render-audio-control.d.ts +126 -0
- package/dist/runtime/render-audio-control.d.ts.map +1 -0
- package/dist/runtime/render-audio-control.js +116 -0
- package/dist/runtime/render-control.d.ts +347 -0
- package/dist/runtime/render-control.d.ts.map +1 -0
- package/dist/runtime/render-control.js +317 -0
- package/dist/runtime/render-seed.d.ts +56 -0
- package/dist/runtime/render-seed.d.ts.map +1 -0
- package/dist/runtime/render-seed.js +73 -0
- package/dist/runtime/state-bridge.d.ts +60 -0
- package/dist/runtime/state-bridge.d.ts.map +1 -0
- package/dist/runtime/state-bridge.js +62 -0
- package/dist/runtime/types.d.ts +285 -0
- package/dist/runtime/types.d.ts.map +1 -0
- package/dist/runtime/types.js +1 -0
- package/dist/runtime/unexported-game-trap.d.ts +36 -0
- package/dist/runtime/unexported-game-trap.d.ts.map +1 -0
- package/dist/runtime/unexported-game-trap.js +80 -0
- package/dist/setup/setup-audio.d.ts +20 -0
- package/dist/setup/setup-audio.d.ts.map +1 -0
- package/dist/setup/setup-audio.js +41 -0
- package/dist/setup/setup-particles.d.ts +17 -0
- package/dist/setup/setup-particles.d.ts.map +1 -0
- package/dist/setup/setup-particles.js +16 -0
- package/dist/setup/setup-physics.d.ts +25 -0
- package/dist/setup/setup-physics.d.ts.map +1 -0
- package/dist/setup/setup-physics.js +51 -0
- package/dist/setup/setup-renderer.d.ts +82 -0
- package/dist/setup/setup-renderer.d.ts.map +1 -0
- package/dist/setup/setup-renderer.js +381 -0
- package/dist/world3d-react/engine-bridge.d.ts +47 -0
- package/dist/world3d-react/engine-bridge.d.ts.map +1 -0
- package/dist/world3d-react/engine-bridge.js +48 -0
- package/dist/world3d-react/index.d.ts +47 -0
- package/dist/world3d-react/index.d.ts.map +1 -0
- package/dist/world3d-react/index.js +46 -0
- package/dist/world3d-react/r3f-adapter.d.ts +51 -0
- package/dist/world3d-react/r3f-adapter.d.ts.map +1 -0
- package/dist/world3d-react/r3f-adapter.js +368 -0
- package/dist/world3d-react/r3f-root-factory.d.ts +62 -0
- package/dist/world3d-react/r3f-root-factory.d.ts.map +1 -0
- package/dist/world3d-react/r3f-root-factory.js +79 -0
- package/dist/world3d-react/rapier-physics-bridge.d.ts +76 -0
- package/dist/world3d-react/rapier-physics-bridge.d.ts.map +1 -0
- package/dist/world3d-react/rapier-physics-bridge.js +120 -0
- package/dist/world3d-react/renderer-config.d.ts +58 -0
- package/dist/world3d-react/renderer-config.d.ts.map +1 -0
- package/dist/world3d-react/renderer-config.js +96 -0
- package/dist/world3d-react/world-context.d.ts +123 -0
- package/dist/world3d-react/world-context.d.ts.map +1 -0
- package/dist/world3d-react/world-context.js +204 -0
- package/dist-config/config.js +303 -0
- package/dist-config/config.js.map +7 -0
- package/package.json +14 -3
- package/src/adapter/rapier-physics-adapter.ts +77 -34
- package/src/config.ts +8 -0
- package/src/core/frame-pacing.ts +26 -0
- package/src/core/game-loop.ts +4 -3
- package/src/dev/instruments.ts +302 -0
- package/src/runtime/mount-game.ts +6 -0
- package/src/runtime/unexported-game-trap.ts +82 -0
- package/src/setup/setup-renderer.ts +2 -0
- package/src/world3d-react/index.ts +9 -0
- package/src/world3d-react/rapier-physics-bridge.tsx +137 -0
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `SimClock` — P3, the sim-time scheduler (punchlist item P3, rung 4).
|
|
3
|
+
*
|
|
4
|
+
* `await` **is** the scheduler; JS already won that argument. The only thing
|
|
5
|
+
* missing from the engine was *clock binding*: a `delay(seconds)` that resolves
|
|
6
|
+
* on the fixed loop's own accumulator instead of on wall time, so a timer
|
|
7
|
+
* pauses when the game pauses, steps when the game steps, and reproduces
|
|
8
|
+
* exactly in an offline export. That, plus a timed-dispose helper for debris,
|
|
9
|
+
* is this whole module.
|
|
10
|
+
*
|
|
11
|
+
* ## This is NOT `AnimationClock`, and it never will be
|
|
12
|
+
*
|
|
13
|
+
* `animation/animation-clock.ts` is a seekable **cinematic** clock: it plays,
|
|
14
|
+
* pauses, loops, runs in reverse and is `seek()`ed to arbitrary times so
|
|
15
|
+
* Theatre/GSAP timelines and cue evaluators can be scrubbed. `SimClock` is the
|
|
16
|
+
* **monotonic gameplay clock**: it only ever moves forward, one fixed substep
|
|
17
|
+
* at a time, and there is no seek. Both live in one engine because they answer
|
|
18
|
+
* different questions — "where is the cinematic playhead" versus "how much
|
|
19
|
+
* gameplay time has actually elapsed".
|
|
20
|
+
*
|
|
21
|
+
* The reason they can never merge is a fact about Promises: **a Promise cannot
|
|
22
|
+
* un-resolve.** Scrub a seekable clock backwards past a `delay` that already
|
|
23
|
+
* fired and there is no correct behavior — you cannot un-await it. So `delay`
|
|
24
|
+
* binds to the clock that cannot rewind, and cinematic scrubbing stays with the
|
|
25
|
+
* clock that can.
|
|
26
|
+
*
|
|
27
|
+
* ## Two forms, because sync and async are genuinely different here
|
|
28
|
+
*
|
|
29
|
+
* `Game.runTicks` is **synchronous** (`runtime/game.ts`): it runs N substeps in
|
|
30
|
+
* one straight-line loop. A `delay` continuation is a microtask, so under
|
|
31
|
+
* `runTicks` — and under an offline export driven the same way — it does *not*
|
|
32
|
+
* interleave between ticks; it runs when the caller's stack unwinds, after the
|
|
33
|
+
* whole run. Under the ordinary rAF loop each frame yields, so the difference
|
|
34
|
+
* is invisible.
|
|
35
|
+
*
|
|
36
|
+
* Therefore:
|
|
37
|
+
*
|
|
38
|
+
* - `await clock.delay(n)` is the **ergonomic** form. Use it for gameplay.
|
|
39
|
+
* - `clock.after(n, fn)` is the **exact** form: `fn` is invoked inline, during
|
|
40
|
+
* the flush of the exact due tick, under every driver — rAF, `runTicks`,
|
|
41
|
+
* `step()`, offline export.
|
|
42
|
+
* - `clock.disposeAfter(obj, n)` is built on `after`, so debris removal is
|
|
43
|
+
* frame-exact in an offline export rather than "some time after the run".
|
|
44
|
+
*
|
|
45
|
+
* ## Firing rule (deterministic — pinned by `test/sim-clock.test.ts`)
|
|
46
|
+
*
|
|
47
|
+
* - The runtime calls `flush(simT)` immediately after it bumps `tick`/`simT`,
|
|
48
|
+
* inside the same `advanced` guard — so a paused or frozen world fires no
|
|
49
|
+
* timers at all, and `Game.play.step()` fires exactly the timers that one
|
|
50
|
+
* substep makes due.
|
|
51
|
+
* - A timer is due when `simT >= scheduledAt + seconds`.
|
|
52
|
+
* - Due timers run in due-time ascending order, insertion-sequence ascending
|
|
53
|
+
* on ties.
|
|
54
|
+
* - A timer scheduled *during* a flush is never due in that same flush, even
|
|
55
|
+
* at `seconds === 0`. That is what makes a self-rescheduling timer unable to
|
|
56
|
+
* hang the frame.
|
|
57
|
+
*
|
|
58
|
+
* ## Cancellation is `AbortSignal`
|
|
59
|
+
*
|
|
60
|
+
* The same rung-1 web primitive the rest of this program uses. An
|
|
61
|
+
* already-aborted signal makes `delay` reject immediately and `after` a no-op;
|
|
62
|
+
* rejection is a `DOMException` with `name === 'AbortError'`, matching `fetch`.
|
|
63
|
+
* Disposing the clock rejects every pending `delay` the same way — game code
|
|
64
|
+
* awaiting a delay is expected to tolerate rejection exactly as aborted `fetch`
|
|
65
|
+
* callers do.
|
|
66
|
+
*
|
|
67
|
+
* ## Ownership: the clock is GAME-scoped, and only the Game disposes it
|
|
68
|
+
*
|
|
69
|
+
* One clock per `Game`, shared by every world on it (`getSimClock(game)`); a
|
|
70
|
+
* mount with no Game shell builds a private, mount-local one instead. Whoever
|
|
71
|
+
* CREATED a clock disposes it: `GameInternal.dispose()` disposes the
|
|
72
|
+
* game-scoped one — `runtime/create-runtime.ts` calls that after every root has
|
|
73
|
+
* torn down — and a root adapter disposes only its own mount-local fallback.
|
|
74
|
+
* A per-root teardown must NEVER dispose the game-scoped clock: disposing one
|
|
75
|
+
* world's `mounted` is a supported way to end a sub-session while the Game
|
|
76
|
+
* keeps running, and doing so would freeze `now()`, reject every sibling
|
|
77
|
+
* world's pending `delay` and turn its later `after()` calls into silent
|
|
78
|
+
* no-ops. (`seededRandom` and `debugRegistry` are game-scoped the same way, and
|
|
79
|
+
* per-root teardown has never destroyed either — that asymmetry is what
|
|
80
|
+
* exposed the bug.)
|
|
81
|
+
*
|
|
82
|
+
* ## Cancelling your own timers is the GAME's job — warm restart will not
|
|
83
|
+
*
|
|
84
|
+
* A timer outlives the code that scheduled it unless something cancels it, and
|
|
85
|
+
* `hotReload` (warm restart) deliberately does not reach the game-scoped clock:
|
|
86
|
+
* it is per-ROOT, so cancelling here would kill a sibling root's live timers —
|
|
87
|
+
* the same ownership violation as above. There is deliberately no `reset()`.
|
|
88
|
+
*
|
|
89
|
+
* So a stale `after` closure surviving a warm restart is the same hazard class
|
|
90
|
+
* as a stale event listener, and it has the same owner and the same remedy: the
|
|
91
|
+
* game's own cleanup. `hotReload` runs the game's `dispose()` first for exactly
|
|
92
|
+
* this reason, and both `after` and `delay` take an `AbortSignal` so one
|
|
93
|
+
* controller cancels everything a `setup()` scheduled:
|
|
94
|
+
*
|
|
95
|
+
* ```ts
|
|
96
|
+
* export async function setup(ctx: GameContext): Promise<GameCleanup> {
|
|
97
|
+
* const ac = new AbortController();
|
|
98
|
+
* ctx.clock.after(3, () => spawnWave(), { signal: ac.signal });
|
|
99
|
+
* return { dispose: () => ac.abort() }; // cancels timers AND listeners
|
|
100
|
+
* }
|
|
101
|
+
* ```
|
|
102
|
+
*
|
|
103
|
+
* This is the engine-wide "Examples must clean up" contract (CLAUDE.md), not a
|
|
104
|
+
* special rule for timers.
|
|
105
|
+
*
|
|
106
|
+
* ## Deliberately absent
|
|
107
|
+
*
|
|
108
|
+
* - **No `every`/`repeat`/interval.** Nothing needs one, and `after`
|
|
109
|
+
* re-arming itself is three lines the game owns.
|
|
110
|
+
* - **No fiber kernel, no coroutine emulation, no `task.spawn`.** Calling an
|
|
111
|
+
* `async function` *is* `task.spawn` — the language already has it.
|
|
112
|
+
* - **No wall clock.** This module reads no system timer and creates no
|
|
113
|
+
* host timeout; every time value comes from the runtime's own accumulator.
|
|
114
|
+
*/
|
|
115
|
+
const NOOP_HANDLE = { cancel() { } };
|
|
116
|
+
/** `fetch`-shaped abort rejection: a `DOMException` named `AbortError`. */
|
|
117
|
+
function abortError(message) {
|
|
118
|
+
return new DOMException(message, 'AbortError');
|
|
119
|
+
}
|
|
120
|
+
function assertSeconds(method, seconds) {
|
|
121
|
+
if (!Number.isFinite(seconds) || seconds < 0) {
|
|
122
|
+
throw new RangeError(`SimClock.${method}: seconds must be a finite, non-negative number, got ${seconds}`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Build the one sim clock a `Game` owns. See the module header for the
|
|
127
|
+
* contract; `runtime/game.ts` is the only production caller.
|
|
128
|
+
*/
|
|
129
|
+
export function createSimClock(options) {
|
|
130
|
+
const disposeObject = options.dispose;
|
|
131
|
+
let simT = 0;
|
|
132
|
+
let ticks = 0;
|
|
133
|
+
let seq = 0;
|
|
134
|
+
let disposed = false;
|
|
135
|
+
const pending = new Set();
|
|
136
|
+
/** Rejectors for in-flight `delay`s, so `dispose()` can settle them all. */
|
|
137
|
+
const pendingDelays = new Set();
|
|
138
|
+
function cancelTimer(timer) {
|
|
139
|
+
if (timer.cancelled)
|
|
140
|
+
return;
|
|
141
|
+
timer.cancelled = true;
|
|
142
|
+
pending.delete(timer);
|
|
143
|
+
timer.detachAbort?.();
|
|
144
|
+
timer.detachAbort = null;
|
|
145
|
+
}
|
|
146
|
+
function after(seconds, fn, opts) {
|
|
147
|
+
assertSeconds('after', seconds);
|
|
148
|
+
const signal = opts?.signal;
|
|
149
|
+
// Already-aborted, or a clock that is gone: a no-op, never a throw.
|
|
150
|
+
if (disposed || signal?.aborted)
|
|
151
|
+
return NOOP_HANDLE;
|
|
152
|
+
const timer = {
|
|
153
|
+
dueAt: simT + seconds,
|
|
154
|
+
seq: seq++,
|
|
155
|
+
fn,
|
|
156
|
+
cancelled: false,
|
|
157
|
+
detachAbort: null,
|
|
158
|
+
};
|
|
159
|
+
pending.add(timer);
|
|
160
|
+
if (signal) {
|
|
161
|
+
const onAbort = () => cancelTimer(timer);
|
|
162
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
163
|
+
timer.detachAbort = () => signal.removeEventListener('abort', onAbort);
|
|
164
|
+
}
|
|
165
|
+
return {
|
|
166
|
+
cancel() {
|
|
167
|
+
cancelTimer(timer);
|
|
168
|
+
},
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
return {
|
|
172
|
+
now: () => simT,
|
|
173
|
+
tickNow: () => ticks,
|
|
174
|
+
after,
|
|
175
|
+
delay(seconds, opts) {
|
|
176
|
+
assertSeconds('delay', seconds);
|
|
177
|
+
const signal = opts?.signal;
|
|
178
|
+
if (signal?.aborted) {
|
|
179
|
+
return Promise.reject(abortError('SimClock.delay: aborted before it was scheduled'));
|
|
180
|
+
}
|
|
181
|
+
if (disposed) {
|
|
182
|
+
return Promise.reject(abortError('SimClock.delay: the sim clock is disposed'));
|
|
183
|
+
}
|
|
184
|
+
return new Promise((resolve, reject) => {
|
|
185
|
+
let handle = null;
|
|
186
|
+
let detachAbort = null;
|
|
187
|
+
let settled = false;
|
|
188
|
+
const finish = () => {
|
|
189
|
+
settled = true;
|
|
190
|
+
pendingDelays.delete(rejectDelay);
|
|
191
|
+
detachAbort?.();
|
|
192
|
+
detachAbort = null;
|
|
193
|
+
};
|
|
194
|
+
function rejectDelay(message) {
|
|
195
|
+
if (settled)
|
|
196
|
+
return;
|
|
197
|
+
finish();
|
|
198
|
+
handle?.cancel();
|
|
199
|
+
reject(abortError(message));
|
|
200
|
+
}
|
|
201
|
+
handle = after(seconds, () => {
|
|
202
|
+
if (settled)
|
|
203
|
+
return;
|
|
204
|
+
finish();
|
|
205
|
+
resolve();
|
|
206
|
+
});
|
|
207
|
+
pendingDelays.add(rejectDelay);
|
|
208
|
+
if (signal) {
|
|
209
|
+
const onAbort = () => rejectDelay('SimClock.delay: aborted');
|
|
210
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
211
|
+
detachAbort = () => signal.removeEventListener('abort', onAbort);
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
},
|
|
215
|
+
disposeAfter(obj, seconds) {
|
|
216
|
+
assertSeconds('disposeAfter', seconds);
|
|
217
|
+
return after(seconds, () => disposeObject(obj));
|
|
218
|
+
},
|
|
219
|
+
flush(nextSimT) {
|
|
220
|
+
if (disposed)
|
|
221
|
+
return;
|
|
222
|
+
simT = nextSimT;
|
|
223
|
+
ticks++;
|
|
224
|
+
if (pending.size === 0)
|
|
225
|
+
return;
|
|
226
|
+
// Snapshot BEFORE running anything: a timer scheduled by one of these
|
|
227
|
+
// callbacks lands in `pending` but not in this batch, so it can never be
|
|
228
|
+
// due in the same flush (module header, firing rule 4).
|
|
229
|
+
const batch = [];
|
|
230
|
+
for (const timer of pending) {
|
|
231
|
+
if (!timer.cancelled && timer.dueAt <= simT)
|
|
232
|
+
batch.push(timer);
|
|
233
|
+
}
|
|
234
|
+
if (batch.length === 0)
|
|
235
|
+
return;
|
|
236
|
+
batch.sort((a, b) => a.dueAt - b.dueAt || a.seq - b.seq);
|
|
237
|
+
for (const timer of batch) {
|
|
238
|
+
// A previously-fired callback may have cancelled this one.
|
|
239
|
+
if (timer.cancelled)
|
|
240
|
+
continue;
|
|
241
|
+
cancelTimer(timer);
|
|
242
|
+
try {
|
|
243
|
+
timer.fn();
|
|
244
|
+
}
|
|
245
|
+
catch (err) {
|
|
246
|
+
// biome-ignore lint/suspicious/noConsole: loud degrade — one bad timer must not abort the frame's remaining timers (same isolation idiom as `runFrameImpl`'s per-world try/catch)
|
|
247
|
+
console.error('[sim-clock] timer callback threw:', err);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
},
|
|
251
|
+
dispose() {
|
|
252
|
+
if (disposed)
|
|
253
|
+
return;
|
|
254
|
+
disposed = true;
|
|
255
|
+
for (const timer of pending) {
|
|
256
|
+
timer.detachAbort?.();
|
|
257
|
+
timer.detachAbort = null;
|
|
258
|
+
timer.cancelled = true;
|
|
259
|
+
}
|
|
260
|
+
pending.clear();
|
|
261
|
+
const rejectors = [...pendingDelays];
|
|
262
|
+
pendingDelays.clear();
|
|
263
|
+
for (const rejectDelay of rejectors) {
|
|
264
|
+
rejectDelay('SimClock: disposed while a delay was pending');
|
|
265
|
+
}
|
|
266
|
+
},
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
// ---------------------------------------------------------------------------
|
|
270
|
+
// Game-scoped registry — mirrors `core/seeded-random.ts`'s
|
|
271
|
+
// `registerSeededRandom`/`getSeededRandom` WeakMap pattern exactly, keyed on a
|
|
272
|
+
// bare `object` (not `Game`) so `core/` never imports `runtime/`. `createGame`
|
|
273
|
+
// is the one real registrant, passing the `GameInternal` shell as the key.
|
|
274
|
+
// ---------------------------------------------------------------------------
|
|
275
|
+
const clockByOwner = new WeakMap();
|
|
276
|
+
/** Called once by `createGame`, right after the clock and the Game shell exist. */
|
|
277
|
+
export function registerSimClock(owner, clock) {
|
|
278
|
+
clockByOwner.set(owner, clock);
|
|
279
|
+
}
|
|
280
|
+
/** The game-scoped clock backing every world's `ctx.clock` — `null` for an
|
|
281
|
+
* owner built without one (a hand-built `Game`-shaped stand-in that never went
|
|
282
|
+
* through `createGame`).
|
|
283
|
+
*
|
|
284
|
+
* Returns the INTERNAL view because this registry is engine-only — but a
|
|
285
|
+
* caller that RESOLVES a clock here is not its owner and must not call
|
|
286
|
+
* `dispose()` on it (module header, ownership). Game code never reaches it at
|
|
287
|
+
* all: `GameContext.clock` is typed as the public {@link SimClock}, which has
|
|
288
|
+
* neither `flush` nor `dispose`, the same way `Game` hides `GameInternal`. */
|
|
289
|
+
export function getSimClock(owner) {
|
|
290
|
+
return clockByOwner.get(owner) ?? null;
|
|
291
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { type SystemDef, type SystemFn, type SystemOptions, type SystemPhaseName, type SystemRunObserver } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Ordered system execution by named phase.
|
|
4
|
+
*
|
|
5
|
+
* Systems are registered with a phase name. When `run(dt)` is called, all
|
|
6
|
+
* phases execute in `PHASE_ORDER` via `runPhase(phase, dt)`. Within a phase,
|
|
7
|
+
* `runPhase` executes two ordered buckets (T7.1 slice 2):
|
|
8
|
+
*
|
|
9
|
+
* 1. **engine** — everything `add()`/`register()`ed at or before the last
|
|
10
|
+
* `markEngineBoundary()` call, or EVERYTHING if no boundary has ever
|
|
11
|
+
* been marked.
|
|
12
|
+
* 2. **game** — everything `add()`/`register()`ed AFTER the last
|
|
13
|
+
* `markEngineBoundary()` call.
|
|
14
|
+
*
|
|
15
|
+
* Within each bucket, systems run in registration order.
|
|
16
|
+
*/
|
|
17
|
+
export declare function createSystemRunner(observer?: SystemRunObserver, scope?: string): {
|
|
18
|
+
/**
|
|
19
|
+
* Register a bare system function in a specific phase.
|
|
20
|
+
* Systems within the same phase run in the order they were added.
|
|
21
|
+
*/
|
|
22
|
+
add(phase: SystemPhaseName, fn: SystemFn, options?: SystemOptions): void;
|
|
23
|
+
/**
|
|
24
|
+
* Remove a bare system function from a phase. Also drops it from the
|
|
25
|
+
* engine-membership set (if present) — the fix for the length-based
|
|
26
|
+
* bookkeeping bug: removing this has no effect on how any OTHER entry
|
|
27
|
+
* (before or after it) is classified, since classification is now
|
|
28
|
+
* per-function membership, not position.
|
|
29
|
+
*/
|
|
30
|
+
remove(phase: SystemPhaseName, fn: SystemFn): void;
|
|
31
|
+
/**
|
|
32
|
+
* Register a lifecycle system. Adds its update to the correct phase.
|
|
33
|
+
* Call runInit() after all systems are registered to invoke init hooks.
|
|
34
|
+
*/
|
|
35
|
+
register(system: SystemDef): void;
|
|
36
|
+
/**
|
|
37
|
+
* Unregister a lifecycle system. Removes its update and calls dispose.
|
|
38
|
+
*/
|
|
39
|
+
unregister(system: SystemDef): void;
|
|
40
|
+
/**
|
|
41
|
+
* Call init() on all registered lifecycle systems, in phase order.
|
|
42
|
+
* Call once after scene load, before the first game loop tick.
|
|
43
|
+
*/
|
|
44
|
+
runInit(): void;
|
|
45
|
+
/**
|
|
46
|
+
* Call dispose() on all registered lifecycle systems and remove them.
|
|
47
|
+
*/
|
|
48
|
+
runDispose(): void;
|
|
49
|
+
/**
|
|
50
|
+
* Run ONE phase's two ordered buckets — engine, game
|
|
51
|
+
* (see the module doc comment). Each system call is isolated: a
|
|
52
|
+
* throwing system is loudly logged (never swallowed) but does not stop
|
|
53
|
+
* its siblings in the same bucket, a later bucket in this phase, or a
|
|
54
|
+
* later phase, from running this frame.
|
|
55
|
+
*
|
|
56
|
+
* Public — the Game root's frame executor (`runtime/game.ts`) calls this
|
|
57
|
+
* directly per (phase, world); `run(dt)` below is just a loop over it,
|
|
58
|
+
* preserved for any direct caller.
|
|
59
|
+
*/
|
|
60
|
+
runPhase(phase: SystemPhaseName, dt: number): void;
|
|
61
|
+
/**
|
|
62
|
+
* Run all phases in order, via `runPhase`.
|
|
63
|
+
*/
|
|
64
|
+
run(dt: number): void;
|
|
65
|
+
/**
|
|
66
|
+
* Mark everything registered so far (via `add()`/`register()`, in every
|
|
67
|
+
* phase) as "engine" — infrastructure that must survive a warm restart.
|
|
68
|
+
* Call once, right after mount finishes wiring engine-level systems and
|
|
69
|
+
* BEFORE any game (`setup()`/scene load) registers its own. `hotReload`
|
|
70
|
+
* calls `removeAllNonEngine()` on every restart, which only ever removes
|
|
71
|
+
* what was added after this mark.
|
|
72
|
+
*/
|
|
73
|
+
markEngineBoundary(): void;
|
|
74
|
+
/**
|
|
75
|
+
* Bulk-remove every system registered after the last `markEngineBoundary()`
|
|
76
|
+
* call — both bare `add()`ed functions and lifecycle `register()`ed systems
|
|
77
|
+
* (the latter get `dispose()`d, mirroring `unregister()`). A no-op if no
|
|
78
|
+
* boundary has been marked. Idempotent: calling it again with nothing new
|
|
79
|
+
* registered since is a safe no-op.
|
|
80
|
+
*
|
|
81
|
+
* This is what makes a warm restart (`hotReload`) safe to call N times
|
|
82
|
+
* without accumulating duplicate systems/listeners — each restart's
|
|
83
|
+
* outgoing game systems are fully removed before the new one registers its
|
|
84
|
+
* own, while the engine systems (input/physics/render/...) registered
|
|
85
|
+
* before the boundary are never touched.
|
|
86
|
+
*/
|
|
87
|
+
removeAllNonEngine(): void;
|
|
88
|
+
/**
|
|
89
|
+
* Total system-function count, optionally scoped to one phase.
|
|
90
|
+
* Test/introspection helper — used to assert a warm restart doesn't
|
|
91
|
+
* accumulate systems (see `removeAllNonEngine`).
|
|
92
|
+
*/
|
|
93
|
+
count(phase?: SystemPhaseName): number;
|
|
94
|
+
};
|
|
95
|
+
export type SystemRunner = ReturnType<typeof createSystemRunner>;
|
|
96
|
+
//# sourceMappingURL=system-runner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"system-runner.d.ts","sourceRoot":"","sources":["../../src/core/system-runner.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,SAAS,EACd,KAAK,QAAQ,EACb,KAAK,aAAa,EAClB,KAAK,eAAe,EACpB,KAAK,iBAAiB,EACvB,MAAM,SAAS,CAAC;AAEjB;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,CAAC,EAAE,iBAAiB,EAAE,KAAK,SAAU;IAyC5E;;;OAGG;eACQ,eAAe,MAAM,QAAQ,YAAY,aAAa;IASjE;;;;;;OAMG;kBACW,eAAe,MAAM,QAAQ;IAQ3C;;;OAGG;qBACc,SAAS;IAU1B;;OAEG;uBACgB,SAAS;IAa5B;;;OAGG;;IAUH;;OAEG;;IAaH;;;;;;;;;;OAUG;oBACa,eAAe,MAAM,MAAM;IA4B3C;;OAEG;YACK,MAAM;IAMd;;;;;;;OAOG;;IAUH;;;;;;;;;;;;OAYG;;IAsBH;;;;OAIG;kBACW,eAAe,GAAG,MAAM;EAWzC;AAED,MAAM,MAAM,YAAY,GAAG,UAAU,CAAC,OAAO,kBAAkB,CAAC,CAAC"}
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import { PHASE_ORDER, } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Ordered system execution by named phase.
|
|
4
|
+
*
|
|
5
|
+
* Systems are registered with a phase name. When `run(dt)` is called, all
|
|
6
|
+
* phases execute in `PHASE_ORDER` via `runPhase(phase, dt)`. Within a phase,
|
|
7
|
+
* `runPhase` executes two ordered buckets (T7.1 slice 2):
|
|
8
|
+
*
|
|
9
|
+
* 1. **engine** — everything `add()`/`register()`ed at or before the last
|
|
10
|
+
* `markEngineBoundary()` call, or EVERYTHING if no boundary has ever
|
|
11
|
+
* been marked.
|
|
12
|
+
* 2. **game** — everything `add()`/`register()`ed AFTER the last
|
|
13
|
+
* `markEngineBoundary()` call.
|
|
14
|
+
*
|
|
15
|
+
* Within each bucket, systems run in registration order.
|
|
16
|
+
*/
|
|
17
|
+
export function createSystemRunner(observer, scope = 'world') {
|
|
18
|
+
const systems = new Map();
|
|
19
|
+
const registered = [];
|
|
20
|
+
const labels = new Map();
|
|
21
|
+
let anonymousId = 0;
|
|
22
|
+
// Initialize all phases with empty arrays.
|
|
23
|
+
for (const phase of PHASE_ORDER) {
|
|
24
|
+
systems.set(phase, []);
|
|
25
|
+
}
|
|
26
|
+
// Membership bookkeeping for `markEngineBoundary()` (T7.2 review fix —
|
|
27
|
+
// replaces the old length/index-snapshot bookkeeping, which broke if a
|
|
28
|
+
// pre-boundary "engine" entry was ever removed: shrinking the list would
|
|
29
|
+
// shift a later "game" entry underneath the recorded length, misclassifying
|
|
30
|
+
// it as engine). `engineFns`/`engineRegistered` instead record WHICH
|
|
31
|
+
// specific function/system was present at the last `markEngineBoundary()`
|
|
32
|
+
// call — membership survives arbitrary removal of any other entry,
|
|
33
|
+
// regardless of position. `boundaryMarked` is false until the first mark
|
|
34
|
+
// (mirrors the old `boundary === null`: everything is "engine" pre-mark).
|
|
35
|
+
let boundaryMarked = false;
|
|
36
|
+
const engineFns = new Map();
|
|
37
|
+
for (const phase of PHASE_ORDER)
|
|
38
|
+
engineFns.set(phase, new Set());
|
|
39
|
+
const engineRegistered = new Set();
|
|
40
|
+
/** Run one system function, isolating a throw so it never wedges the frame. */
|
|
41
|
+
function runOne(fn, phase, dt) {
|
|
42
|
+
const label = labels.get(fn) ?? fn.name ?? `anonymous-${++anonymousId}`;
|
|
43
|
+
labels.set(fn, label);
|
|
44
|
+
observer?.beginSystem(scope, phase, label);
|
|
45
|
+
try {
|
|
46
|
+
fn(dt);
|
|
47
|
+
}
|
|
48
|
+
catch (err) {
|
|
49
|
+
const label = fn.name ? `"${fn.name}"` : '(anonymous)';
|
|
50
|
+
console.error(`[system-runner] system ${label} in phase "${phase}" threw:`, err);
|
|
51
|
+
}
|
|
52
|
+
finally {
|
|
53
|
+
observer?.endSystem(scope, phase, label);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return {
|
|
57
|
+
/**
|
|
58
|
+
* Register a bare system function in a specific phase.
|
|
59
|
+
* Systems within the same phase run in the order they were added.
|
|
60
|
+
*/
|
|
61
|
+
add(phase, fn, options) {
|
|
62
|
+
const list = systems.get(phase);
|
|
63
|
+
if (!list) {
|
|
64
|
+
throw new Error(`Unknown phase: ${phase}. Valid phases: ${PHASE_ORDER.join(', ')}`);
|
|
65
|
+
}
|
|
66
|
+
list.push(fn);
|
|
67
|
+
labels.set(fn, options?.name ?? fn.name ?? `anonymous-${++anonymousId}`);
|
|
68
|
+
},
|
|
69
|
+
/**
|
|
70
|
+
* Remove a bare system function from a phase. Also drops it from the
|
|
71
|
+
* engine-membership set (if present) — the fix for the length-based
|
|
72
|
+
* bookkeeping bug: removing this has no effect on how any OTHER entry
|
|
73
|
+
* (before or after it) is classified, since classification is now
|
|
74
|
+
* per-function membership, not position.
|
|
75
|
+
*/
|
|
76
|
+
remove(phase, fn) {
|
|
77
|
+
const list = systems.get(phase);
|
|
78
|
+
if (!list)
|
|
79
|
+
return;
|
|
80
|
+
const idx = list.indexOf(fn);
|
|
81
|
+
if (idx !== -1)
|
|
82
|
+
list.splice(idx, 1);
|
|
83
|
+
engineFns.get(phase)?.delete(fn);
|
|
84
|
+
},
|
|
85
|
+
/**
|
|
86
|
+
* Register a lifecycle system. Adds its update to the correct phase.
|
|
87
|
+
* Call runInit() after all systems are registered to invoke init hooks.
|
|
88
|
+
*/
|
|
89
|
+
register(system) {
|
|
90
|
+
const list = systems.get(system.phase);
|
|
91
|
+
if (!list) {
|
|
92
|
+
throw new Error(`Unknown phase: ${system.phase}. Valid phases: ${PHASE_ORDER.join(', ')}`);
|
|
93
|
+
}
|
|
94
|
+
list.push(system.update);
|
|
95
|
+
labels.set(system.update, system.name ?? system.update.name ?? `anonymous-${++anonymousId}`);
|
|
96
|
+
registered.push(system);
|
|
97
|
+
},
|
|
98
|
+
/**
|
|
99
|
+
* Unregister a lifecycle system. Removes its update and calls dispose.
|
|
100
|
+
*/
|
|
101
|
+
unregister(system) {
|
|
102
|
+
const list = systems.get(system.phase);
|
|
103
|
+
if (list) {
|
|
104
|
+
const idx = list.indexOf(system.update);
|
|
105
|
+
if (idx !== -1)
|
|
106
|
+
list.splice(idx, 1);
|
|
107
|
+
}
|
|
108
|
+
engineFns.get(system.phase)?.delete(system.update);
|
|
109
|
+
const regIdx = registered.indexOf(system);
|
|
110
|
+
if (regIdx !== -1)
|
|
111
|
+
registered.splice(regIdx, 1);
|
|
112
|
+
engineRegistered.delete(system);
|
|
113
|
+
system.dispose?.();
|
|
114
|
+
},
|
|
115
|
+
/**
|
|
116
|
+
* Call init() on all registered lifecycle systems, in phase order.
|
|
117
|
+
* Call once after scene load, before the first game loop tick.
|
|
118
|
+
*/
|
|
119
|
+
runInit() {
|
|
120
|
+
const sorted = [...registered].sort((a, b) => PHASE_ORDER.indexOf(a.phase) - PHASE_ORDER.indexOf(b.phase));
|
|
121
|
+
for (const sys of sorted) {
|
|
122
|
+
sys.init?.();
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
/**
|
|
126
|
+
* Call dispose() on all registered lifecycle systems and remove them.
|
|
127
|
+
*/
|
|
128
|
+
runDispose() {
|
|
129
|
+
for (const sys of registered) {
|
|
130
|
+
const list = systems.get(sys.phase);
|
|
131
|
+
if (list) {
|
|
132
|
+
const idx = list.indexOf(sys.update);
|
|
133
|
+
if (idx !== -1)
|
|
134
|
+
list.splice(idx, 1);
|
|
135
|
+
}
|
|
136
|
+
sys.dispose?.();
|
|
137
|
+
}
|
|
138
|
+
registered.length = 0;
|
|
139
|
+
},
|
|
140
|
+
/**
|
|
141
|
+
* Run ONE phase's two ordered buckets — engine, game
|
|
142
|
+
* (see the module doc comment). Each system call is isolated: a
|
|
143
|
+
* throwing system is loudly logged (never swallowed) but does not stop
|
|
144
|
+
* its siblings in the same bucket, a later bucket in this phase, or a
|
|
145
|
+
* later phase, from running this frame.
|
|
146
|
+
*
|
|
147
|
+
* Public — the Game root's frame executor (`runtime/game.ts`) calls this
|
|
148
|
+
* directly per (phase, world); `run(dt)` below is just a loop over it,
|
|
149
|
+
* preserved for any direct caller.
|
|
150
|
+
*/
|
|
151
|
+
runPhase(phase, dt) {
|
|
152
|
+
const list = systems.get(phase);
|
|
153
|
+
if (!list) {
|
|
154
|
+
throw new Error(`Unknown phase: ${phase}. Valid phases: ${PHASE_ORDER.join(', ')}`);
|
|
155
|
+
}
|
|
156
|
+
// Partition by MEMBERSHIP, not position: everything in this phase's
|
|
157
|
+
// `engineFns` set (or, if no boundary has ever been marked, everything)
|
|
158
|
+
// runs immediately, in list order, as it's
|
|
159
|
+
// encountered; everything else is queued into `gameFns` (also in list
|
|
160
|
+
// order) and run after them. Because classification is
|
|
161
|
+
// per-function rather than "index < some remembered length", removing
|
|
162
|
+
// ANY entry via `remove()` — including a pre-boundary "engine" one —
|
|
163
|
+
// cannot shift a later game system into the engine bucket.
|
|
164
|
+
const engineSet = engineFns.get(phase);
|
|
165
|
+
const gameFns = [];
|
|
166
|
+
for (const fn of list) {
|
|
167
|
+
if (boundaryMarked && !engineSet.has(fn)) {
|
|
168
|
+
gameFns.push(fn);
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
runOne(fn, phase, dt);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
for (const fn of gameFns) {
|
|
175
|
+
runOne(fn, phase, dt);
|
|
176
|
+
}
|
|
177
|
+
},
|
|
178
|
+
/**
|
|
179
|
+
* Run all phases in order, via `runPhase`.
|
|
180
|
+
*/
|
|
181
|
+
run(dt) {
|
|
182
|
+
for (const phase of PHASE_ORDER) {
|
|
183
|
+
this.runPhase(phase, dt);
|
|
184
|
+
}
|
|
185
|
+
},
|
|
186
|
+
/**
|
|
187
|
+
* Mark everything registered so far (via `add()`/`register()`, in every
|
|
188
|
+
* phase) as "engine" — infrastructure that must survive a warm restart.
|
|
189
|
+
* Call once, right after mount finishes wiring engine-level systems and
|
|
190
|
+
* BEFORE any game (`setup()`/scene load) registers its own. `hotReload`
|
|
191
|
+
* calls `removeAllNonEngine()` on every restart, which only ever removes
|
|
192
|
+
* what was added after this mark.
|
|
193
|
+
*/
|
|
194
|
+
markEngineBoundary() {
|
|
195
|
+
for (const phase of PHASE_ORDER) {
|
|
196
|
+
const set = engineFns.get(phase);
|
|
197
|
+
for (const fn of systems.get(phase))
|
|
198
|
+
set.add(fn);
|
|
199
|
+
}
|
|
200
|
+
for (const sys of registered)
|
|
201
|
+
engineRegistered.add(sys);
|
|
202
|
+
boundaryMarked = true;
|
|
203
|
+
},
|
|
204
|
+
/**
|
|
205
|
+
* Bulk-remove every system registered after the last `markEngineBoundary()`
|
|
206
|
+
* call — both bare `add()`ed functions and lifecycle `register()`ed systems
|
|
207
|
+
* (the latter get `dispose()`d, mirroring `unregister()`). A no-op if no
|
|
208
|
+
* boundary has been marked. Idempotent: calling it again with nothing new
|
|
209
|
+
* registered since is a safe no-op.
|
|
210
|
+
*
|
|
211
|
+
* This is what makes a warm restart (`hotReload`) safe to call N times
|
|
212
|
+
* without accumulating duplicate systems/listeners — each restart's
|
|
213
|
+
* outgoing game systems are fully removed before the new one registers its
|
|
214
|
+
* own, while the engine systems (input/physics/render/...) registered
|
|
215
|
+
* before the boundary are never touched.
|
|
216
|
+
*/
|
|
217
|
+
removeAllNonEngine() {
|
|
218
|
+
if (!boundaryMarked)
|
|
219
|
+
return;
|
|
220
|
+
// Membership-based, same reasoning as runPhase: partition by whether
|
|
221
|
+
// each entry is in the engine set, not by a remembered length/index.
|
|
222
|
+
const keptRegistered = registered.filter((sys) => engineRegistered.has(sys));
|
|
223
|
+
const removedRegistered = registered.filter((sys) => !engineRegistered.has(sys));
|
|
224
|
+
registered.length = 0;
|
|
225
|
+
registered.push(...keptRegistered);
|
|
226
|
+
for (const sys of removedRegistered) {
|
|
227
|
+
sys.dispose?.();
|
|
228
|
+
}
|
|
229
|
+
for (const phase of PHASE_ORDER) {
|
|
230
|
+
const list = systems.get(phase);
|
|
231
|
+
const engineSet = engineFns.get(phase);
|
|
232
|
+
const kept = list.filter((fn) => engineSet.has(fn));
|
|
233
|
+
list.length = 0;
|
|
234
|
+
list.push(...kept);
|
|
235
|
+
}
|
|
236
|
+
},
|
|
237
|
+
/**
|
|
238
|
+
* Total system-function count, optionally scoped to one phase.
|
|
239
|
+
* Test/introspection helper — used to assert a warm restart doesn't
|
|
240
|
+
* accumulate systems (see `removeAllNonEngine`).
|
|
241
|
+
*/
|
|
242
|
+
count(phase) {
|
|
243
|
+
if (phase) {
|
|
244
|
+
return systems.get(phase)?.length ?? 0;
|
|
245
|
+
}
|
|
246
|
+
let total = 0;
|
|
247
|
+
for (const list of systems.values()) {
|
|
248
|
+
total += list.length;
|
|
249
|
+
}
|
|
250
|
+
return total;
|
|
251
|
+
},
|
|
252
|
+
};
|
|
253
|
+
}
|