@vgai/engine 0.5.15 → 0.5.16
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-module.d.ts +228 -17
- package/dist/adapter/adapter-module.d.ts.map +1 -1
- package/dist/adapter/adapter-module.js +224 -42
- package/dist/adapter/authoring-seam-contract.d.ts +530 -0
- package/dist/adapter/authoring-seam-contract.d.ts.map +1 -0
- package/dist/adapter/authoring-seam-contract.js +162 -0
- package/dist/adapter/authoring.d.ts +237 -50
- package/dist/adapter/authoring.d.ts.map +1 -1
- package/dist/adapter/authoring.js +2 -1
- package/dist/adapter/entrypoint-selection-source.d.ts +35 -0
- package/dist/adapter/entrypoint-selection-source.d.ts.map +1 -0
- package/dist/adapter/entrypoint-selection-source.js +151 -0
- package/dist/adapter/finders/scenes-from-entrypoint-selection.d.ts +7 -1
- package/dist/adapter/finders/scenes-from-entrypoint-selection.d.ts.map +1 -1
- package/dist/adapter/finders/scenes-from-entrypoint-selection.js +142 -21
- package/dist/adapter/first-party-systems.d.ts +13 -3
- package/dist/adapter/first-party-systems.d.ts.map +1 -1
- package/dist/adapter/first-party-systems.js +10 -3
- package/dist/adapter/index.d.ts +7 -2
- package/dist/adapter/index.d.ts.map +1 -1
- package/dist/adapter/index.js +4 -0
- package/dist/adapter/ingest/contract-system-adapters.d.ts +1 -1
- package/dist/adapter/ingest/contract-system-adapters.d.ts.map +1 -1
- package/dist/adapter/ingest/contract-system-adapters.js +13 -25
- package/dist/adapter/ingest/game-contract-seams.d.ts +214 -0
- package/dist/adapter/ingest/game-contract-seams.d.ts.map +1 -0
- package/dist/adapter/ingest/game-contract-seams.js +62 -0
- package/dist/adapter/ingest/game-contract.d.ts +5 -3
- package/dist/adapter/ingest/game-contract.d.ts.map +1 -1
- package/dist/adapter/ingest/game-contract.js +4 -1
- package/dist/adapter/ingest/merge-debug-adapters.d.ts +52 -0
- package/dist/adapter/ingest/merge-debug-adapters.d.ts.map +1 -0
- package/dist/adapter/ingest/merge-debug-adapters.js +141 -0
- package/dist/adapter/ingest/observation-debug-adapter.d.ts +56 -0
- package/dist/adapter/ingest/observation-debug-adapter.d.ts.map +1 -0
- package/dist/adapter/ingest/observation-debug-adapter.js +131 -0
- package/dist/adapter/ingest/scene-capture.d.ts +38 -13
- package/dist/adapter/ingest/scene-capture.d.ts.map +1 -1
- package/dist/adapter/ingest/scene-capture.js +101 -24
- package/dist/adapter/ingest/visible-capture-window.d.ts +22 -5
- package/dist/adapter/ingest/visible-capture-window.d.ts.map +1 -1
- package/dist/adapter/ingest/visible-capture-window.js +46 -7
- package/dist/adapter/native-debug-module.d.ts +43 -0
- package/dist/adapter/native-debug-module.d.ts.map +1 -0
- package/dist/adapter/native-debug-module.js +230 -0
- package/dist/adapter/object3d-authoring-subject.d.ts +32 -0
- package/dist/adapter/object3d-authoring-subject.d.ts.map +1 -0
- package/dist/adapter/object3d-authoring-subject.js +25 -0
- package/dist/adapter/root-adapter.d.ts +21 -56
- package/dist/adapter/root-adapter.d.ts.map +1 -1
- package/dist/adapter/root-adapter.js +4 -20
- package/dist/adapter/root-seam-contract.d.ts +124 -0
- package/dist/adapter/root-seam-contract.d.ts.map +1 -0
- package/dist/adapter/root-seam-contract.js +36 -0
- package/dist/adapter/seam-evidence.d.ts +118 -0
- package/dist/adapter/seam-evidence.d.ts.map +1 -0
- package/dist/adapter/seam-evidence.js +231 -0
- package/dist/adapter/setup-three-root-adapter.d.ts +2 -3
- package/dist/adapter/setup-three-root-adapter.d.ts.map +1 -1
- package/dist/adapter/setup-three-root-adapter.js +2 -3
- package/dist/adapter/system-adapter.d.ts +6 -1
- package/dist/adapter/system-adapter.d.ts.map +1 -1
- package/dist/adapter/system-seam-contract.d.ts +354 -0
- package/dist/adapter/system-seam-contract.d.ts.map +1 -0
- package/dist/adapter/system-seam-contract.js +95 -0
- package/dist/animation/xstate-animation-binding.d.ts +6 -0
- package/dist/animation/xstate-animation-binding.d.ts.map +1 -1
- package/dist/animation/xstate-animation-binding.js +142 -4
- package/dist/asset-formats/collider.d.ts +1 -1
- package/dist/asset-formats/mesh.d.ts +2 -2
- package/dist/asset-formats/particles.d.ts +1 -1
- package/dist/canvas-react/engine-bridge.d.ts +6 -6
- package/dist/canvas-react/engine-bridge.d.ts.map +1 -1
- package/dist/canvas-react/engine-bridge.js +2 -2
- package/dist/canvas-react/index.d.ts +2 -2
- package/dist/canvas-react/index.d.ts.map +1 -1
- package/dist/canvas-react/index.js +1 -1
- package/dist/canvas-react/pixi-react-adapter.d.ts +15 -9
- package/dist/canvas-react/pixi-react-adapter.d.ts.map +1 -1
- package/dist/canvas-react/pixi-react-adapter.js +43 -29
- package/dist/canvas-react/pixi-react-root-factory.d.ts.map +1 -1
- package/dist/canvas-react/pixi-react-root-factory.js +4 -0
- package/dist/canvas-react/world-context.d.ts +22 -12
- package/dist/canvas-react/world-context.d.ts.map +1 -1
- package/dist/canvas-react/world-context.js +12 -51
- package/dist/ecs/user-data.d.ts +5 -0
- package/dist/ecs/user-data.d.ts.map +1 -1
- package/dist/ecs/user-data.js +3 -0
- package/dist/input/input-manager.d.ts +49 -0
- package/dist/input/input-manager.d.ts.map +1 -1
- package/dist/input/input-manager.js +127 -0
- package/dist/manifest/schema.d.ts +9 -9
- package/dist/pixi/authoring.d.ts.map +1 -1
- package/dist/pixi/authoring.js +220 -1
- package/dist/pixi/ingest.d.ts +2 -1
- package/dist/pixi/ingest.d.ts.map +1 -1
- package/dist/pixi/ingest.js +4 -2
- package/dist/pixi/scene-capture.d.ts +9 -1
- package/dist/pixi/scene-capture.d.ts.map +1 -1
- package/dist/pixi/scene-capture.js +9 -6
- package/dist/render/particles-factory.d.ts +13 -6
- package/dist/render/particles-factory.d.ts.map +1 -1
- package/dist/render/particles-factory.js +6 -3
- package/dist/runtime/create-runtime.d.ts +23 -46
- package/dist/runtime/create-runtime.d.ts.map +1 -1
- package/dist/runtime/create-runtime.js +22 -67
- package/dist/runtime/game.d.ts +29 -71
- package/dist/runtime/game.d.ts.map +1 -1
- package/dist/runtime/game.js +15 -54
- package/dist/runtime/mount-game.d.ts.map +1 -1
- package/dist/runtime/mount-game.js +12 -1
- package/dist/runtime/mount-manifest.d.ts.map +1 -1
- package/dist/runtime/mount-manifest.js +4 -5
- package/dist/runtime/types.d.ts +18 -0
- package/dist/runtime/types.d.ts.map +1 -1
- package/dist/setup/setup-renderer.d.ts +3 -4
- package/dist/setup/setup-renderer.d.ts.map +1 -1
- package/dist/setup/setup-renderer.js +3 -4
- package/dist/world3d-react/engine-bridge.d.ts +1 -1
- package/dist/world3d-react/engine-bridge.js +1 -1
- package/dist/world3d-react/r3f-adapter.d.ts +7 -0
- package/dist/world3d-react/r3f-adapter.d.ts.map +1 -1
- package/dist/world3d-react/r3f-adapter.js +50 -21
- package/dist/world3d-react/r3f-root-factory.d.ts.map +1 -1
- package/dist/world3d-react/r3f-root-factory.js +4 -0
- package/dist/world3d-react/world-context.d.ts +9 -3
- package/dist/world3d-react/world-context.d.ts.map +1 -1
- package/dist/world3d-react/world-context.js +5 -18
- package/package.json +1 -1
- package/schemas/engine-capabilities.json +7 -7
- package/src/adapter/adapter-module.ts +407 -50
- package/src/adapter/authoring-seam-contract.ts +211 -0
- package/src/adapter/authoring.ts +242 -50
- package/src/adapter/entrypoint-selection-source.ts +159 -0
- package/src/adapter/finders/scenes-from-entrypoint-selection.ts +159 -28
- package/src/adapter/first-party-systems.ts +17 -3
- package/src/adapter/index.ts +51 -1
- package/src/adapter/ingest/contract-system-adapters.ts +26 -30
- package/src/adapter/ingest/game-contract-seams.ts +85 -0
- package/src/adapter/ingest/game-contract.ts +9 -4
- package/src/adapter/ingest/merge-debug-adapters.ts +193 -0
- package/src/adapter/ingest/observation-debug-adapter.ts +158 -0
- package/src/adapter/ingest/scene-capture.ts +126 -23
- package/src/adapter/ingest/visible-capture-window.ts +63 -9
- package/src/adapter/native-debug-module.ts +307 -0
- package/src/adapter/object3d-authoring-subject.ts +50 -0
- package/src/adapter/root-adapter.ts +21 -56
- package/src/adapter/root-seam-contract.ts +56 -0
- package/src/adapter/seam-evidence.ts +336 -0
- package/src/adapter/setup-three-root-adapter.ts +4 -6
- package/src/adapter/system-adapter.ts +6 -1
- package/src/adapter/system-seam-contract.ts +121 -0
- package/src/animation/xstate-animation-binding.ts +151 -4
- package/src/canvas-react/engine-bridge.ts +6 -6
- package/src/canvas-react/index.ts +2 -1
- package/src/canvas-react/pixi-react-adapter.tsx +55 -34
- package/src/canvas-react/pixi-react-root-factory.tsx +4 -0
- package/src/canvas-react/world-context.ts +26 -101
- package/src/ecs/user-data.ts +5 -0
- package/src/input/input-manager.ts +122 -0
- package/src/pixi/authoring.ts +236 -1
- package/src/pixi/ingest.ts +9 -2
- package/src/pixi/scene-capture.ts +20 -5
- package/src/render/particles-factory.ts +16 -6
- package/src/runtime/create-runtime.ts +41 -106
- package/src/runtime/game.ts +41 -111
- package/src/runtime/mount-game.ts +19 -1
- package/src/runtime/mount-manifest.ts +4 -5
- package/src/runtime/types.ts +22 -0
- package/src/setup/setup-renderer.ts +3 -4
- package/src/world3d-react/engine-bridge.ts +1 -1
- package/src/world3d-react/r3f-adapter.tsx +64 -24
- package/src/world3d-react/r3f-root-factory.tsx +4 -0
- package/src/world3d-react/world-context.ts +11 -36
|
@@ -23,12 +23,14 @@ import {
|
|
|
23
23
|
advance,
|
|
24
24
|
createRoot,
|
|
25
25
|
extend,
|
|
26
|
+
flushSync,
|
|
26
27
|
events as pointerEvents,
|
|
27
28
|
type RootState,
|
|
28
29
|
useFrame,
|
|
29
30
|
} from '@react-three/fiber';
|
|
30
31
|
import { createElement, Fragment, type ReactNode, useEffect, useMemo } from 'react';
|
|
31
32
|
import type { MountedThreeRoot, RootAdapter, ThreeHostContext } from '../adapter';
|
|
33
|
+
import type { SystemAdapters } from '../adapter/system-adapter';
|
|
32
34
|
import { type RenderVitalsRegistration, registerRenderVitals } from '../dev/register-render-vitals';
|
|
33
35
|
import {
|
|
34
36
|
createRenderDebugAdapter,
|
|
@@ -40,6 +42,7 @@ import { RENDER_SUBMIT_PHASE } from '../dev/render-vitals';
|
|
|
40
42
|
import { createWebGLFrameCapture } from '../dev/webgl-frame-capture';
|
|
41
43
|
import { WorldProvider } from '../react/world-state';
|
|
42
44
|
import { createSoftParticleDepthPass } from '../render/soft-particle-depth';
|
|
45
|
+
import { getDebugRegistry } from '../runtime/debug-registry';
|
|
43
46
|
import { devLayersEnabled } from '../runtime/dev-layers';
|
|
44
47
|
import { EngineBridge, type EngineBridgeValue } from './engine-bridge';
|
|
45
48
|
import { applyWorldRendererConfig, type WorldRendererConfig } from './renderer-config';
|
|
@@ -99,6 +102,13 @@ export interface CreateR3FAdapterOptions {
|
|
|
99
102
|
* actions). A missing/invalid map degrades loudly without failing the
|
|
100
103
|
* mount — see `world-context.ts`. */
|
|
101
104
|
readonly inputMapPath?: string | null | undefined;
|
|
105
|
+
/**
|
|
106
|
+
* Transitional compatibility for explicit pre-native adapters. The
|
|
107
|
+
* default-export root factory always passes `false`: ordinary R3F trees get
|
|
108
|
+
* no vgai context/provider or private scheduler. Remove this switch with
|
|
109
|
+
* the last explicit legacy adapter migration.
|
|
110
|
+
*/
|
|
111
|
+
readonly engineRuntime?: boolean | undefined;
|
|
102
112
|
/** The colour pipeline this world was AUTHORED for, applied to the host's renderer for the life
|
|
103
113
|
* of the mount and restored on dispose. Omit it (every world here does) to keep the host's own
|
|
104
114
|
* defaults; declare it when the world's colours were picked against a different engine's
|
|
@@ -113,7 +123,7 @@ export interface CreateR3FAdapterOptions {
|
|
|
113
123
|
* second `requestAnimationFrame` loop (design §1.C).
|
|
114
124
|
*/
|
|
115
125
|
export function createR3FAdapter(options: CreateR3FAdapterOptions): RootAdapter {
|
|
116
|
-
const { id, content, inputMapPath, renderer: rendererConfig } = options;
|
|
126
|
+
const { id, content, inputMapPath, engineRuntime = true, renderer: rendererConfig } = options;
|
|
117
127
|
|
|
118
128
|
return {
|
|
119
129
|
id,
|
|
@@ -171,15 +181,14 @@ export function createR3FAdapter(options: CreateR3FAdapterOptions): RootAdapter
|
|
|
171
181
|
// for one is a compile error rather than anything that can happen at
|
|
172
182
|
// runtime. `scene`/`camera` are backfilled right after fiber's first
|
|
173
183
|
// commit resolves them, before any tick can run.
|
|
174
|
-
const runtime = createR3FRootContext(host, { id, inputMapPath });
|
|
175
|
-
const { systems } = runtime;
|
|
184
|
+
const runtime = engineRuntime ? createR3FRootContext(host, { id, inputMapPath }) : null;
|
|
176
185
|
// `ctx` rides the bridge so ordinary components can reach engine
|
|
177
186
|
// services through `useGameContext()`.
|
|
178
|
-
const bridge: EngineBridgeValue = { ctx: runtime.ctx };
|
|
187
|
+
const bridge: EngineBridgeValue | null = runtime ? { ctx: runtime.ctx } : null;
|
|
179
188
|
// Actions must exist before any component's init()/first frame reads
|
|
180
189
|
// them — wait for the (never-rejecting) input-map load before the first
|
|
181
190
|
// commit.
|
|
182
|
-
await runtime
|
|
191
|
+
await runtime?.inputMapReady;
|
|
183
192
|
|
|
184
193
|
// The world's own colour pipeline. Applied AFTER `root.configure()` below — see the comment
|
|
185
194
|
// at its call site for why "before" is the version that silently does nothing.
|
|
@@ -199,6 +208,19 @@ export function createR3FAdapter(options: CreateR3FAdapterOptions): RootAdapter
|
|
|
199
208
|
const statePromise = new Promise<RootState>((resolve) => {
|
|
200
209
|
resolveState = resolve;
|
|
201
210
|
});
|
|
211
|
+
let resolveEffectsReady!: () => void;
|
|
212
|
+
const effectsReady = new Promise<void>((resolve) => {
|
|
213
|
+
resolveEffectsReady = resolve;
|
|
214
|
+
});
|
|
215
|
+
// Rendered after the game's content. React runs passive effects in tree
|
|
216
|
+
// order, so when this fires every useDebugProvider/useDebugCommand in
|
|
217
|
+
// the initial tree has registered. A Play acknowledgement is a claim
|
|
218
|
+
// that the mounted game's command surface exists, not merely that Fiber
|
|
219
|
+
// allocated a scene.
|
|
220
|
+
function MountEffectsReady(): null {
|
|
221
|
+
useEffect(resolveEffectsReady, []);
|
|
222
|
+
return null;
|
|
223
|
+
}
|
|
202
224
|
|
|
203
225
|
// `root.configure()` is ASYNC in fiber v9 (`Promise<ReconcilerRoot>`)
|
|
204
226
|
// — the design doc's §1.C sketch shows it called synchronously; the
|
|
@@ -298,21 +320,24 @@ export function createR3FAdapter(options: CreateR3FAdapterOptions): RootAdapter
|
|
|
298
320
|
// until this landed. `host.game` is genuinely absent in bare/headless
|
|
299
321
|
// hosts, so the provider is conditional and those hooks stay the inert
|
|
300
322
|
// no-ops they already document themselves to be.
|
|
301
|
-
const game = runtime
|
|
323
|
+
const game = runtime?.ctx.game;
|
|
302
324
|
// `SoftParticleDepth` trails the world's own tree deliberately — see its
|
|
303
325
|
// docblock for why the subscription must land last.
|
|
304
326
|
const world = createElement(
|
|
305
327
|
Fragment,
|
|
306
328
|
null,
|
|
307
329
|
content,
|
|
308
|
-
createElement(SoftParticleDepth, { key: 'vgai-soft-particle-depth' }),
|
|
330
|
+
runtime ? createElement(SoftParticleDepth, { key: 'vgai-soft-particle-depth' }) : null,
|
|
331
|
+
createElement(MountEffectsReady, { key: 'vgai-mount-effects-ready' }),
|
|
309
332
|
);
|
|
310
333
|
root.render(
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
334
|
+
bridge
|
|
335
|
+
? createElement(
|
|
336
|
+
EngineBridge.Provider,
|
|
337
|
+
{ value: bridge },
|
|
338
|
+
game ? createElement(WorldProvider, { game }, world) : world,
|
|
339
|
+
)
|
|
340
|
+
: world,
|
|
316
341
|
);
|
|
317
342
|
// A reconcile-time crash (e.g. a missing `extend` catalogue entry)
|
|
318
343
|
// surfaces as an uncaught window error and `onCreated` never fires —
|
|
@@ -323,7 +348,7 @@ export function createR3FAdapter(options: CreateR3FAdapterOptions): RootAdapter
|
|
|
323
348
|
const onError = (event: ErrorEvent) => {
|
|
324
349
|
cleanup();
|
|
325
350
|
root.unmount();
|
|
326
|
-
runtime
|
|
351
|
+
runtime?.dispose();
|
|
327
352
|
restoreRendererConfig();
|
|
328
353
|
reject(
|
|
329
354
|
new Error(
|
|
@@ -335,7 +360,7 @@ export function createR3FAdapter(options: CreateR3FAdapterOptions): RootAdapter
|
|
|
335
360
|
const timer = setTimeout(() => {
|
|
336
361
|
cleanup();
|
|
337
362
|
root.unmount();
|
|
338
|
-
runtime
|
|
363
|
+
runtime?.dispose();
|
|
339
364
|
restoreRendererConfig();
|
|
340
365
|
reject(
|
|
341
366
|
new Error(
|
|
@@ -349,7 +374,7 @@ export function createR3FAdapter(options: CreateR3FAdapterOptions): RootAdapter
|
|
|
349
374
|
window.removeEventListener('error', onError);
|
|
350
375
|
};
|
|
351
376
|
window.addEventListener('error', onError);
|
|
352
|
-
void statePromise.then((s) => {
|
|
377
|
+
void Promise.all([statePromise, effectsReady]).then(([s]) => {
|
|
353
378
|
cleanup();
|
|
354
379
|
resolve(s);
|
|
355
380
|
});
|
|
@@ -369,7 +394,7 @@ export function createR3FAdapter(options: CreateR3FAdapterOptions): RootAdapter
|
|
|
369
394
|
// Backfill the component runtime's ctx with fiber's real scene/camera
|
|
370
395
|
// (W3 — see the HONESTY NOTE above). This happens before the first
|
|
371
396
|
// `update(dt)` tick, so no component ever observes them missing.
|
|
372
|
-
runtime
|
|
397
|
+
runtime?.setSceneCamera(state.scene, state.camera);
|
|
373
398
|
|
|
374
399
|
// THE STORE IS THE TRUTH, NOT THE `onCreated` SNAPSHOT.
|
|
375
400
|
//
|
|
@@ -395,6 +420,7 @@ export function createR3FAdapter(options: CreateR3FAdapterOptions): RootAdapter
|
|
|
395
420
|
// `RootState` for precisely this reason, so reading it per frame is the
|
|
396
421
|
// supported way to see the live root. It is a plain map read.
|
|
397
422
|
const live = (): RootState => state.get();
|
|
423
|
+
const gameDebugRegistry = host.game ? getDebugRegistry(host.game) : null;
|
|
398
424
|
|
|
399
425
|
// --- Live render vitals (issue #1504), for an R3F world -----------------
|
|
400
426
|
// The SAME door the classic adapter seeds
|
|
@@ -434,8 +460,11 @@ export function createR3FAdapter(options: CreateR3FAdapterOptions): RootAdapter
|
|
|
434
460
|
// sets on play — under the dev gate this is the same cost arriving a
|
|
435
461
|
// little earlier, not a new one.
|
|
436
462
|
host.game.profiler.enabled = true;
|
|
463
|
+
if (!gameDebugRegistry) {
|
|
464
|
+
throw new Error('r3f-adapter: mounted Game has no debug registry.');
|
|
465
|
+
}
|
|
437
466
|
renderVitals = registerRenderVitals({
|
|
438
|
-
registry:
|
|
467
|
+
registry: gameDebugRegistry,
|
|
439
468
|
worldId: id,
|
|
440
469
|
profiler: host.game.profiler,
|
|
441
470
|
scene: live().scene,
|
|
@@ -461,7 +490,12 @@ export function createR3FAdapter(options: CreateR3FAdapterOptions): RootAdapter
|
|
|
461
490
|
memory: () => collectRenderMemory(live().scene, host.renderer.info),
|
|
462
491
|
})
|
|
463
492
|
: null;
|
|
464
|
-
|
|
493
|
+
// The session debug registry is host instrumentation, not component
|
|
494
|
+
// context. Publishing the existing adapter here lets coverage and
|
|
495
|
+
// editor panels see the same door without wrapping the R3F tree.
|
|
496
|
+
const systemAdapters: SystemAdapters =
|
|
497
|
+
runtime?.systemAdapters ?? (gameDebugRegistry ? { debug: gameDebugRegistry.adapter } : {});
|
|
498
|
+
if (renderDebugWiring) systemAdapters.renderDebug = renderDebugWiring.adapter;
|
|
465
499
|
|
|
466
500
|
// The engine drives every `useFrame` through the mounted world's
|
|
467
501
|
// `update(dt)` hook, never off a raw host-loop callback. That is the
|
|
@@ -490,7 +524,7 @@ export function createR3FAdapter(options: CreateR3FAdapterOptions): RootAdapter
|
|
|
490
524
|
// resumed frame as one giant `useFrame` delta — the exact "time passed
|
|
491
525
|
// while frozen" illusion acceptance gate 2 forbids.
|
|
492
526
|
let elapsed = 0;
|
|
493
|
-
/** The
|
|
527
|
+
/** The legacy component ctx camera last backfilled by `update()`. */
|
|
494
528
|
let backfilledCamera = state.camera;
|
|
495
529
|
|
|
496
530
|
return {
|
|
@@ -524,7 +558,7 @@ export function createR3FAdapter(options: CreateR3FAdapterOptions): RootAdapter
|
|
|
524
558
|
// WebGL2 context exists, `renderDebug` are engine-seeded. Game code
|
|
525
559
|
// adds its own capabilities via `ctx.registerSystemAdapter` exactly
|
|
526
560
|
// as in a classic world.
|
|
527
|
-
systems:
|
|
561
|
+
systems: systemAdapters,
|
|
528
562
|
update(dt: number): void {
|
|
529
563
|
elapsed += dt;
|
|
530
564
|
const current = live();
|
|
@@ -533,14 +567,14 @@ export function createR3FAdapter(options: CreateR3FAdapterOptions): RootAdapter
|
|
|
533
567
|
// component reading one while the frame uses the other is the
|
|
534
568
|
// silent kind of wrong. Identity-gated, so this is one comparison
|
|
535
569
|
// per frame in the overwhelmingly common no-swap case.
|
|
536
|
-
if (current.camera !== backfilledCamera) {
|
|
570
|
+
if (runtime && current.camera !== backfilledCamera) {
|
|
537
571
|
backfilledCamera = current.camera;
|
|
538
572
|
runtime.setSceneCamera(current.scene, current.camera);
|
|
539
573
|
}
|
|
540
574
|
// Engine phases FIRST (registered systems mutate transforms),
|
|
541
575
|
// then fiber's advance (useFrame callbacks + the actual render see
|
|
542
576
|
// the fresh state). Both are host-gated: neither runs while frozen.
|
|
543
|
-
systems.run(dt);
|
|
577
|
+
runtime?.systems.run(dt);
|
|
544
578
|
renderDebugWiring?.beforeRender();
|
|
545
579
|
try {
|
|
546
580
|
const profiler = host.game?.profiler;
|
|
@@ -591,7 +625,13 @@ export function createR3FAdapter(options: CreateR3FAdapterOptions): RootAdapter
|
|
|
591
625
|
// Reject/restore a pending capture while its renderables still
|
|
592
626
|
// exist, then unmount component effects and strip runtime state.
|
|
593
627
|
renderDebugWiring?.dispose();
|
|
594
|
-
|
|
628
|
+
// The editor audits and disposes the mounted game's window/document realm immediately
|
|
629
|
+
// after `session.stop()`. Fiber's ordinary `unmount()` submits a concurrent reconciler
|
|
630
|
+
// update, so component effect cleanups could still be pending at that boundary and the
|
|
631
|
+
// audit would truthfully report/reclaim listeners the world was about to remove itself.
|
|
632
|
+
// Force the unmount COMMIT (not fiber's deliberately delayed renderer/context disposal)
|
|
633
|
+
// to finish here: a stopped root owns no live component lifecycle beyond this method.
|
|
634
|
+
flushSync(() => root.unmount());
|
|
595
635
|
// The vitals fold owns two `profiler.subscribe` registrations, and
|
|
596
636
|
// `runtime.dispose()`'s `strip(id)` removes the REGISTRATIONS only —
|
|
597
637
|
// this is the one path that ends the subscriptions, exactly as the
|
|
@@ -599,7 +639,7 @@ export function createR3FAdapter(options: CreateR3FAdapterOptions): RootAdapter
|
|
|
599
639
|
// subscription can never outlive the door it feeds.
|
|
600
640
|
renderVitals?.dispose();
|
|
601
641
|
renderVitals = null;
|
|
602
|
-
runtime
|
|
642
|
+
runtime?.dispose();
|
|
603
643
|
restoreRendererConfig();
|
|
604
644
|
},
|
|
605
645
|
};
|
|
@@ -69,6 +69,10 @@ export function resolveR3FEntryAdapter(entryModule: unknown, rootId: string): Ro
|
|
|
69
69
|
return createR3FAdapter({
|
|
70
70
|
id: rootId,
|
|
71
71
|
content: createElement(mod.default),
|
|
72
|
+
// A default-exported R3F component is an ordinary R3F app. The host
|
|
73
|
+
// controls Fiber's native `frameloop: 'never'` scheduler; vgai runtime
|
|
74
|
+
// context, systems and state providers never enter its React tree.
|
|
75
|
+
engineRuntime: false,
|
|
72
76
|
});
|
|
73
77
|
}
|
|
74
78
|
return null;
|
|
@@ -13,12 +13,15 @@
|
|
|
13
13
|
* `ctx.debug = registry.forRoot(id)`, so a component's
|
|
14
14
|
* effect can `ctx.debug.registerStateProvider(...)` and be visible
|
|
15
15
|
* to `vgai eval`/the editor's debug panels — previously a silent no-op.
|
|
16
|
-
* 2. **`ctx.input` is the game-owned `InputManager
|
|
16
|
+
* 2. **`ctx.input` is the game-owned `InputManager` when a Game hosts the
|
|
17
|
+
* root** (`host.game.input` —
|
|
17
18
|
* the same instance the classic adapter uses when hosted), and this
|
|
18
19
|
* module registers the SAME per-world debug-registry seams the classic
|
|
19
20
|
* adapter registers (`setVirtualInputTarget`/`setInputActionsSource`/
|
|
20
21
|
* `setInputTraceSource`), which is what makes `game.input.*` (the bot
|
|
21
22
|
* input doctrine) work instead of throwing `DEBUG_INPUT_UNAVAILABLE`.
|
|
23
|
+
* A bare/foreign mount receives `null`, visibly in the type, rather than
|
|
24
|
+
* a getter that throws later inside a frame callback.
|
|
22
25
|
* The project's input map is loaded through `Game.loadInputMap` (the
|
|
23
26
|
* game-owned, load-once path every root shares) with the SAME default
|
|
24
27
|
* path the classic adapter uses. A missing/unparseable map degrades
|
|
@@ -49,7 +52,7 @@ import {
|
|
|
49
52
|
getDebugRegistry,
|
|
50
53
|
} from '../runtime/debug-registry';
|
|
51
54
|
import { disposeDebrisSubtree } from '../runtime/game';
|
|
52
|
-
import type { GameContext } from '../runtime/types';
|
|
55
|
+
import type { ComponentContextBase, GameContext } from '../runtime/types';
|
|
53
56
|
|
|
54
57
|
/** The classic adapter's default input-map path — the conventional location
|
|
55
58
|
* every scaffolded project ships (`public/inputmaps/default.inputmap.json`). */
|
|
@@ -90,22 +93,11 @@ export const DEFAULT_INPUT_MAP_PATH = '/inputmaps/default.inputmap.json';
|
|
|
90
93
|
* exporting `setup(ctx): Promise<GameCleanup>`, which `SetupThreeRootAdapter`
|
|
91
94
|
* mounts with the complete {@link GameContext}.
|
|
92
95
|
*/
|
|
93
|
-
export type R3FGameContext =
|
|
94
|
-
GameContext,
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
| 'input'
|
|
99
|
-
| 'assets'
|
|
100
|
-
| 'systems'
|
|
101
|
-
| 'clock'
|
|
102
|
-
| 'debug'
|
|
103
|
-
| 'random'
|
|
104
|
-
| 'game'
|
|
105
|
-
| 'roots'
|
|
106
|
-
| 'playtest'
|
|
107
|
-
| 'registerSystemAdapter'
|
|
108
|
-
>;
|
|
96
|
+
export type R3FGameContext = ComponentContextBase &
|
|
97
|
+
Pick<GameContext, 'scene' | 'sceneIndex' | 'assets' | 'clock'> & {
|
|
98
|
+
/** Fiber may install either a perspective or orthographic camera. */
|
|
99
|
+
camera: THREE.Camera;
|
|
100
|
+
};
|
|
109
101
|
|
|
110
102
|
export interface R3FRootContextOptions {
|
|
111
103
|
/** World id — provenance for every debug-registry registration (matches the
|
|
@@ -185,6 +177,7 @@ export function createR3FRootContext(
|
|
|
185
177
|
const ctxRaw: Record<string, unknown> = {
|
|
186
178
|
systems,
|
|
187
179
|
assets: host.assets,
|
|
180
|
+
input: host.game?.input ?? null,
|
|
188
181
|
registerSystemAdapter: (kind: keyof SystemAdapters, adapter: SystemAdapters[typeof kind]) => {
|
|
189
182
|
// biome-ignore lint/suspicious/noExplicitAny: same per-key record write the classic adapter performs; correct by construction
|
|
190
183
|
(systemAdapters as any)[kind] = adapter;
|
|
@@ -232,7 +225,6 @@ export function createR3FRootContext(
|
|
|
232
225
|
if (host.game) {
|
|
233
226
|
const game = host.game;
|
|
234
227
|
const input = game.input;
|
|
235
|
-
ctxRaw['input'] = input;
|
|
236
228
|
debugRegistry.setInputActionsSource(id, () =>
|
|
237
229
|
input.actionNames().map((name) => ({ name, valueType: input.getActionValueType(name) })),
|
|
238
230
|
);
|
|
@@ -274,23 +266,6 @@ export function createR3FRootContext(
|
|
|
274
266
|
);
|
|
275
267
|
});
|
|
276
268
|
}
|
|
277
|
-
} else {
|
|
278
|
-
// `input` IS a field of every R3F world's ctx — it is game-owned, and this
|
|
279
|
-
// particular mount has no Game to own it (a bare harness or a foreign
|
|
280
|
-
// host). A throwing getter names that at the read, instead of handing back
|
|
281
|
-
// `undefined` that breaks deep inside a frame callback. Non-enumerable so
|
|
282
|
-
// spreads/serialization of the ctx never trip it.
|
|
283
|
-
Object.defineProperty(ctxRaw, 'input', {
|
|
284
|
-
configurable: true,
|
|
285
|
-
enumerable: false,
|
|
286
|
-
get(): never {
|
|
287
|
-
throw new Error(
|
|
288
|
-
`R3F world "${id}": ctx.input is game-owned and this mount has no Game host ` +
|
|
289
|
-
'(host.game is absent — bare harness/foreign host); mount through the vgai host ' +
|
|
290
|
-
'to get the shared InputManager.',
|
|
291
|
-
);
|
|
292
|
-
},
|
|
293
|
-
});
|
|
294
269
|
}
|
|
295
270
|
|
|
296
271
|
return {
|