@volter/editor-game 0.5.66 → 0.5.67
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/contributions/audio-unlock.service.ts +2 -2
- package/contributions/autoplay.service.ts +1 -1
- package/contributions/bridge.command.ts +3 -3
- package/contributions/canvas/component-board.service.ts +16 -0
- package/contributions/canvas/design-time-mount.service.ts +45 -0
- package/contributions/canvas-story-capture.service.ts +12 -0
- package/contributions/edit-mode-audio.service.ts +2 -2
- package/contributions/edit-mode-networking.service.ts +1 -1
- package/contributions/gameplay.command.ts +4 -4
- package/contributions/generation.service.ts +1 -1
- package/contributions/godot.style.ts +26 -6
- package/contributions/godot.view.ts +6 -0
- package/contributions/ingest.service.ts +2 -2
- package/contributions/instances.command.ts +1 -1
- package/contributions/navmesh.menu.ts +1 -1
- package/contributions/network-observer.service.ts +14 -0
- package/contributions/play.command.ts +10 -4
- package/contributions/react/component-board.service.ts +1 -1
- package/contributions/react/design-time-mount.service.ts +1 -1
- package/contributions/scene-document.service.ts +3 -3
- package/contributions/state-watch.menu.ts +1 -1
- package/contributions/state-watch.utility.tsx +1 -1
- package/contributions/team-playtest.service.ts +4 -4
- package/contributions/three/component-board.service.ts +1 -1
- package/contributions/three/component-verbs.command.ts +6 -6
- package/contributions/three/three-authoring.service.ts +8 -5
- package/contributions/three-story-capture.service.ts +12 -0
- package/contributions/unity.style.ts +17 -7
- package/contributions/unity.view.ts +6 -0
- package/contributions/unreal.style.ts +10 -2
- package/contributions/unreal.view.ts +5 -0
- package/package.json +20 -10
- package/src/asset-budget/AssetBudgetPanel.tsx +1 -1
- package/src/asset-budget/asset-budget-model.ts +1 -1
- package/src/audio/AudioDebuggerPanel.tsx +1 -1
- package/src/bridge/dispatch.ts +14 -14
- package/src/bridge/live-frames.ts +1 -1
- package/src/bridge/screenshot.ts +3 -3
- package/src/build/BuildProfilesPanel.tsx +3 -3
- package/src/canvas/canvas-board/CanvasBoardDocument.tsx +676 -0
- package/src/canvas/canvas-board/canvas-board-model.ts +407 -0
- package/src/canvas/canvas-board/canvas-component-board.ts +56 -0
- package/src/canvas/canvas-design-mount.ts +524 -0
- package/src/canvas/design-time-canvas-mount.ts +79 -0
- package/src/coverage/live-authoring-surface.ts +5 -5
- package/src/coverage/live-project-verbs.ts +1 -1
- package/src/coverage/native-system-coverage.ts +5 -5
- package/src/coverage/root-coverage.ts +1 -1
- package/src/coverage/session-coverage.ts +3 -3
- package/src/design-system-stories/ApplicationChrome.stories.tsx +5 -5
- package/src/design-system-stories/InspectorNarrowBodies.stories.tsx +7 -7
- package/src/edit-mode/edit-mode-audio.ts +4 -4
- package/src/edit-mode/edit-mode-networking.ts +2 -2
- package/src/game-document/GameCaptureFrameButton.tsx +1 -1
- package/src/game-document/GameDocument.tsx +2 -2
- package/src/game-document/GamePanel.tsx +4 -4
- package/src/game-document/InstanceInspectorPicker.tsx +2 -2
- package/src/game-document/crowd-debug.ts +1 -1
- package/src/game-document/device-preview.ts +10 -11
- package/src/game-document/physics-debug.ts +1 -1
- package/src/generation/GenerationActivity.tsx +3 -3
- package/src/generation/generation-documents.tsx +5 -5
- package/src/generation/generation-jobs.ts +1 -1
- package/src/host/adapter-runtime-bindings.ts +96 -5
- package/src/host/authoring/babylon-authoring-adapter.ts +6 -6
- package/src/host/authoring/gesture-persist.ts +1 -1
- package/src/host/authoring/ingest-data-writer.ts +1 -1
- package/src/host/authoring/ingest-source-persistence.ts +4 -4
- package/src/host/authoring/mounted-authoring.ts +4 -4
- package/src/host/authoring/phaser-live-authoring-adapter.ts +4 -4
- package/src/host/authoring/pixi-authoring-adapter.ts +40 -15
- package/src/host/authoring/pixi-creation-site-write-target.ts +2 -2
- package/src/host/authoring/pixi-live-write-target.ts +4 -4
- package/src/host/authoring/pixi-source-identity.ts +3 -3
- package/src/host/authoring/pixi-source-write-target.ts +1614 -0
- package/src/host/authoring/pixi-still-presentation.ts +1 -1
- package/src/host/authoring/pixi-structure-history.ts +2 -2
- package/src/host/authoring/pixi-transform-channels.ts +16 -14
- package/src/host/authoring/source-persistence-backend.ts +3 -3
- package/src/host/authoring/struct-write-pipe.ts +1 -1
- package/src/host/binding-resolver.ts +8 -9
- package/src/host/browser-transpile.ts +1 -1
- package/src/host/canvas-entry-runtime.ts +58 -47
- package/src/host/canvas-preview-frames.ts +482 -0
- package/src/host/components/CameraAuthoringOverlay.tsx +1 -1
- package/src/host/components/HeaderTelemetry.tsx +4 -4
- package/src/host/components/PixiIsolationSceneContent.tsx +10 -10
- package/src/host/components/ThreeIsolationSceneContent.tsx +3 -3
- package/src/host/components/frame-debugger-model.ts +2 -2
- package/src/host/components/header-telemetry-model.ts +2 -2
- package/src/host/components/scene-document.tsx +14 -14
- package/src/host/components/utility-view-state.ts +1 -1
- package/src/host/components/world-root-stage-binding.tsx +12 -12
- package/src/host/components/world-root-stage.ts +70 -49
- package/src/host/coverage/system-adapter-coverage.ts +3 -4
- package/src/host/design-system-stories/fixtures/editor-runtime.tsx +9 -11
- package/src/host/document-preview-three.ts +1 -1
- package/src/host/entry-adjudication.ts +6 -6
- package/src/host/game-css-scope-transform.ts +4 -0
- package/src/host/game-realm-page.ts +1 -1
- package/src/host/gameplay-export.ts +25 -14
- package/src/host/gameplay-recording.ts +5 -5
- package/src/host/gated-globals.ts +2 -2
- package/src/host/history/json-history-resource.ts +1 -1
- package/src/host/projection/pixi.ts +24 -2
- package/src/host/r3f-entry-runtime.ts +65 -34
- package/src/host/react-mount-runtime.ts +7 -48
- package/src/host/realm-services.ts +1 -1
- package/src/host/roots/canvas-root.tsx +361 -0
- package/src/host/roots/r3f-root.tsx +473 -0
- package/src/host/roots/react-root.ts +9 -43
- package/src/host/served-bundle-runtime-modules.ts +1 -19
- package/src/host/server-log-bridge.ts +2 -2
- package/src/host/stories/mounted-story-viewport-source.ts +1 -1
- package/src/host/stories/pixi-story-model.ts +30 -0
- package/src/host/stories/story-media-captures.ts +46 -0
- package/src/host/stories/story-media-presence.ts +3 -3
- package/src/host/stories/story-pixi-preview.ts +408 -0
- package/src/host/stories/story-three-preview.ts +806 -0
- package/src/host/stories/three-story-captures.ts +35 -0
- package/src/host/story-three-preview-runtime.ts +56 -0
- package/src/host/use-active-performance-source.ts +2 -2
- package/src/host/viewport-pose-memory.ts +1 -1
- package/src/host/viewport-root-presentation.ts +6 -5
- package/src/ingest/active-ingest.ts +1 -1
- package/src/ingest/authoring/ingest-dom-surface-authoring.ts +4 -4
- package/src/ingest/authoring/ingest-root-adapter.ts +8 -8
- package/src/ingest/deferred-ingest-play.ts +6 -6
- package/src/ingest/discovery-public-ingest.ts +2 -2
- package/src/ingest/ingest-boot-viewport.ts +2 -2
- package/src/ingest/ingest-canvas-scene-document.tsx +8 -8
- package/src/ingest/ingest-canvas-scene.ts +3 -3
- package/src/ingest/ingest-evidence-hook.ts +1 -1
- package/src/ingest/ingest-frame-snapshot.ts +1 -1
- package/src/ingest/ingest-play-control.ts +1 -1
- package/src/ingest/ingest-render-debug.ts +10 -10
- package/src/ingest/ingest-siblings.ts +13 -25
- package/src/ingest/module-mode.ts +14 -14
- package/src/ingest/mount-canvas-ingest-root.ts +21 -21
- package/src/ingest/mount-coverage.ts +2 -2
- package/src/ingest/mount-dom-ingest-root.ts +11 -11
- package/src/ingest/mount-ingest-root.ts +8 -8
- package/src/ingest/mount-three-ingest-root.ts +8 -8
- package/src/ingest/resolve-canvas.ts +1 -1
- package/src/ingest/served-html-boot.ts +1 -1
- package/src/ingest/surface-canvas.ts +1 -1
- package/src/ingest/unmount-ingest-root.ts +5 -5
- package/src/navmesh/navmesh-handler.ts +24 -16
- package/src/network/NetworkInspectorPanel.tsx +449 -14
- package/src/network/network-inspector-model.ts +14 -2
- package/src/play/play-log-events.ts +1 -1
- package/src/play/play-mode.ts +76 -133
- package/src/play/play-recording.ts +1 -1
- package/src/play/react-play-live-authoring.ts +3 -3
- package/src/play/run-selection.ts +93 -0
- package/src/play-bar/PlayBar.tsx +20 -39
- package/src/profiler/FrameDebuggerPanel.tsx +1 -1
- package/src/profiler/PerformancePanel.tsx +3 -3
- package/src/react/design-time-react-mount.ts +23 -65
- package/src/react/dom-authoring-adapter.ts +9 -9
- package/src/react/react-inspector-section.tsx +5 -5
- package/src/react/react-world-authoring-adapter.ts +11 -11
- package/src/react/story-documents/story-documents.tsx +9 -9
- package/src/react/ui-board-document.tsx +7 -7
- package/src/react/ui-component-board.ts +2 -2
- package/src/runtime/adapter/audio-meter.ts +21 -0
- package/src/runtime/adapter/first-party-audio-system.ts +230 -0
- package/src/runtime/adapter/ingest/contract-debug-adapter.ts +114 -0
- package/src/runtime/adapter/ingest/contract-system-adapters.ts +256 -0
- package/src/runtime/adapter/ingest/merge-debug-adapters.ts +197 -0
- package/src/runtime/adapter/ingest/observation-debug-adapter.ts +162 -0
- package/src/runtime/adapter/ingest/upstream-pin.ts +51 -0
- package/src/runtime/adapter/native-debug-module.ts +498 -0
- package/src/runtime/audio/bus-mixer.ts +161 -0
- package/src/runtime/audio/pose-guard.ts +80 -0
- package/src/runtime/core/frame-pacing.ts +126 -0
- package/src/runtime/core/game-loop.ts +225 -0
- package/src/runtime/core/game-scoped-slot.ts +28 -0
- package/src/runtime/core/seeded-random.ts +162 -0
- package/src/runtime/core/sim-clock.ts +391 -0
- package/src/runtime/core/system-runner.ts +269 -0
- package/src/runtime/core/types.ts +104 -0
- package/src/runtime/create-runtime.ts +1128 -0
- package/src/runtime/debug-bridge.ts +570 -0
- package/src/runtime/debug-registry.ts +899 -0
- package/src/runtime/dev/chrome-trace.ts +153 -0
- package/src/runtime/dev/instruments.ts +403 -0
- package/src/runtime/dev/logger.ts +119 -0
- package/src/runtime/dev/performance-profiler.ts +367 -0
- package/src/runtime/dev/register-render-vitals.ts +276 -0
- package/src/runtime/dev/render-census.ts +354 -0
- package/src/runtime/dev/render-debug-adapter.ts +218 -0
- package/src/runtime/dev/render-memory.ts +226 -0
- package/src/runtime/dev/render-vitals.ts +338 -0
- package/src/runtime/dev/static-batch-advisor.ts +188 -0
- package/src/runtime/dev/webgl-frame-capture.ts +366 -0
- package/src/runtime/dev/webgl-gpu-timer.ts +53 -0
- package/src/runtime/dev-build.ts +47 -0
- package/src/runtime/game.ts +1636 -0
- package/src/runtime/gameplay-rng-trap.ts +135 -0
- package/src/runtime/host-context.ts +64 -0
- package/src/runtime/input-router.ts +169 -0
- package/src/runtime/mount-manifest.ts +480 -0
- package/src/runtime/pixi/authoring.ts +706 -0
- package/src/runtime/pixi/ingest.ts +116 -0
- package/src/runtime/pixi/physics-registry.ts +49 -0
- package/src/runtime/pixi/render-pass-bracket.ts +117 -0
- package/src/runtime/pixi/scene-capture.ts +179 -0
- package/src/runtime/pixi/system-adapters.ts +69 -0
- package/src/runtime/playtest.ts +22 -0
- package/src/runtime/presentation.ts +141 -0
- package/src/runtime/render-control.ts +642 -0
- package/src/runtime/render-seed.ts +77 -0
- package/src/runtime/run-ticks-settled.ts +73 -0
- package/src/runtime/setup/setup-audio.ts +72 -0
- package/src/services/audio-pose-guard.ts +2 -2
- package/src/services/game-audio.ts +152 -0
- package/src/services/game-network.ts +657 -0
- package/src/services/game-physics.ts +334 -0
- package/src/state-watch/StateWatchPanel.tsx +1 -1
- package/src/three/authoring/camera-runtime-inspector-section.tsx +3 -2
- package/src/three/authoring/constraint-inspector-section.tsx +6 -5
- package/src/three/authoring/model-asset-inspector-section.tsx +7 -6
- package/src/three/authoring/oid-source-persistence.ts +7 -7
- package/src/three/authoring/r3f-design-session.ts +58 -54
- package/src/three/authoring/r3f-source-authoring-adapter.ts +95 -91
- package/src/three/authoring/reflection-probe-inspector-section.tsx +3 -2
- package/src/three/authoring/three-authoring-adapter.ts +29 -29
- package/src/three/component-verbs/extract-menu.ts +7 -6
- package/src/three/component-verbs/fork-menu.ts +7 -6
- package/src/three/component-verbs/internals-menu.ts +2 -2
- package/src/three/story-documents/three-story-documents.tsx +13 -13
- package/src/three/three-board/ThreeBoardDocument.tsx +13 -12
- package/src/three/three-board/board-scene.ts +6 -6
- package/src/three/three-board/three-component-board.ts +2 -2
- package/src/services/game-audio-unlock.ts +0 -48
|
@@ -0,0 +1,473 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The editor's mount of a `three` root whose entry module DEFAULT-EXPORTS a
|
|
3
|
+
* React component — the world a game's own boot renders inside `<Canvas>`:
|
|
4
|
+
*
|
|
5
|
+
* ```tsx
|
|
6
|
+
* export default function World() {
|
|
7
|
+
* return <><ambientLight /><mesh>…</mesh></>;
|
|
8
|
+
* }
|
|
9
|
+
* ```
|
|
10
|
+
*
|
|
11
|
+
* The world is an ordinary R3F app. The editor renders it bare into a Fiber
|
|
12
|
+
* root on the host's canvas and renderer, drives Fiber's `frameloop: 'never'`
|
|
13
|
+
* scheduler from the game's own loop. A game's input is its own: the session's
|
|
14
|
+
* input door reaches it through the entry's `debug.input` export.
|
|
15
|
+
*
|
|
16
|
+
* WHOSE REACT AND FIBER. The entry's hooks resolve `react` and
|
|
17
|
+
* `@react-three/fiber` through the project's module graph, so the root that
|
|
18
|
+
* renders it must be built with the same instances: {@link R3FRuntime} is
|
|
19
|
+
* handed in by `r3f-entry-runtime.ts`, which takes them from the R3F doorway
|
|
20
|
+
* under the packaged runtime and from its own imports in a checkout, where one
|
|
21
|
+
* Vite graph already shares them.
|
|
22
|
+
*
|
|
23
|
+
* three.js identity: nothing here imports `three` for scene objects. Fiber's
|
|
24
|
+
* catalogue is extended with the host's `three` (`host.three`), the same
|
|
25
|
+
* instance the returned scene and camera belong to.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
import type * as Fiber from '@react-three/fiber';
|
|
29
|
+
import type { RootState } from '@react-three/fiber';
|
|
30
|
+
import type { MountedThreeRoot, RootAdapter } from '@volter/editor-project/adapter';
|
|
31
|
+
import type { SystemAdapters } from '@volter/editor-project/adapter/system-adapter';
|
|
32
|
+
import {
|
|
33
|
+
type RenderVitalsRegistration,
|
|
34
|
+
registerRenderVitals,
|
|
35
|
+
} from '../../runtime/dev/register-render-vitals';
|
|
36
|
+
import {
|
|
37
|
+
createRenderDebugAdapter,
|
|
38
|
+
frameCaptureContextFor,
|
|
39
|
+
type RenderDebugWiring,
|
|
40
|
+
} from '../../runtime/dev/render-debug-adapter';
|
|
41
|
+
import { collectRenderMemory } from '../../runtime/dev/render-memory';
|
|
42
|
+
import { RENDER_SUBMIT_PHASE } from '../../runtime/dev/render-vitals';
|
|
43
|
+
import { createWebGLFrameCapture } from '../../runtime/dev/webgl-frame-capture';
|
|
44
|
+
import { getDebugRegistry } from '../../runtime/debug-registry';
|
|
45
|
+
import { devBuildEnabled } from '../../runtime/dev-build';
|
|
46
|
+
import type { GameThreeHostContext } from '../../runtime/host-context';
|
|
47
|
+
import type * as React from 'react';
|
|
48
|
+
import type { ComponentType, PropsWithChildren } from 'react';
|
|
49
|
+
|
|
50
|
+
/** The React and Fiber a three world is mounted with — the ones its own hooks resolve. */
|
|
51
|
+
export interface R3FRuntime {
|
|
52
|
+
readonly createElement: typeof React.createElement;
|
|
53
|
+
readonly Fragment: typeof React.Fragment;
|
|
54
|
+
readonly Component: typeof React.Component;
|
|
55
|
+
readonly useEffect: typeof React.useEffect;
|
|
56
|
+
readonly createRoot: typeof Fiber.createRoot;
|
|
57
|
+
readonly extend: typeof Fiber.extend;
|
|
58
|
+
readonly advance: typeof Fiber.advance;
|
|
59
|
+
readonly flushSync: typeof Fiber.flushSync;
|
|
60
|
+
readonly events: typeof Fiber.events;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** The slice of `WebGLRenderer.info` the vitals reporter reads. Declared
|
|
64
|
+
* structurally rather than imported from `three`, per this module's own
|
|
65
|
+
* identity rule (see the header): a duck-typed host renderer legitimately has
|
|
66
|
+
* no `info` at all, which is why every read of it is guarded. */
|
|
67
|
+
interface RendererCounters {
|
|
68
|
+
readonly render: { readonly calls: number; readonly triangles: number; readonly frame: number };
|
|
69
|
+
readonly memory: { readonly geometries: number; readonly textures: number };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
interface R3FEntryModuleExports {
|
|
73
|
+
/** The one shape: the world IS a component. */
|
|
74
|
+
readonly default?: ComponentType;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
type MountErrorBoundaryProps = PropsWithChildren<{ onError: (error: Error) => void }>;
|
|
78
|
+
|
|
79
|
+
/** React errors must reject a mount in Node too, where there is no window
|
|
80
|
+
* error event. This boundary changes no authored content on successful mounts.
|
|
81
|
+
* Built on the runtime's own `Component`, once per runtime. */
|
|
82
|
+
const boundaries = new WeakMap<object, ComponentType<MountErrorBoundaryProps>>();
|
|
83
|
+
function mountErrorBoundary(runtime: R3FRuntime): ComponentType<MountErrorBoundaryProps> {
|
|
84
|
+
const known = boundaries.get(runtime.Component);
|
|
85
|
+
if (known) return known;
|
|
86
|
+
class MountErrorBoundary extends runtime.Component<MountErrorBoundaryProps, { failed: boolean }> {
|
|
87
|
+
override state = { failed: false };
|
|
88
|
+
static getDerivedStateFromError(): { failed: boolean } {
|
|
89
|
+
return { failed: true };
|
|
90
|
+
}
|
|
91
|
+
override componentDidCatch(error: Error): void {
|
|
92
|
+
this.props.onError(error);
|
|
93
|
+
}
|
|
94
|
+
override render() {
|
|
95
|
+
return this.state.failed ? null : this.props.children;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
boundaries.set(runtime.Component, MountErrorBoundary);
|
|
99
|
+
return MountErrorBoundary;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Build the `RootAdapter` for one default-exported R3F world. Mounts through
|
|
104
|
+
* react-three-fiber, gated entirely by the host's own loop and rendering
|
|
105
|
+
* through the host's own `WebGLRenderer` — never a second renderer, never a
|
|
106
|
+
* second `requestAnimationFrame` loop.
|
|
107
|
+
*/
|
|
108
|
+
function threeWorldAdapter(id: string, component: ComponentType, runtime: R3FRuntime): RootAdapter {
|
|
109
|
+
const { createElement, Fragment, useEffect, createRoot, extend, advance, flushSync } = runtime;
|
|
110
|
+
const MountErrorBoundary = mountErrorBoundary(runtime);
|
|
111
|
+
const content = createElement(component);
|
|
112
|
+
|
|
113
|
+
return {
|
|
114
|
+
id,
|
|
115
|
+
|
|
116
|
+
async mount(host: GameThreeHostContext): Promise<MountedThreeRoot> {
|
|
117
|
+
// Headless mounts use this same Fiber reconciler and authored tree.
|
|
118
|
+
// Their host supplies an inert draw command; absence of a GPU is not
|
|
119
|
+
// permission to return a successful but empty scene.
|
|
120
|
+
// Fiber v9 made the THREE catalogue tree-shakeable: `<Canvas>` calls
|
|
121
|
+
// `extend(THREE)` for you, a bare `createRoot` does NOT — without this,
|
|
122
|
+
// the FIRST three intrinsic in the tree (`<color>`, `<ambientLight>`,
|
|
123
|
+
// …) throws "X is not part of the THREE namespace! Did you forget to
|
|
124
|
+
// extend?" at reconcile time. Extending with `host.three` (not a fresh
|
|
125
|
+
// `import * as THREE`) keeps the catalogue on the host's deduped three
|
|
126
|
+
// instance — the same identity rule the returned scene/camera rely on.
|
|
127
|
+
// `extend` merges into a module-global catalogue, so calling it once
|
|
128
|
+
// per mount is idempotent.
|
|
129
|
+
extend(host.three as unknown as Parameters<typeof extend>[0]);
|
|
130
|
+
|
|
131
|
+
const gameDebugRegistry = host.game ? getDebugRegistry(host.game) : null;
|
|
132
|
+
|
|
133
|
+
const canvas = host.surface.canvas;
|
|
134
|
+
const root = createRoot(canvas);
|
|
135
|
+
|
|
136
|
+
// `RootState` (the live scene/camera/gl fiber built) only arrives via
|
|
137
|
+
// the `onCreated` callback — `root.render()`'s return value is
|
|
138
|
+
// TECHNICALLY the same store, but `onCreated` is the supported hook, and
|
|
139
|
+
// waiting for it (rather than assuming the first commit already ran
|
|
140
|
+
// synchronously) is the honest choice under React 19's concurrent
|
|
141
|
+
// renderer, which does not guarantee a synchronous first commit the way
|
|
142
|
+
// legacy ReactDOM.render did.
|
|
143
|
+
let resolveState!: (state: RootState) => void;
|
|
144
|
+
const statePromise = new Promise<RootState>((resolve) => {
|
|
145
|
+
resolveState = resolve;
|
|
146
|
+
});
|
|
147
|
+
let resolveEffectsReady!: () => void;
|
|
148
|
+
const effectsReady = new Promise<void>((resolve) => {
|
|
149
|
+
resolveEffectsReady = resolve;
|
|
150
|
+
});
|
|
151
|
+
// Rendered after the game's content. React runs passive effects in tree
|
|
152
|
+
// order, so when this fires every provider/command effect in the
|
|
153
|
+
// initial tree has registered. A Play acknowledgement is a claim that
|
|
154
|
+
// the mounted game's command surface exists, not merely that Fiber
|
|
155
|
+
// allocated a scene.
|
|
156
|
+
function MountEffectsReady(): null {
|
|
157
|
+
useEffect(resolveEffectsReady, []);
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// `root.configure()` is ASYNC in fiber v9 (`Promise<ReconcilerRoot>`) —
|
|
162
|
+
// it must be awaited before `render()`.
|
|
163
|
+
await root.configure({
|
|
164
|
+
// The engine's renderer, not a second one — fiber renders THROUGH
|
|
165
|
+
// `host.renderer` instead of constructing its own `WebGLRenderer`.
|
|
166
|
+
gl: host.renderer,
|
|
167
|
+
// …and the host's shadow map, not fiber's.
|
|
168
|
+
//
|
|
169
|
+
// `configure()` runs `gl.shadowMap.enabled = !!shadows`
|
|
170
|
+
// UNCONDITIONALLY, against its own default of `false`. Passing nothing
|
|
171
|
+
// therefore does not mean "leave it alone" — it means "turn shadows
|
|
172
|
+
// off on the renderer the host just configured" (FX-5: every three
|
|
173
|
+
// root silently read flat, with no error anywhere). Reading it back
|
|
174
|
+
// off the renderer is exactly "don't clobber": a boolean also pins
|
|
175
|
+
// `shadowMap.type` to `PCFSoftShadowMap`, which is what
|
|
176
|
+
// `createHostRenderer` already sets, so the round trip is lossless
|
|
177
|
+
// and the HOST stays the one deciding.
|
|
178
|
+
//
|
|
179
|
+
// Optional-chained even though `WebGLRenderer.shadowMap` is not
|
|
180
|
+
// optional: a headless/stub host renderer is a real shape in this
|
|
181
|
+
// repo's tests, and fiber's own `if (gl.shadowMap)` guard means the
|
|
182
|
+
// value it reads there is ignored anyway.
|
|
183
|
+
shadows: host.renderer.shadowMap?.enabled ?? false,
|
|
184
|
+
// The engine's gated loop is the ONLY loop — fiber must never run its
|
|
185
|
+
// own rAF (that would defeat editor pause; `drivesOwnLoop: false`).
|
|
186
|
+
frameloop: 'never',
|
|
187
|
+
// `RenderProps.size` types as the FULL `Size` (width/height/top/left),
|
|
188
|
+
// so `top`/`left` are pinned to 0 (this bridge always fills its whole
|
|
189
|
+
// canvas; no offset viewport).
|
|
190
|
+
size: { width: host.surface.width, height: host.surface.height, top: 0, left: 0 },
|
|
191
|
+
// Fiber's pointer-event manager, passed EXPLICITLY. There IS no
|
|
192
|
+
// default for a bare `createRoot`: the `events = createPointerEvents`
|
|
193
|
+
// default lives in the `<Canvas>` COMPONENT, which this mount does not
|
|
194
|
+
// use, and `configure` gates on `if (events && !state.events.handlers)`.
|
|
195
|
+
// With `events` undefined the store keeps its initial
|
|
196
|
+
// `{ priority, enabled, connected: false }` — no handlers, nothing
|
|
197
|
+
// bound to the canvas, no error — and every mesh-level pointer prop
|
|
198
|
+
// (`onClick`, `onPointerOver`, `onPointerMissed`) is dead. Proven by a
|
|
199
|
+
// control experiment differing ONLY in this property.
|
|
200
|
+
...(host.headless ? {} : { events: runtime.events }),
|
|
201
|
+
onCreated: (state) => resolveState(state),
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
// No `<StrictMode>` — the host mounts once; StrictMode's deliberate
|
|
205
|
+
// double-invoke of effects would double-subscribe `useFrame` callbacks
|
|
206
|
+
// against a host loop that only ticks once per frame.
|
|
207
|
+
const world = createElement(
|
|
208
|
+
Fragment,
|
|
209
|
+
null,
|
|
210
|
+
content,
|
|
211
|
+
createElement(MountEffectsReady, { key: 'vgai-mount-effects-ready' }),
|
|
212
|
+
);
|
|
213
|
+
// A reconcile-time crash (e.g. a missing `extend` catalogue entry)
|
|
214
|
+
// surfaces as an uncaught window error and `onCreated` never fires —
|
|
215
|
+
// without this guard, `mount()` would await `statePromise` FOREVER and
|
|
216
|
+
// silently wedge every world declared after this one (roots mount
|
|
217
|
+
// sequentially). Convert that class of failure into a loud mount error.
|
|
218
|
+
const state = await new Promise<RootState>((resolve, reject) => {
|
|
219
|
+
const fail = (message: string) => {
|
|
220
|
+
cleanup();
|
|
221
|
+
root.unmount();
|
|
222
|
+
gameDebugRegistry?.strip(id);
|
|
223
|
+
reject(
|
|
224
|
+
new Error(`three world "${id}": fiber crashed before its first commit — ${message}`),
|
|
225
|
+
);
|
|
226
|
+
};
|
|
227
|
+
const onError = (event: ErrorEvent) => {
|
|
228
|
+
fail(event.message);
|
|
229
|
+
};
|
|
230
|
+
const timer = setTimeout(() => {
|
|
231
|
+
cleanup();
|
|
232
|
+
root.unmount();
|
|
233
|
+
gameDebugRegistry?.strip(id);
|
|
234
|
+
reject(
|
|
235
|
+
new Error(
|
|
236
|
+
`three world "${id}": onCreated did not fire within 10s — the R3F tree never ` +
|
|
237
|
+
'reached its first commit (mount() fails loudly instead of hanging)',
|
|
238
|
+
),
|
|
239
|
+
);
|
|
240
|
+
}, 10_000);
|
|
241
|
+
const cleanup = () => {
|
|
242
|
+
clearTimeout(timer);
|
|
243
|
+
if (typeof window !== 'undefined') window.removeEventListener('error', onError);
|
|
244
|
+
};
|
|
245
|
+
if (typeof window !== 'undefined') window.addEventListener('error', onError);
|
|
246
|
+
root.render(
|
|
247
|
+
createElement(MountErrorBoundary, { onError: (error) => fail(error.message) }, world),
|
|
248
|
+
);
|
|
249
|
+
void Promise.all([statePromise, effectsReady]).then(([s]) => {
|
|
250
|
+
cleanup();
|
|
251
|
+
resolve(s);
|
|
252
|
+
});
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
// Clock hardening: fiber's internal `update()` calls
|
|
256
|
+
// `state.clock.getDelta()` BEFORE its `frameloop:'never'` branch, and a
|
|
257
|
+
// RUNNING (or autoStart) `THREE.Clock` accumulates WALL time into
|
|
258
|
+
// `elapsedTime` as a side effect — skewing the deltas the 'never'
|
|
259
|
+
// branch derives from the game timestamps `update(dt)` feeds below.
|
|
260
|
+
// Stopped + autoStart=false makes `getDelta()` a pure no-op, so
|
|
261
|
+
// `useFrame` deltas come from game time alone.
|
|
262
|
+
state.clock.autoStart = false;
|
|
263
|
+
state.clock.stop();
|
|
264
|
+
|
|
265
|
+
// THE STORE IS THE TRUTH, NOT THE `onCreated` SNAPSHOT.
|
|
266
|
+
//
|
|
267
|
+
// `state` is one immutable zustand snapshot: every `set(...)` produces a
|
|
268
|
+
// NEW state object. A world that legitimately REPLACES the default
|
|
269
|
+
// camera — `useThree(s => s.set)({ camera })`, which is exactly what
|
|
270
|
+
// drei's `makeDefault` does — never reaches anything read off `state`
|
|
271
|
+
// directly, and fiber's own `update()` renders
|
|
272
|
+
// `state.gl.render(state.scene, state.camera)` off the state object it
|
|
273
|
+
// is HANDED. Measured: an authored `OrthographicCamera` world rendered
|
|
274
|
+
// from fiber's default (0, 0, 5) forever, culling every ground polygon.
|
|
275
|
+
// `state.get()` is fiber's own `StoreApi.getState`, carried on
|
|
276
|
+
// `RootState` for precisely this reason; it is a plain map read.
|
|
277
|
+
const live = (): RootState => state.get();
|
|
278
|
+
|
|
279
|
+
// --- Live render vitals, host-seeded ---------------------------------
|
|
280
|
+
// A running game must be able to explain its own frame cost through the
|
|
281
|
+
// debug registry, with no capability to install and nothing for a game
|
|
282
|
+
// to write. Gate: `devBuildEnabled()` (the ONE owner of "is this a dev
|
|
283
|
+
// context" — a ship build registers nothing and pays nothing) and a
|
|
284
|
+
// `Game` shell (the readings are folded out of that game's profiler
|
|
285
|
+
// frames). A headless host cannot supply GPU readings.
|
|
286
|
+
let renderVitals: RenderVitalsRegistration | null = null;
|
|
287
|
+
if (!host.headless && host.game && devBuildEnabled()) {
|
|
288
|
+
// The readings are folded out of profiler frames, and the profiler is
|
|
289
|
+
// a flag the editor already sets on play — under the dev gate this is
|
|
290
|
+
// the same cost arriving a little earlier, not a new one.
|
|
291
|
+
host.game.profiler.enabled = true;
|
|
292
|
+
if (!gameDebugRegistry) {
|
|
293
|
+
throw new Error(`three world "${id}": mounted Game has no debug registry.`);
|
|
294
|
+
}
|
|
295
|
+
renderVitals = registerRenderVitals({
|
|
296
|
+
registry: gameDebugRegistry,
|
|
297
|
+
worldId: id,
|
|
298
|
+
profiler: host.game.profiler,
|
|
299
|
+
scene: live().scene,
|
|
300
|
+
renderer: host.renderer,
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Engine-owned render introspection, built over the SAME renderer and
|
|
305
|
+
// scene Fiber uses. A design-time stand-in has no context; a headless
|
|
306
|
+
// mount also honestly omits the GPU capture capability.
|
|
307
|
+
const rendererContext =
|
|
308
|
+
typeof host.renderer.getContext === 'function' ? host.renderer.getContext() : undefined;
|
|
309
|
+
const frameCaptureContext = frameCaptureContextFor(host.headless ?? false, rendererContext);
|
|
310
|
+
const frameCapture = frameCaptureContext
|
|
311
|
+
? createWebGLFrameCapture(frameCaptureContext)
|
|
312
|
+
: null;
|
|
313
|
+
const renderDebugWiring: RenderDebugWiring | null = frameCapture
|
|
314
|
+
? createRenderDebugAdapter({
|
|
315
|
+
capture: frameCapture,
|
|
316
|
+
scene: state.scene,
|
|
317
|
+
memory: () => collectRenderMemory(live().scene, host.renderer.info),
|
|
318
|
+
})
|
|
319
|
+
: null;
|
|
320
|
+
// The session debug registry is host instrumentation, not component
|
|
321
|
+
// context. Publishing the existing adapter here lets coverage and
|
|
322
|
+
// editor panels see the same door without wrapping the R3F tree. The
|
|
323
|
+
// world's own declared systems merge in from the entry module at the
|
|
324
|
+
// Game layer (`installDeclaredSystemAdapters`).
|
|
325
|
+
const systemAdapters: SystemAdapters = gameDebugRegistry
|
|
326
|
+
? { debug: gameDebugRegistry.adapter }
|
|
327
|
+
: {};
|
|
328
|
+
if (renderDebugWiring) systemAdapters.renderDebug = renderDebugWiring.adapter;
|
|
329
|
+
|
|
330
|
+
// The engine drives every `useFrame` through the mounted world's
|
|
331
|
+
// `update(dt)` hook, never off a raw Fiber loop. This mount declares
|
|
332
|
+
// that indivisible Fiber advance at DISPLAY cadence below: the real
|
|
333
|
+
// host calls it once per presentation, a paused world gets dt=0, and an
|
|
334
|
+
// explicit `Game.play.step()` still ticks it exactly once.
|
|
335
|
+
//
|
|
336
|
+
// `advance(timestamp, runGlobalEffects, state)`'s `timestamp` is
|
|
337
|
+
// consumed as `THREE.Clock.elapsedTime` DIRECTLY when
|
|
338
|
+
// `frameloop:'never'` (verified against fiber's `update()`
|
|
339
|
+
// implementation): `delta = timestamp - clock.elapsedTime;
|
|
340
|
+
// clock.elapsedTime = timestamp`. So `timestamp` must be a
|
|
341
|
+
// monotonically increasing SECONDS value — GAME time, not wall time:
|
|
342
|
+
// `update` simply isn't called while this world is frozen, so
|
|
343
|
+
// accumulating its `dt` means fiber's clock does not advance across a
|
|
344
|
+
// pause. Wall clock would leak the pause duration into the first
|
|
345
|
+
// resumed frame as one giant `useFrame` delta.
|
|
346
|
+
let elapsed = 0;
|
|
347
|
+
|
|
348
|
+
return {
|
|
349
|
+
kind: 'three',
|
|
350
|
+
// Fiber's REAL `THREE.Scene`/`THREE.Camera`. Checkout development
|
|
351
|
+
// dedupes `three`; a registry-installed project and the prebundled
|
|
352
|
+
// editor can legitimately have distinct constructor identities, so
|
|
353
|
+
// editor boundaries recognize Three's stable structural brands
|
|
354
|
+
// (`isScene`, `isCamera`) rather than relying only on `instanceof`.
|
|
355
|
+
//
|
|
356
|
+
// Both are GETTERS over the live store, not snapshot fields: the host
|
|
357
|
+
// and the editor must see the camera the world actually renders
|
|
358
|
+
// through, including one the world swapped in after the first commit
|
|
359
|
+
// (see `live` above).
|
|
360
|
+
get scene() {
|
|
361
|
+
return live().scene;
|
|
362
|
+
},
|
|
363
|
+
get camera() {
|
|
364
|
+
return live().camera;
|
|
365
|
+
},
|
|
366
|
+
drivesOwnLoop: false,
|
|
367
|
+
// Fiber's `advance()` cannot separate its `useFrame` callbacks from
|
|
368
|
+
// its WebGL draw. Running this opaque update in every fixed catch-up
|
|
369
|
+
// substep renders N complete frames inside one rAF callback: once a
|
|
370
|
+
// scene costs more than 16.7ms, the accumulator asks for more draws,
|
|
371
|
+
// those draws make the next gap larger, and the loop spirals to its
|
|
372
|
+
// max-substep ceiling. The host's display pass is the one clock that
|
|
373
|
+
// must own this inseparable native advance.
|
|
374
|
+
updateCadence: 'display',
|
|
375
|
+
// Adapter surface: `debug` (the shared game registry) and, when a real
|
|
376
|
+
// WebGL2 context exists, `renderDebug` are engine-seeded. The game's
|
|
377
|
+
// own capabilities arrive as the entry module's declared `systems`.
|
|
378
|
+
systems: systemAdapters,
|
|
379
|
+
systemScope: live().scene,
|
|
380
|
+
update(dt: number): void {
|
|
381
|
+
elapsed += dt;
|
|
382
|
+
const current = live();
|
|
383
|
+
// Fiber receives an ABSOLUTE timestamp and reconstructs `delta` by subtraction. Publish
|
|
384
|
+
// the exact host-owned dt beside the synchronous advance so compatibility adapters that
|
|
385
|
+
// reproduce another engine's frame clock do not inherit one-ulp cancellation residue.
|
|
386
|
+
try {
|
|
387
|
+
renderDebugWiring?.beforeRender();
|
|
388
|
+
const profiler = host.game?.profiler;
|
|
389
|
+
if (!profiler?.enabled) {
|
|
390
|
+
advance(elapsed, true, current);
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
// The frame's CPU submission cost, produced where the draw
|
|
394
|
+
// actually happens: `advance()` under `frameloop: 'never'` is what
|
|
395
|
+
// calls `gl.render(scene, camera)`, and this bracket is also what
|
|
396
|
+
// makes this frame a PRESENTATION as far as the vitals fold is
|
|
397
|
+
// concerned (`../dev/render-vitals.ts`: no bracket, no display
|
|
398
|
+
// frame). The profiler's phase clock is a stack, so bracketing
|
|
399
|
+
// here cannot truncate an enclosing phase.
|
|
400
|
+
const info = host.renderer.info as RendererCounters | undefined;
|
|
401
|
+
const passesBefore = info?.render.frame ?? 0;
|
|
402
|
+
profiler.beginPhase();
|
|
403
|
+
try {
|
|
404
|
+
advance(elapsed, true, current);
|
|
405
|
+
} finally {
|
|
406
|
+
profiler.endPhase(RENDER_SUBMIT_PHASE);
|
|
407
|
+
}
|
|
408
|
+
// Checked, not assumed — a duck-typed/design-time renderer
|
|
409
|
+
// supplies only what a mount needs, and a mount with no counters
|
|
410
|
+
// must report NOTHING rather than throw once per frame. `gpuMs` is
|
|
411
|
+
// null: this path runs no GPU timer, and the profiler keeps the
|
|
412
|
+
// previous reading rather than inventing a zero.
|
|
413
|
+
if (info) {
|
|
414
|
+
profiler.reportRender({
|
|
415
|
+
gpuMs: null,
|
|
416
|
+
drawCalls: info.render.calls,
|
|
417
|
+
triangles: info.render.triangles,
|
|
418
|
+
geometries: info.memory.geometries,
|
|
419
|
+
textures: info.memory.textures,
|
|
420
|
+
renderPasses: info.render.frame - passesBefore,
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
} finally {
|
|
424
|
+
renderDebugWiring?.afterRender();
|
|
425
|
+
}
|
|
426
|
+
},
|
|
427
|
+
resize(width: number, height: number): void {
|
|
428
|
+
live().setSize(width, height);
|
|
429
|
+
},
|
|
430
|
+
dispose(): void {
|
|
431
|
+
// Reject/restore a pending capture while its renderables still
|
|
432
|
+
// exist, then unmount component effects and strip this world's
|
|
433
|
+
// registrations.
|
|
434
|
+
renderDebugWiring?.dispose();
|
|
435
|
+
// The editor audits and disposes the mounted game's window/document
|
|
436
|
+
// realm immediately after `session.stop()`. Fiber's ordinary
|
|
437
|
+
// `unmount()` submits a concurrent reconciler update, so component
|
|
438
|
+
// effect cleanups could still be pending at that boundary and the
|
|
439
|
+
// audit would truthfully report/reclaim listeners the world was
|
|
440
|
+
// about to remove itself. Force the unmount COMMIT (not fiber's
|
|
441
|
+
// deliberately delayed renderer/context disposal) to finish here: a
|
|
442
|
+
// stopped root owns no live component lifecycle beyond this method.
|
|
443
|
+
flushSync(() => root.unmount());
|
|
444
|
+
// The vitals fold owns two `profiler.subscribe` registrations —
|
|
445
|
+
// ended here, before the strip, so a subscription can never outlive
|
|
446
|
+
// the door it feeds.
|
|
447
|
+
renderVitals?.dispose();
|
|
448
|
+
renderVitals = null;
|
|
449
|
+
// `strip(id)` ends this world's PROVIDERS AND COMMANDS (the
|
|
450
|
+
// render-vitals door above is the one this mount registers under
|
|
451
|
+
// `id`); sibling roots are untouched. It does NOT end the input
|
|
452
|
+
// seams `wireGameInputSeams` set — see that module's LIFETIME note.
|
|
453
|
+
gameDebugRegistry?.strip(id);
|
|
454
|
+
},
|
|
455
|
+
};
|
|
456
|
+
},
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* What a three entry module MEANS: a default-exported component is a world;
|
|
462
|
+
* anything else is `null`, and each caller decides what that means (Play
|
|
463
|
+
* refuses the root by name, the design session declines the world).
|
|
464
|
+
*/
|
|
465
|
+
export function resolveR3FEntryAdapter(
|
|
466
|
+
entryModule: unknown,
|
|
467
|
+
rootId: string,
|
|
468
|
+
runtime: R3FRuntime,
|
|
469
|
+
): RootAdapter | null {
|
|
470
|
+
const mod = entryModule as R3FEntryModuleExports | undefined;
|
|
471
|
+
if (typeof mod?.default === 'function') return threeWorldAdapter(rootId, mod.default, runtime);
|
|
472
|
+
return null;
|
|
473
|
+
}
|
|
@@ -3,11 +3,9 @@
|
|
|
3
3
|
* `ingest-react` (a vendored React game reached through a host shim).
|
|
4
4
|
*
|
|
5
5
|
* They mount through the SAME react host stack: the same realm entry load, the
|
|
6
|
-
* same default-export-component validation, the same
|
|
7
|
-
* the
|
|
8
|
-
*
|
|
9
|
-
* no special case": the automatic `<WorldProvider>` wrap is context-only and
|
|
10
|
-
* harmless to a foreign tree.
|
|
6
|
+
* same default-export-component validation, the same `createRoot(...).render(<Entry />)`
|
|
7
|
+
* the game's own boot performs. Entry validation names the identity, so a failure is
|
|
8
|
+
* attributed to the right one.
|
|
11
9
|
*
|
|
12
10
|
* An ingest-react world's `entry` is NOT a first-party component — it is a
|
|
13
11
|
* HOST-SHIM module living beside the ingested game's manifest, importing the
|
|
@@ -17,12 +15,10 @@
|
|
|
17
15
|
* declared `entry` is ever imported, so that entry point is never reached.
|
|
18
16
|
*/
|
|
19
17
|
|
|
20
|
-
import type { MountedReactGame, ReactRootAdapter } from '
|
|
21
|
-
import type {
|
|
22
|
-
import type { GameDomHostContext } from '@volter/game-runtime/runtime/host-context';
|
|
18
|
+
import type { MountedReactGame, ReactRootAdapter } from '../../runtime/create-runtime';
|
|
19
|
+
import type { GameDomHostContext } from '../../runtime/host-context';
|
|
23
20
|
import type { ResolvedAdapterRoot } from '@volter/editor-project/manifest/load';
|
|
24
21
|
import type { ComponentType } from 'react';
|
|
25
|
-
import { resolveWorldProviderForProject } from '../react-mount-runtime';
|
|
26
22
|
import type { RealmServices } from '../realm-services';
|
|
27
23
|
|
|
28
24
|
/**
|
|
@@ -47,33 +43,10 @@ function loadReactEntryComponent(
|
|
|
47
43
|
return mod.default as ComponentType;
|
|
48
44
|
}
|
|
49
45
|
|
|
50
|
-
/**
|
|
51
|
-
* `DomHostContext.game` is a typed, guaranteed field on the native multi-root
|
|
52
|
-
* mount path. The loud check here stays for the one host that legitimately
|
|
53
|
-
* carries none — a `dom` sibling mounted BESIDE an ingest root
|
|
54
|
-
* (`ingest-siblings.ts`) has no native `Game`, and that host renders the entry
|
|
55
|
-
* component BARE rather than fabricating an empty Game into `<WorldProvider>`.
|
|
56
|
-
* A hand-rolled host that skipped the engine's mount path must fail loudly
|
|
57
|
-
* here, never lie an `undefined` Game into the provider.
|
|
58
|
-
*/
|
|
59
|
-
function requireHostGame(world: ResolvedAdapterRoot, host: GameDomHostContext, label: string): Game {
|
|
60
|
-
const game = host.game;
|
|
61
|
-
if (!game) {
|
|
62
|
-
throw new Error(
|
|
63
|
-
`resolveRootBinding: world "${world.id}" (${label}) mount() received a DomHostContext ` +
|
|
64
|
-
'with no `game` — WorldProvider needs a live Game at mount time and this host did not ' +
|
|
65
|
-
'carry one.',
|
|
66
|
-
);
|
|
67
|
-
}
|
|
68
|
-
return game;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
46
|
/**
|
|
72
47
|
* The shared entry-loading step, exported so a composite's `dom` SIBLING mount
|
|
73
48
|
* (`ingest-siblings.ts`) reuses the EXACT SAME `entry`-required check and
|
|
74
|
-
* component-shape validation without duplicating any of it.
|
|
75
|
-
* renders the returned component bare; {@link resolveDomAdapter} below wraps
|
|
76
|
-
* it in `<WorldProvider>`.
|
|
49
|
+
* component-shape validation without duplicating any of it.
|
|
77
50
|
*/
|
|
78
51
|
export async function resolveReactAdapterRootComponent(
|
|
79
52
|
world: ResolvedAdapterRoot,
|
|
@@ -96,9 +69,7 @@ export async function resolveReactAdapterRootComponent(
|
|
|
96
69
|
/** Build the `mount` half both identities share. */
|
|
97
70
|
function reactMount(
|
|
98
71
|
world: ResolvedAdapterRoot,
|
|
99
|
-
label: 'dom' | 'ingest-react',
|
|
100
72
|
Entry: ComponentType,
|
|
101
|
-
WorldProvider: ComponentType<{ game: Game; children?: unknown }>,
|
|
102
73
|
realm: RealmServices,
|
|
103
74
|
/** Commit the initial DOM synchronously. The play authoring adapter is
|
|
104
75
|
* installed immediately after `mount()` resolves, so a first-party root
|
|
@@ -109,7 +80,6 @@ function reactMount(
|
|
|
109
80
|
return {
|
|
110
81
|
id: world.id,
|
|
111
82
|
async mount(host: GameDomHostContext): Promise<MountedReactGame> {
|
|
112
|
-
const game = requireHostGame(world, host, label);
|
|
113
83
|
// The runtime comes from the realm — the PROJECT's own react under the
|
|
114
84
|
// packaged runtime, the editor's static imports otherwise — so this
|
|
115
85
|
// mount always shares ONE react instance with the entry component's own
|
|
@@ -117,9 +87,7 @@ function reactMount(
|
|
|
117
87
|
const runtime = await realm.reactDomRuntime();
|
|
118
88
|
const root = runtime.createRoot(host.container);
|
|
119
89
|
const render = () => {
|
|
120
|
-
root.render(
|
|
121
|
-
runtime.createElement(WorldProvider, { game, children: runtime.createElement(Entry) }),
|
|
122
|
-
);
|
|
90
|
+
root.render(runtime.createElement(Entry));
|
|
123
91
|
};
|
|
124
92
|
if (flushInitialRender) runtime.flushSync(render);
|
|
125
93
|
else render();
|
|
@@ -142,8 +110,7 @@ export async function resolveDomAdapter(
|
|
|
142
110
|
onEntryModule?: ((module: Record<string, unknown>) => void) | undefined,
|
|
143
111
|
): Promise<ReactRootAdapter> {
|
|
144
112
|
const Entry = await resolveReactAdapterRootComponent(world, realm, onEntryModule);
|
|
145
|
-
|
|
146
|
-
return reactMount(world, 'dom', Entry, WorldProvider, realm, true);
|
|
113
|
+
return reactMount(world, Entry, realm, true);
|
|
147
114
|
}
|
|
148
115
|
|
|
149
116
|
/**
|
|
@@ -173,9 +140,8 @@ export async function resolveIngestReactAdapter(
|
|
|
173
140
|
}
|
|
174
141
|
const mod = await realm.loadEntryModule(world.entry, world.id, 'dom');
|
|
175
142
|
const Entry = loadReactEntryComponent(world, mod, 'ingest-react');
|
|
176
|
-
const WorldProvider = await resolveWorldProviderForProject();
|
|
177
143
|
return {
|
|
178
|
-
adapter: reactMount(world,
|
|
144
|
+
adapter: reactMount(world, Entry, realm, false),
|
|
179
145
|
module: mod,
|
|
180
146
|
};
|
|
181
147
|
}
|
|
@@ -103,8 +103,6 @@ export const BUNDLE_RUNTIME_MODULE_LOADERS: Record<string, () => Promise<unknown
|
|
|
103
103
|
// the editor share one live module namespace.
|
|
104
104
|
'@volter/threejs-runtime/adapter/constraint': () =>
|
|
105
105
|
import('@volter/threejs-runtime/adapter/constraint'),
|
|
106
|
-
'@volter/game-runtime/adapter/first-party-audio-system': () =>
|
|
107
|
-
import('@volter/game-runtime/adapter/first-party-audio-system'),
|
|
108
106
|
'@volter/threejs-runtime/adapter/first-party-navigation-system': () =>
|
|
109
107
|
import('@volter/threejs-runtime/adapter/first-party-navigation-system'),
|
|
110
108
|
'@volter/threejs-runtime/adapter/hierarchy-marks': () =>
|
|
@@ -126,17 +124,7 @@ export const BUNDLE_RUNTIME_MODULE_LOADERS: Record<string, () => Promise<unknown
|
|
|
126
124
|
}),
|
|
127
125
|
'@volter/game-runtime/data/curve': () => import('@volter/game-runtime/data/curve'),
|
|
128
126
|
'@volter/game-runtime/data/data-asset': () => import('@volter/game-runtime/data/data-asset'),
|
|
129
|
-
'@volter/game-runtime/dev/instruments': () => import('@volter/game-runtime/dev/instruments'),
|
|
130
127
|
'@volter/threejs-runtime/loader': () => import('@volter/threejs-runtime/loader'),
|
|
131
|
-
'@volter/game-runtime/react/world-state': () => import('@volter/game-runtime/react/world-state'),
|
|
132
|
-
'@volter/game-runtime/runtime/debug-registry': () =>
|
|
133
|
-
import('@volter/game-runtime/runtime/debug-registry'),
|
|
134
|
-
'@volter/game-runtime/runtime/game': () => import('@volter/game-runtime/runtime/game'),
|
|
135
|
-
'@volter/game-runtime/world3d-react': () => import('@volter/game-runtime/world3d-react'),
|
|
136
|
-
'@volter/game-runtime/world3d-react/rapier-physics-bridge': () =>
|
|
137
|
-
import('@volter/game-runtime/world3d-react/rapier-physics-bridge'),
|
|
138
|
-
// …and the factory that mounts an example's default-exported world. A
|
|
139
|
-
// three root cannot mount hosted without it.
|
|
140
128
|
// The mesh-kit's two library-backed modifiers. `src/lib/mesh/modifiers.ts` —
|
|
141
129
|
// scaffolded into EVERY default project by the humanoid capability, and
|
|
142
130
|
// present verbatim in the template and four examples — imports both at
|
|
@@ -165,17 +153,11 @@ export const BUNDLE_RUNTIME_MODULE_LOADERS: Record<string, () => Promise<unknown
|
|
|
165
153
|
'@volter/threejs-runtime/setup/setup-renderer': () =>
|
|
166
154
|
import('@volter/threejs-runtime/setup/setup-renderer'),
|
|
167
155
|
// littlest-tokyo (GLTF quick-start) extras:
|
|
168
|
-
'@volter/game-runtime/input/rebind-controller': () =>
|
|
169
|
-
import('@volter/game-runtime/input/rebind-controller'),
|
|
170
156
|
// The generic fetched-asset parse error — the template's and
|
|
171
157
|
// top-down-strategy's `runtime/pixi-adapter.ts` raise it, so a canvas root
|
|
172
158
|
// cannot mount hosted without it registered.
|
|
173
159
|
'@volter/threejs-runtime/asset-parse-error': () =>
|
|
174
160
|
import('@volter/threejs-runtime/asset-parse-error'),
|
|
175
|
-
// rendering-scale's render lab drives the engine's batch renderer + scoped
|
|
176
|
-
// render settings directly:
|
|
177
|
-
'@volter/threejs-runtime/render/render-batch-system': () =>
|
|
178
|
-
import('@volter/threejs-runtime/render/render-batch-system'),
|
|
179
161
|
'@volter/threejs-runtime/render/render-settings': () =>
|
|
180
162
|
import('@volter/threejs-runtime/render/render-settings'),
|
|
181
163
|
// The bundle inlines @colyseus/schema (modular-action's rooms), and the
|
|
@@ -189,7 +171,7 @@ export const BUNDLE_RUNTIME_MODULE_LOADERS: Record<string, () => Promise<unknown
|
|
|
189
171
|
// capabilities. Each entry names its importer, so a removal is checked
|
|
190
172
|
// against the same sweep.
|
|
191
173
|
'@colyseus/sdk': () => import('@colyseus/sdk'), // template main.ts
|
|
192
|
-
'react-dom': () => import('react-dom'), //
|
|
174
|
+
'react-dom': () => import('react-dom'), // template main.ts
|
|
193
175
|
// ---- Editor-citizen modules PROJECT TOOL CONTRIBUTIONS import. A
|
|
194
176
|
// contribution is project source rendered inside the editor's own panels,
|
|
195
177
|
// so these must resolve to the editor's LIVE modules — a second copy of the
|
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
* endpoint and the console is this shell-side bridge, mounted once by the
|
|
7
7
|
* session's viewport panel.
|
|
8
8
|
*/
|
|
9
|
-
import { getServerValidationLog } from '@volter/editor-
|
|
9
|
+
import { getServerValidationLog } from '@volter/editor-sdk/kit/api/project-state';
|
|
10
10
|
import { emitServerLogEntry } from '@volter/editor-sdk/kit/editor-console';
|
|
11
|
-
import { connectEvents } from '@volter/editor-
|
|
11
|
+
import { connectEvents } from '@volter/editor-sdk/kit/editor-presence';
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* Connect to the editor SSE endpoint and pipe `server-log` events
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* the owner has retired AND no borrow is outstanding.
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
-
import { disposeStoryObject3D, type MountedStoryObject3D } from '
|
|
19
|
+
import { disposeStoryObject3D, type MountedStoryObject3D } from './story-three-preview';
|
|
20
20
|
|
|
21
21
|
export interface MountedStoryViewportSource {
|
|
22
22
|
readonly build: () => {
|