@volter/editor-game 0.5.65
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/LICENSE +661 -0
- package/NOTICE +23 -0
- package/contributions/asset-budget-asset.menu.ts +26 -0
- package/contributions/asset-budget.action.ts +18 -0
- package/contributions/asset-budget.document.tsx +21 -0
- package/contributions/asset-budget.menu.ts +22 -0
- package/contributions/audio-unlock.service.ts +17 -0
- package/contributions/audio.utility.tsx +15 -0
- package/contributions/autoplay.service.ts +48 -0
- package/contributions/bridge.command.ts +172 -0
- package/contributions/build-profiles.document.tsx +21 -0
- package/contributions/build-progress.status.tsx +45 -0
- package/contributions/build.action.ts +26 -0
- package/contributions/build.command.ts +27 -0
- package/contributions/build.header.tsx +37 -0
- package/contributions/build.menu.ts +31 -0
- package/contributions/build.service.ts +31 -0
- package/contributions/connection.status.tsx +43 -0
- package/contributions/coverage.service.ts +118 -0
- package/contributions/edit-mode-audio.service.ts +23 -0
- package/contributions/edit-mode-networking.service.ts +30 -0
- package/contributions/game-document.service.ts +25 -0
- package/contributions/game-eval.command.ts +210 -0
- package/contributions/game.layout.ts +19 -0
- package/contributions/gameplay.command.ts +255 -0
- package/contributions/generation.service.ts +96 -0
- package/contributions/generations.status.tsx +53 -0
- package/contributions/ingest.service.ts +80 -0
- package/contributions/instances.command.ts +75 -0
- package/contributions/navmesh.menu.ts +39 -0
- package/contributions/navmesh.service.ts +19 -0
- package/contributions/network.utility.tsx +17 -0
- package/contributions/play.command.ts +355 -0
- package/contributions/profiler.action.ts +16 -0
- package/contributions/profiler.menu.ts +22 -0
- package/contributions/profiler.utility.tsx +17 -0
- package/contributions/react/component-board.service.ts +21 -0
- package/contributions/react/design-time-mount.service.ts +60 -0
- package/contributions/react/pasteboard.action.ts +41 -0
- package/contributions/react/react-inspector.service.ts +85 -0
- package/contributions/react/story-documents.service.ts +44 -0
- package/contributions/scene-document.service.ts +32 -0
- package/contributions/state-watch.action.ts +17 -0
- package/contributions/state-watch.menu.ts +23 -0
- package/contributions/state-watch.utility.tsx +20 -0
- package/contributions/team-playtest.service.ts +124 -0
- package/contributions/three/camera-runtime.inspector.tsx +34 -0
- package/contributions/three/component-board.service.ts +24 -0
- package/contributions/three/component-verbs.command.ts +110 -0
- package/contributions/three/component-verbs.service.ts +92 -0
- package/contributions/three/constraints.inspector.tsx +34 -0
- package/contributions/three/model-asset-sections.service.ts +26 -0
- package/contributions/three/reflection-probe-capture.inspector.tsx +32 -0
- package/contributions/three/story-documents.service.ts +31 -0
- package/contributions/three/three-authoring.service.ts +66 -0
- package/contributions/transport.header.tsx +19 -0
- package/contributions/xstate-behavior.action.ts +42 -0
- package/contributions/xstate-behavior.document.tsx +73 -0
- package/contributions/xstate-behavior.inspector.tsx +28 -0
- package/contributions/xstate-behavior.menu.ts +23 -0
- package/package.json +144 -0
- package/src/asset-budget/AssetBudgetPanel.tsx +1172 -0
- package/src/asset-budget/asset-budget-model.ts +799 -0
- package/src/asset-budget/basis-encoder.ts +165 -0
- package/src/asset-budget/gltf-io.ts +154 -0
- package/src/asset-budget/gltf-optimize.ts +384 -0
- package/src/asset-budget/image-dims.ts +78 -0
- package/src/asset-budget/optimize-apply.ts +221 -0
- package/src/audio/AudioDebuggerPanel.tsx +457 -0
- package/src/audio/audio-debugger-model.ts +87 -0
- package/src/bridge/call.ts +60 -0
- package/src/bridge/dispatch.ts +459 -0
- package/src/bridge/live-frames.ts +25 -0
- package/src/bridge/screenshot.ts +316 -0
- package/src/build/BuildProfilesPanel.tsx +476 -0
- package/src/build/build-session.ts +247 -0
- package/src/build/format-bytes.ts +14 -0
- package/src/command-results.ts +36 -0
- package/src/coverage/live-authoring-surface.ts +30 -0
- package/src/coverage/live-project-verbs.ts +162 -0
- package/src/coverage/native-system-coverage.ts +143 -0
- package/src/coverage/root-coverage.ts +79 -0
- package/src/coverage/session-coverage.ts +193 -0
- package/src/design-system-stories/ApplicationChrome.stories.tsx +100 -0
- package/src/design-system-stories/InspectorNarrowBodies.stories.tsx +406 -0
- package/src/edit-mode/edit-mode-audio.ts +56 -0
- package/src/edit-mode/edit-mode-networking.ts +112 -0
- package/src/game-document/DevicePresetPicker.tsx +84 -0
- package/src/game-document/GameCaptureFrameButton.tsx +45 -0
- package/src/game-document/GameDocument.tsx +215 -0
- package/src/game-document/GamePanel.tsx +662 -0
- package/src/game-document/InstanceInspectorPicker.tsx +183 -0
- package/src/game-document/crowd-debug.ts +183 -0
- package/src/game-document/device-preview.ts +336 -0
- package/src/game-document/game-view-store.ts +107 -0
- package/src/game-document/physics-debug.ts +187 -0
- package/src/generation/GenerationActivity.tsx +421 -0
- package/src/generation/GenerationGallery.css +231 -0
- package/src/generation/generation-documents.tsx +338 -0
- package/src/generation/generation-jobs.ts +128 -0
- package/src/generation/generation-presentation.ts +257 -0
- package/src/host/adapter-reach.ts +445 -0
- package/src/host/adapter-runtime-bindings.ts +303 -0
- package/src/host/after-paint.ts +112 -0
- package/src/host/api/configurations.ts +66 -0
- package/src/host/authoring/babylon-authoring-adapter.ts +703 -0
- package/src/host/authoring/canvas-runtime-recognition.ts +50 -0
- package/src/host/authoring/contract-hierarchy-authoring.ts +144 -0
- package/src/host/authoring/contract-scenes-stories.ts +204 -0
- package/src/host/authoring/creation-site-related.ts +55 -0
- package/src/host/authoring/ephemeral-persistence.ts +29 -0
- package/src/host/authoring/gesture-persist.ts +84 -0
- package/src/host/authoring/ingest-data-writer.ts +232 -0
- package/src/host/authoring/ingest-source-persistence.ts +826 -0
- package/src/host/authoring/mount-isolated-pixi-screen.ts +250 -0
- package/src/host/authoring/mounted-authoring.ts +41 -0
- package/src/host/authoring/owned-pixi-ticker-listeners.ts +96 -0
- package/src/host/authoring/phaser-live-authoring-adapter.ts +265 -0
- package/src/host/authoring/pixi-authoring-adapter.ts +1554 -0
- package/src/host/authoring/pixi-creation-site-write-target.ts +60 -0
- package/src/host/authoring/pixi-isolation-assets.ts +25 -0
- package/src/host/authoring/pixi-live-write-target.ts +979 -0
- package/src/host/authoring/pixi-source-identity.ts +141 -0
- package/src/host/authoring/pixi-still-presentation.ts +71 -0
- package/src/host/authoring/pixi-structure-history.ts +237 -0
- package/src/host/authoring/pixi-transform-channels.ts +205 -0
- package/src/host/authoring/selection-remount-handoff.ts +23 -0
- package/src/host/authoring/source-persistence-backend.ts +373 -0
- package/src/host/authoring/source-refresh-revisions.ts +81 -0
- package/src/host/authoring/struct-write-pipe.ts +143 -0
- package/src/host/auto-frame-window.ts +89 -0
- package/src/host/binding-resolver.ts +393 -0
- package/src/host/browser-transpile.ts +631 -0
- package/src/host/canvas-entry-runtime.ts +95 -0
- package/src/host/components/CameraAuthoringOverlay.tsx +216 -0
- package/src/host/components/HeaderTelemetry.tsx +341 -0
- package/src/host/components/PixiIsolationSceneContent.tsx +280 -0
- package/src/host/components/ResolutionPicker.tsx +89 -0
- package/src/host/components/ThreeIsolationSceneContent.tsx +180 -0
- package/src/host/components/frame-debugger-model.ts +579 -0
- package/src/host/components/header-telemetry-model.ts +74 -0
- package/src/host/components/scene-document.tsx +447 -0
- package/src/host/components/utility-view-state.ts +87 -0
- package/src/host/components/world-root-stage-binding.tsx +133 -0
- package/src/host/components/world-root-stage.ts +1046 -0
- package/src/host/coverage/authoring-read-probe.ts +583 -0
- package/src/host/coverage/capability-coverage.ts +1587 -0
- package/src/host/coverage/coverage-accounting.ts +261 -0
- package/src/host/coverage/game-contract-seam-evidence.ts +82 -0
- package/src/host/coverage/project-verb-coverage.ts +148 -0
- package/src/host/coverage/system-adapter-coverage.ts +373 -0
- package/src/host/design-system-stories/StoryLayout.tsx +104 -0
- package/src/host/design-system-stories/fixtures/authoring.ts +247 -0
- package/src/host/design-system-stories/fixtures/editor-runtime.tsx +115 -0
- package/src/host/document-preview-three.ts +110 -0
- package/src/host/entry-adjudication.ts +89 -0
- package/src/host/game-location-guard.ts +150 -0
- package/src/host/game-module-access.ts +196 -0
- package/src/host/game-realm-page.ts +358 -0
- package/src/host/game-realm-reclaim.ts +55 -0
- package/src/host/game-realm-storage.ts +104 -0
- package/src/host/gameplay-export.ts +288 -0
- package/src/host/gameplay-recording.ts +717 -0
- package/src/host/gated-globals.ts +1511 -0
- package/src/host/history/json-history-resource.ts +254 -0
- package/src/host/ingest/registry.ts +261 -0
- package/src/host/instance-extract-actions.ts +120 -0
- package/src/host/instance-fork-actions.ts +120 -0
- package/src/host/play-control-hook.ts +26 -0
- package/src/host/playwright-shim.ts +479 -0
- package/src/host/projection/dom.ts +295 -0
- package/src/host/projection/pixi.ts +288 -0
- package/src/host/r3f-entry-runtime.ts +77 -0
- package/src/host/react-mount-runtime.ts +162 -0
- package/src/host/realm-services.ts +148 -0
- package/src/host/recording-preview.ts +106 -0
- package/src/host/roots/module-root.ts +203 -0
- package/src/host/roots/react-root.ts +181 -0
- package/src/host/same-realm-loop-gate.ts +544 -0
- package/src/host/scene-view-drawability.ts +69 -0
- package/src/host/sdk/tools.ts +31 -0
- package/src/host/served-bundle-runtime-modules.ts +331 -0
- package/src/host/server-log-bridge.ts +60 -0
- package/src/host/staged-projects.ts +24 -0
- package/src/host/stories/mounted-story-viewport-source.ts +64 -0
- package/src/host/stories/story-arg-descriptors.ts +50 -0
- package/src/host/stories/story-media-presence.ts +152 -0
- package/src/host/surface-content.ts +87 -0
- package/src/host/take-named-export.ts +23 -0
- package/src/host/three-ingest-runtime.ts +76 -0
- package/src/host/types-fastnoise-lite.d.ts +7 -0
- package/src/host/types-mikktspace.d.ts +20 -0
- package/src/host/types-troika-three-text.d.ts +7 -0
- package/src/host/use-active-performance-source.ts +53 -0
- package/src/host/viewport-pose-memory.ts +48 -0
- package/src/host/viewport-root-presentation.ts +40 -0
- package/src/ingest/active-ingest.ts +251 -0
- package/src/ingest/active-scene-navigation.ts +43 -0
- package/src/ingest/authoring/ingest-dom-surface-authoring.ts +191 -0
- package/src/ingest/authoring/ingest-root-adapter.ts +897 -0
- package/src/ingest/capture-wait-report.ts +122 -0
- package/src/ingest/deferred-ingest-play.ts +216 -0
- package/src/ingest/deferred-ingest-session.ts +23 -0
- package/src/ingest/discovery-public-ingest.ts +242 -0
- package/src/ingest/dom-stub-mark.ts +7 -0
- package/src/ingest/entry-load.ts +56 -0
- package/src/ingest/game-contract-realm.ts +34 -0
- package/src/ingest/game-pointer-lock.ts +89 -0
- package/src/ingest/held-scene-repaint.ts +73 -0
- package/src/ingest/host-surface-box.ts +174 -0
- package/src/ingest/ingest-boot-viewport.ts +54 -0
- package/src/ingest/ingest-canvas-scene-document.tsx +177 -0
- package/src/ingest/ingest-canvas-scene.ts +50 -0
- package/src/ingest/ingest-evidence-hook.ts +131 -0
- package/src/ingest/ingest-frame-snapshot.ts +183 -0
- package/src/ingest/ingest-play-commands.ts +134 -0
- package/src/ingest/ingest-play-control.ts +299 -0
- package/src/ingest/ingest-render-debug.ts +320 -0
- package/src/ingest/ingest-siblings.ts +487 -0
- package/src/ingest/ingest-status.ts +62 -0
- package/src/ingest/live-ingest-facet.ts +46 -0
- package/src/ingest/module-mode.ts +229 -0
- package/src/ingest/mount-canvas-ingest-root.ts +899 -0
- package/src/ingest/mount-coverage.ts +296 -0
- package/src/ingest/mount-dom-ingest-root.ts +282 -0
- package/src/ingest/mount-ingest-root.ts +744 -0
- package/src/ingest/mount-three-ingest-root.ts +366 -0
- package/src/ingest/resolve-canvas.ts +47 -0
- package/src/ingest/resolve-three.ts +123 -0
- package/src/ingest/served-bundle.ts +109 -0
- package/src/ingest/served-html-boot.ts +292 -0
- package/src/ingest/surface-canvas.ts +84 -0
- package/src/ingest/surface-dom.ts +84 -0
- package/src/ingest/surface-three.ts +128 -0
- package/src/ingest/types.ts +76 -0
- package/src/ingest/unmount-ingest-root.ts +224 -0
- package/src/navmesh/navmesh-actions.ts +27 -0
- package/src/navmesh/navmesh-handler.ts +237 -0
- package/src/navmesh/navmesh-workflow-store.ts +78 -0
- package/src/network/NetworkInspectorPanel.tsx +644 -0
- package/src/network/network-inspector-model.ts +225 -0
- package/src/play/play-boot-stall.ts +118 -0
- package/src/play/play-log-events.ts +26 -0
- package/src/play/play-mode.ts +2553 -0
- package/src/play/play-recording.ts +335 -0
- package/src/play/react-play-live-authoring.ts +165 -0
- package/src/play-bar/PlayBar.tsx +488 -0
- package/src/play-bar/PlayerCountPicker.tsx +100 -0
- package/src/profiler/FrameDebuggerPanel.tsx +458 -0
- package/src/profiler/PerformancePanel.tsx +1068 -0
- package/src/profiler/ProfilerPanel.tsx +53 -0
- package/src/profiler/frame-debugger-store.ts +97 -0
- package/src/profiler/main-thread-busy.ts +90 -0
- package/src/react/design-time-react-mount.ts +782 -0
- package/src/react/dom-authoring-adapter.ts +764 -0
- package/src/react/pasteboard-materialize.ts +154 -0
- package/src/react/react-inspector-section.tsx +2496 -0
- package/src/react/react-world-authoring-adapter.ts +3795 -0
- package/src/react/story-documents/story-args-section.ts +19 -0
- package/src/react/story-documents/story-documents.tsx +1380 -0
- package/src/react/story-paint-bounds.ts +94 -0
- package/src/react/ui-board-document.tsx +101 -0
- package/src/react/ui-board-title.ts +9 -0
- package/src/react/ui-component-board.ts +72 -0
- package/src/services/audio-pose-guard.ts +81 -0
- package/src/services/game-audio-unlock.ts +48 -0
- package/src/state-watch/StateWatchPanel.tsx +535 -0
- package/src/three/authoring/camera-runtime-inspector-section.tsx +135 -0
- package/src/three/authoring/constraint-inspector-section.tsx +189 -0
- package/src/three/authoring/design-time-renderer.ts +189 -0
- package/src/three/authoring/model-asset-inspector-section.css +41 -0
- package/src/three/authoring/model-asset-inspector-section.tsx +869 -0
- package/src/three/authoring/oid-source-persistence.ts +557 -0
- package/src/three/authoring/r3f-design-session.ts +1174 -0
- package/src/three/authoring/r3f-source-authoring-adapter.ts +5949 -0
- package/src/three/authoring/reflection-probe-inspector-section.tsx +76 -0
- package/src/three/authoring/spatial-audio-handles.ts +215 -0
- package/src/three/authoring/spatial-collider-handles.ts +229 -0
- package/src/three/authoring/spatial-joint-handles.ts +114 -0
- package/src/three/authoring/spatial-light-handles.ts +178 -0
- package/src/three/authoring/spatial-lod-handles.ts +93 -0
- package/src/three/authoring/spatial-particle-handles.ts +368 -0
- package/src/three/authoring/three-authoring-adapter.ts +1953 -0
- package/src/three/authoring/three-scene-identity.ts +19 -0
- package/src/three/authoring/three-spatial-handles.ts +161 -0
- package/src/three/authoring/typed-three-inspector.ts +528 -0
- package/src/three/component-verbs/extract-menu.ts +71 -0
- package/src/three/component-verbs/fork-menu.ts +78 -0
- package/src/three/component-verbs/internals-menu.ts +91 -0
- package/src/three/story-documents/three-story-documents.tsx +607 -0
- package/src/three/three-board/ThreeBoardDocument.tsx +888 -0
- package/src/three/three-board/board-framing.ts +412 -0
- package/src/three/three-board/board-layout.ts +401 -0
- package/src/three/three-board/board-scene.ts +901 -0
- package/src/three/three-board/three-component-board.ts +62 -0
- package/src/xstate/XStateBehaviorSection.tsx +130 -0
- package/src/xstate/XStateMachineInspector.tsx +566 -0
- package/src/xstate/character-animation-machine.fixture.ts +74 -0
- package/src/xstate/live-behaviors.ts +106 -0
- package/src/xstate/use-live-actor-state.ts +44 -0
- package/src/xstate/xstate-graph.ts +235 -0
- package/src/xstate/xstate-layout.ts +76 -0
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The react-world mount's OWN react — the sixth realm doorway, finally living
|
|
3
|
+
* beside its five siblings (`r3f-entry-runtime`, `canvas-entry-runtime`,
|
|
4
|
+
* `three-ingest-runtime`, `story-dom-runtime`, `story-three-preview-runtime`)
|
|
5
|
+
* instead of inside the root resolver.
|
|
6
|
+
*
|
|
7
|
+
* Every one of those modules answers the same question for its own subject:
|
|
8
|
+
* *whose copy of this library do we build with?* Under dev the answer
|
|
9
|
+
* is always the editor's own static imports — one shared Vite instance plus the
|
|
10
|
+
* root `vite.config.ts`'s `resolve.dedupe` already collapse the editor's and
|
|
11
|
+
* the project's react onto one instance. Under the PACKAGED runtime the editor
|
|
12
|
+
* shell is a prebuilt static bundle with its own baked-in `react`/`react-dom`,
|
|
13
|
+
* disconnected from the separate project-rooted Vite instance the entry
|
|
14
|
+
* component's hooks resolve `react` through — mounting with the shell's copy
|
|
15
|
+
* produces two react instances and `useContext` throws on a `null` dispatcher
|
|
16
|
+
* (confirmed live; see `packaged.ts`'s header).
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { WorldProvider as EngineWorldProvider } from '@volter/game-runtime/react/world-state';
|
|
20
|
+
import type { Game } from '@volter/game-runtime/runtime/game';
|
|
21
|
+
import {
|
|
22
|
+
type ComponentType,
|
|
23
|
+
createElement,
|
|
24
|
+
useEffect as reactUseEffect,
|
|
25
|
+
useRef as reactUseRef,
|
|
26
|
+
} from 'react';
|
|
27
|
+
import { flushSync } from 'react-dom';
|
|
28
|
+
import { createRoot } from 'react-dom/client';
|
|
29
|
+
import { REACT_WORLD_RUNTIME_PATH } from '@volter/editor-sdk/host';
|
|
30
|
+
import { isPackagedRuntime } from '@volter/editor-core/packaged-runtime';
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* The pieces of "react itself" a react-world mount needs:
|
|
34
|
+
* `createElement`/`createRoot` (to build + render the `<WorldProvider><Entry/>
|
|
35
|
+
* </WorldProvider>` tree) and the engine-published `WorldProvider`.
|
|
36
|
+
*/
|
|
37
|
+
export interface ReactRootMountRuntime {
|
|
38
|
+
createElement: typeof createElement;
|
|
39
|
+
createRoot: typeof createRoot;
|
|
40
|
+
useEffect: typeof reactUseEffect;
|
|
41
|
+
useRef: typeof reactUseRef;
|
|
42
|
+
/** `react-dom`'s synchronous-flush escape hatch — the design-time react
|
|
43
|
+
* layer mount needs it from the SAME react-dom peer as `createRoot` above
|
|
44
|
+
* (both are entry points of the same installed package). */
|
|
45
|
+
flushSync: typeof flushSync;
|
|
46
|
+
engineWorldProvider: ComponentType<{ game: Game; children?: unknown }>;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** The dev default: this module's own static imports. */
|
|
50
|
+
function staticReactRootMountRuntime(): ReactRootMountRuntime {
|
|
51
|
+
return {
|
|
52
|
+
createElement,
|
|
53
|
+
createRoot,
|
|
54
|
+
useEffect: reactUseEffect,
|
|
55
|
+
useRef: reactUseRef,
|
|
56
|
+
flushSync,
|
|
57
|
+
engineWorldProvider: EngineWorldProvider as unknown as ComponentType<{
|
|
58
|
+
game: Game;
|
|
59
|
+
children?: unknown;
|
|
60
|
+
}>,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Import + shape-check the packaged runtime's synthetic
|
|
66
|
+
* `/__vgai-react-world-runtime` module (`vite-plugin-module-doorways.ts`,
|
|
67
|
+
* registered only by `packaged.ts`'s project-rooted Vite instance and by
|
|
68
|
+
* `vitest.config.ts` for test coverage — never by `dev.ts`/the root
|
|
69
|
+
* `vite.config.ts`). A loud, named failure if the module resolves but is
|
|
70
|
+
* malformed — never a silent fall-through that would re-introduce the dual-
|
|
71
|
+
* instance bug it exists to prevent.
|
|
72
|
+
*/
|
|
73
|
+
async function loadPackagedReactRootMountRuntime(): Promise<ReactRootMountRuntime> {
|
|
74
|
+
const mod = (await import(/* @vite-ignore */ REACT_WORLD_RUNTIME_PATH)) as {
|
|
75
|
+
createElement?: unknown;
|
|
76
|
+
createRoot?: unknown;
|
|
77
|
+
useEffect?: unknown;
|
|
78
|
+
useRef?: unknown;
|
|
79
|
+
flushSync?: unknown;
|
|
80
|
+
EngineWorldProvider?: unknown;
|
|
81
|
+
};
|
|
82
|
+
if (
|
|
83
|
+
typeof mod.createElement !== 'function' ||
|
|
84
|
+
typeof mod.createRoot !== 'function' ||
|
|
85
|
+
typeof mod.useEffect !== 'function' ||
|
|
86
|
+
typeof mod.useRef !== 'function' ||
|
|
87
|
+
typeof mod.flushSync !== 'function' ||
|
|
88
|
+
typeof mod.EngineWorldProvider !== 'function'
|
|
89
|
+
) {
|
|
90
|
+
throw new Error(
|
|
91
|
+
"The packaged runtime's synthetic react-world-runtime module " +
|
|
92
|
+
`("${REACT_WORLD_RUNTIME_PATH}") did not export createElement/createRoot/useEffect/useRef/flushSync/` +
|
|
93
|
+
'EngineWorldProvider as functions — see vite-plugin-module-doorways.ts.',
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
return {
|
|
97
|
+
createElement: mod.createElement as typeof createElement,
|
|
98
|
+
createRoot: mod.createRoot as typeof createRoot,
|
|
99
|
+
useEffect: mod.useEffect as typeof reactUseEffect,
|
|
100
|
+
useRef: mod.useRef as typeof reactUseRef,
|
|
101
|
+
flushSync: mod.flushSync as typeof flushSync,
|
|
102
|
+
engineWorldProvider: mod.EngineWorldProvider as ComponentType<{
|
|
103
|
+
game: Game;
|
|
104
|
+
children?: unknown;
|
|
105
|
+
}>,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
let cachedPackagedReactRootMountRuntime: Promise<ReactRootMountRuntime> | null = null;
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Resolve the react-world mount's own react. A react world is an ISOLATED
|
|
113
|
+
* `createRoot` (its own DOM subtree / react tree) that does NOT need to share
|
|
114
|
+
* react with the editor's own UI — it only needs to be internally consistent
|
|
115
|
+
* (the wrapper + `WorldProvider` + the project's entry component all sharing
|
|
116
|
+
* ONE react instance), which is exactly what the packaged branch restores.
|
|
117
|
+
*
|
|
118
|
+
* Memoized twice over: `isPackagedRuntime()` is itself session-memoized, and
|
|
119
|
+
* the packaged module load is cached here, so this performs at most one
|
|
120
|
+
* `/__editor/project` fetch and one dynamic import per editor session however
|
|
121
|
+
* many react roots mount.
|
|
122
|
+
*/
|
|
123
|
+
export async function resolveReactRootMountRuntime(): Promise<ReactRootMountRuntime> {
|
|
124
|
+
const packaged = await isPackagedRuntime();
|
|
125
|
+
if (!packaged) return staticReactRootMountRuntime();
|
|
126
|
+
if (!cachedPackagedReactRootMountRuntime) {
|
|
127
|
+
cachedPackagedReactRootMountRuntime = loadPackagedReactRootMountRuntime();
|
|
128
|
+
}
|
|
129
|
+
return cachedPackagedReactRootMountRuntime;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* `WorldProvider` is the canonical engine export from
|
|
134
|
+
* `@volter/game-runtime/react/world-state`, reached through the mount runtime above so
|
|
135
|
+
* the project's copy is used under the packaged runtime. Hosts and game
|
|
136
|
+
* entries therefore share ONE module and one React context identity.
|
|
137
|
+
*
|
|
138
|
+
* WHY THIS IS AN ENGINE EXPORT AND NOT A PER-PROJECT FILE — a finding verified
|
|
139
|
+
* empirically, kept because the trap it describes is still live for anyone
|
|
140
|
+
* tempted to reintroduce a project-local provider. Back when each project
|
|
141
|
+
* owned its own bridge module, a hand-built runtime `import('/@fs/' +
|
|
142
|
+
* projectRoot + '/…')` for it did NOT reliably land on the same module
|
|
143
|
+
* instance a react-world entry's own STATIC relative import resolved to:
|
|
144
|
+
* Vite's dev server treated the two differently at the HTTP level (a
|
|
145
|
+
* query-string divergence, confirmed by logging `import.meta.url` inside two
|
|
146
|
+
* otherwise-identical fetches) even though both targeted the identical file on
|
|
147
|
+
* disk, and browser ES module identity is keyed by the exact request URL. That
|
|
148
|
+
* alone produced TWO separate `createContext()` calls. The whole apparatus is
|
|
149
|
+
* gone: one engine module, no per-project path to diverge on.
|
|
150
|
+
*/
|
|
151
|
+
export async function resolveWorldProviderForProject(): Promise<
|
|
152
|
+
ComponentType<{ game: Game; children?: unknown }>
|
|
153
|
+
> {
|
|
154
|
+
const runtime = await resolveReactRootMountRuntime();
|
|
155
|
+
return runtime.engineWorldProvider;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** Test-only: reset the packaged react-world mount runtime cache between
|
|
159
|
+
* tests that toggle `isPackagedRuntime()`'s own (separately memoized) result. */
|
|
160
|
+
export function resetReactRootMountRuntimeCacheForTest(): void {
|
|
161
|
+
cachedPackagedReactRootMountRuntime = null;
|
|
162
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THE REALM — where this editor session's project modules come from, as ONE
|
|
3
|
+
* value instead of a three-way branch repeated in every root resolver.
|
|
4
|
+
*
|
|
5
|
+
* ## The fork this closes
|
|
6
|
+
*
|
|
7
|
+
* `resolveDefaultThreeAdapter`, `resolveDefaultCanvasAdapter` and
|
|
8
|
+
* `resolveReactAdapterRootComponent` each carried their OWN copy of the same
|
|
9
|
+
* question, and the copies had already drifted. There is one dispatch now, and
|
|
10
|
+
* it is {@link RealmServices.loadEntryModule}.
|
|
11
|
+
*
|
|
12
|
+
* ## ONE LOADING LANE, TWO RUNTIMES
|
|
13
|
+
*
|
|
14
|
+
* A project module is fetched ONE way — `/@fs/` through the session's own dev
|
|
15
|
+
* server — because the session IS the editor's host. What varies is the
|
|
16
|
+
* RUNTIME: whose copy of react/fiber/pixi/three the mount builds with, the
|
|
17
|
+
* editor SHELL's or the PROJECT's own module graph.
|
|
18
|
+
*
|
|
19
|
+
* dev = fs × shell
|
|
20
|
+
* packaged = fs × project graph
|
|
21
|
+
*
|
|
22
|
+
* ## What is here, and what is deliberately NOT
|
|
23
|
+
*
|
|
24
|
+
* The RUNTIME axis is already single-owner per subject — `r3f-entry-runtime`,
|
|
25
|
+
* `canvas-entry-runtime`, `react-mount-runtime`, `three-ingest-runtime` each
|
|
26
|
+
* decide it once, for everyone, from `isPackagedRuntime()`. This module names
|
|
27
|
+
* the ones root binding actually opens so a resolver asks the realm rather
|
|
28
|
+
* than importing four doorways by hand; it does not re-implement them, and it
|
|
29
|
+
* does not list the two STORY doorways (`story-dom-runtime`,
|
|
30
|
+
* `story-three-preview-runtime`) — their consumers are story/preview surfaces,
|
|
31
|
+
* not root binding, so a member here would have no caller.
|
|
32
|
+
*
|
|
33
|
+
* Ingest's served-bundle-vs-`/@fs/` choice is likewise NOT here: it is a
|
|
34
|
+
* per-PROJECT property (how that one game was vendored), so two ingested games
|
|
35
|
+
* open in the same realm can answer differently. It lives with the ingest code
|
|
36
|
+
* (`ingest/resolve-three.ts`).
|
|
37
|
+
*
|
|
38
|
+
* ## Reject, never guess
|
|
39
|
+
*
|
|
40
|
+
* Construction AWAITS `isPackagedRuntime()`, so a probe that could not get an
|
|
41
|
+
* answer REJECTS through this function to the mount site. Nothing here spends
|
|
42
|
+
* "I don't know" as "dev" — see `packaged-runtime.ts`'s header for the
|
|
43
|
+
* measured session-long mis-mount that latch caused.
|
|
44
|
+
*/
|
|
45
|
+
|
|
46
|
+
import {
|
|
47
|
+
type EntrypointSelectionOverride,
|
|
48
|
+
type ProjectMountEpoch,
|
|
49
|
+
projectEntryImportUrl,
|
|
50
|
+
} from '@volter/editor-sdk/session/project-module-url';
|
|
51
|
+
import type { AdapterSurface } from '@volter/editor-project/adapter/adapter-surface';
|
|
52
|
+
import { resolveCanvasEntryAdapterForEditor } from './canvas-entry-runtime';
|
|
53
|
+
import { isPackagedRuntime } from '@volter/editor-core/packaged-runtime';
|
|
54
|
+
import { type ReactRootMountRuntime, resolveReactRootMountRuntime } from './react-mount-runtime';
|
|
55
|
+
import {
|
|
56
|
+
resolveThreeIngestRuntimeForEditor,
|
|
57
|
+
type ThreeIngestRuntime,
|
|
58
|
+
} from './three-ingest-runtime';
|
|
59
|
+
|
|
60
|
+
/** Which of the two this session is in. Reported, never inferred twice. */
|
|
61
|
+
export type RealmId = 'dev' | 'packaged';
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* The realm's services, bound to ONE project root and ONE mount epoch — a
|
|
65
|
+
* realm value is "how THIS project's modules are reached in THIS mount
|
|
66
|
+
* generation", which is why it is constructed per resolve rather than being a
|
|
67
|
+
* module-level singleton.
|
|
68
|
+
*/
|
|
69
|
+
export interface RealmServices {
|
|
70
|
+
readonly id: RealmId;
|
|
71
|
+
/** The project this realm was constructed for. */
|
|
72
|
+
readonly projectRoot: string;
|
|
73
|
+
/** The mount generation every module url of this composition shares. */
|
|
74
|
+
readonly epoch: ProjectMountEpoch;
|
|
75
|
+
/**
|
|
76
|
+
* Import one root's entry module.
|
|
77
|
+
*
|
|
78
|
+
* `rootId` and `surface` identify the root for the caller's own errors;
|
|
79
|
+
* dispatch is the entry PATH. `selectionOverride` is the dev server's
|
|
80
|
+
* entrypoint swap slot, rewritten at serve time.
|
|
81
|
+
*/
|
|
82
|
+
loadEntryModule(
|
|
83
|
+
entry: string,
|
|
84
|
+
rootId: string,
|
|
85
|
+
surface: AdapterSurface,
|
|
86
|
+
selectionOverride?: EntrypointSelectionOverride | undefined,
|
|
87
|
+
): Promise<Record<string, unknown>>;
|
|
88
|
+
/** The entry namespace this realm hands back is the module's WHOLE
|
|
89
|
+
* namespace — the honest input to `RootDeclaration.entry`'s `reach`
|
|
90
|
+
* discriminant, which kept a second value while a bundle-map lane
|
|
91
|
+
* existed. */
|
|
92
|
+
readonly entryReach: 'full';
|
|
93
|
+
/** The canvas doorway: turn a canvas entry namespace into its adapter. */
|
|
94
|
+
readonly pixiRuntime: typeof resolveCanvasEntryAdapterForEditor;
|
|
95
|
+
/** The react-world mount's own react/react-dom/WorldProvider. */
|
|
96
|
+
readonly reactDomRuntime: () => Promise<ReactRootMountRuntime>;
|
|
97
|
+
/** The three-ingest doorway: the namespace an ingested three game captures
|
|
98
|
+
* against. */
|
|
99
|
+
readonly threeIngestRuntime: () => Promise<ThreeIngestRuntime>;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const SHARED_RUNTIMES = {
|
|
103
|
+
pixiRuntime: resolveCanvasEntryAdapterForEditor,
|
|
104
|
+
reactDomRuntime: resolveReactRootMountRuntime,
|
|
105
|
+
threeIngestRuntime: resolveThreeIngestRuntimeForEditor,
|
|
106
|
+
} as const;
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* dev AND packaged: `/@fs/` through the dev server, always with the mount
|
|
110
|
+
* epoch's cache-buster (`projectEntryImportUrl` — never a bare url; a
|
|
111
|
+
* byte-identical specifier is served out of the browser's module registry on
|
|
112
|
+
* remount while its cache-busted siblings are re-transformed, leaving two live
|
|
113
|
+
* copies of one module-level `let` and no error).
|
|
114
|
+
*/
|
|
115
|
+
function fsLoader(projectRoot: string, epoch: ProjectMountEpoch) {
|
|
116
|
+
return async (
|
|
117
|
+
entry: string,
|
|
118
|
+
_rootId: string,
|
|
119
|
+
_surface: AdapterSurface,
|
|
120
|
+
selectionOverride?: EntrypointSelectionOverride | undefined,
|
|
121
|
+
): Promise<Record<string, unknown>> =>
|
|
122
|
+
(await import(
|
|
123
|
+
/* @vite-ignore */ projectEntryImportUrl(projectRoot, entry, epoch, selectionOverride)
|
|
124
|
+
)) as Record<string, unknown>;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Construct this session's realm for one project and one mount epoch.
|
|
129
|
+
*
|
|
130
|
+
* REJECTS rather than guessing when `isPackagedRuntime()` could not get an
|
|
131
|
+
* answer — see this module's header.
|
|
132
|
+
*/
|
|
133
|
+
export async function activeRealmServices(
|
|
134
|
+
projectRoot: string,
|
|
135
|
+
epoch: ProjectMountEpoch,
|
|
136
|
+
): Promise<RealmServices> {
|
|
137
|
+
// The LOADING axis is `/@fs/` either way; only the runtime doorways differ,
|
|
138
|
+
// and each of those decides for itself from the same probe.
|
|
139
|
+
const packaged = await isPackagedRuntime();
|
|
140
|
+
return {
|
|
141
|
+
id: packaged ? 'packaged' : 'dev',
|
|
142
|
+
projectRoot,
|
|
143
|
+
epoch,
|
|
144
|
+
loadEntryModule: fsLoader(projectRoot, epoch),
|
|
145
|
+
entryReach: 'full',
|
|
146
|
+
...SHARED_RUNTIMES,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/** Two-Hz filmstrip encoding. HTMLCanvasElement.toBlob can synchronously flush
|
|
2
|
+
* GPU work before returning; keeping only its callback async does not protect
|
|
3
|
+
* the game loop. Transfer an ImageBitmap; draw/downscale and encode in a worker.
|
|
4
|
+
* One pending image bounds memory. Stop settles it and closes every owned bitmap.
|
|
5
|
+
* Unsupported workers/canvases report a preview error; never silently fall back
|
|
6
|
+
* to blocking the main thread. The video recorder can continue without previews.
|
|
7
|
+
*/
|
|
8
|
+
export function createRecordingPreviewEncoder() {
|
|
9
|
+
let worker: Worker | null = null;
|
|
10
|
+
let stopped = false;
|
|
11
|
+
let pending: { resolve: (blob: Blob) => void; reject: (error: Error) => void } | null = null;
|
|
12
|
+
const source = `
|
|
13
|
+
let canvas;
|
|
14
|
+
onmessage = async ({data}) => {
|
|
15
|
+
const {bitmap, width, height, type, quality} = data;
|
|
16
|
+
try {
|
|
17
|
+
if (!canvas) canvas = new OffscreenCanvas(width, height);
|
|
18
|
+
if (canvas.width !== width) canvas.width = width;
|
|
19
|
+
if (canvas.height !== height) canvas.height = height;
|
|
20
|
+
const context = canvas.getContext('2d');
|
|
21
|
+
if (!context) throw new Error('No worker canvas context');
|
|
22
|
+
context.globalCompositeOperation = 'copy';
|
|
23
|
+
context.drawImage(bitmap, 0, 0, width, height);
|
|
24
|
+
bitmap.close();
|
|
25
|
+
const blob = await canvas.convertToBlob({type, quality});
|
|
26
|
+
postMessage({blob});
|
|
27
|
+
} catch (error) {
|
|
28
|
+
bitmap.close();
|
|
29
|
+
postMessage({error: error instanceof Error ? error.message : String(error)});
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
`;
|
|
33
|
+
function fail(error: Error) {
|
|
34
|
+
const job = pending;
|
|
35
|
+
pending = null;
|
|
36
|
+
worker?.terminate();
|
|
37
|
+
worker = null;
|
|
38
|
+
job?.reject(error);
|
|
39
|
+
}
|
|
40
|
+
return {
|
|
41
|
+
encode(
|
|
42
|
+
canvas: HTMLCanvasElement,
|
|
43
|
+
width: number,
|
|
44
|
+
height: number,
|
|
45
|
+
type: string,
|
|
46
|
+
quality: number,
|
|
47
|
+
): Promise<Blob> {
|
|
48
|
+
if (stopped) return Promise.reject(new Error('Recording preview encoder stopped'));
|
|
49
|
+
if (pending) return Promise.reject(new Error('Recording preview already in flight'));
|
|
50
|
+
return new Promise<Blob>((resolve, reject) => {
|
|
51
|
+
const job = { resolve, reject };
|
|
52
|
+
pending = job;
|
|
53
|
+
try {
|
|
54
|
+
if (!worker) {
|
|
55
|
+
const url = URL.createObjectURL(new Blob([source], { type: 'text/javascript' }));
|
|
56
|
+
try {
|
|
57
|
+
worker = new Worker(url);
|
|
58
|
+
} finally {
|
|
59
|
+
URL.revokeObjectURL(url);
|
|
60
|
+
}
|
|
61
|
+
worker.onmessage = ({ data }: MessageEvent<{ blob?: Blob; error?: string }>) => {
|
|
62
|
+
if (data.error || !data.blob) {
|
|
63
|
+
fail(new Error(data.error ?? 'No recording preview image'));
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const job = pending;
|
|
67
|
+
pending = null;
|
|
68
|
+
job?.resolve(data.blob);
|
|
69
|
+
};
|
|
70
|
+
worker.onerror = (event) => {
|
|
71
|
+
event.preventDefault();
|
|
72
|
+
fail(new Error(event.message || 'Recording preview worker failed'));
|
|
73
|
+
};
|
|
74
|
+
worker.onmessageerror = () =>
|
|
75
|
+
fail(new Error('Recording preview worker message failed'));
|
|
76
|
+
}
|
|
77
|
+
// Snapshot cost belongs in the main-thread profile too. Do not replace
|
|
78
|
+
// this with toDataURL/getImageData: those force synchronous readback.
|
|
79
|
+
createImageBitmap(canvas).then(
|
|
80
|
+
(bitmap) => {
|
|
81
|
+
if (stopped || pending !== job || !worker) {
|
|
82
|
+
bitmap.close();
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
try {
|
|
86
|
+
worker.postMessage({ bitmap, width, height, type, quality }, [bitmap]);
|
|
87
|
+
} catch (error) {
|
|
88
|
+
bitmap.close();
|
|
89
|
+
fail(error instanceof Error ? error : new Error(String(error)));
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
(error) => {
|
|
93
|
+
if (pending === job) fail(error instanceof Error ? error : new Error(String(error)));
|
|
94
|
+
},
|
|
95
|
+
);
|
|
96
|
+
} catch (error) {
|
|
97
|
+
fail(error instanceof Error ? error : new Error(String(error)));
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
},
|
|
101
|
+
dispose() {
|
|
102
|
+
stopped = true;
|
|
103
|
+
fail(new Error('Recording preview encoder stopped'));
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A `{ module }` root: the project's OWN adapter module is the mechanism.
|
|
3
|
+
*
|
|
4
|
+
* The editor imports the game folder's declared module (path-guarded before,
|
|
5
|
+
* shape-checked after — both loud, named failures, never a silent `undefined`
|
|
6
|
+
* or a crash deep inside `mount`), then wraps its `mount` so that what the
|
|
7
|
+
* editor actually REACHES is measured off the real mounted surface and every
|
|
8
|
+
* capability short of a native root's is warned about by name.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
type ProjectMountEpoch,
|
|
13
|
+
projectEntryImportUrl,
|
|
14
|
+
} from '@volter/editor-sdk/session/project-module-url';
|
|
15
|
+
import { relativePathRejection } from '@volter/editor-sdk/session/relative-path-guard';
|
|
16
|
+
import type { RootAdapter, SurfaceAdapter } from '@volter/editor-project/adapter';
|
|
17
|
+
import type { AdapterSurface } from '@volter/editor-project/adapter/adapter-surface';
|
|
18
|
+
import { assertNever } from '@volter/editor-project/adapter/adapter-surface';
|
|
19
|
+
import type { HostContextFor } from '@volter/editor-project/adapter/host-context';
|
|
20
|
+
import type { MountedRootFor } from '@volter/editor-project/adapter/root-adapter';
|
|
21
|
+
import type { ResolvedAdapterRoot } from '@volter/editor-project/manifest/load';
|
|
22
|
+
import { formatCapabilityWarning, measureAdapterReach } from '../adapter-reach';
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Validate `adapter.module` is a game-folder-RELATIVE path with no `..`
|
|
26
|
+
* escape. The rule itself is `relative-path-guard.ts`'s
|
|
27
|
+
* `relativePathRejection` — the ONE spelling every trust boundary in the
|
|
28
|
+
* editor shares. Only the MESSAGES are local: a manifest author needs to be
|
|
29
|
+
* told which declaration is wrong.
|
|
30
|
+
*
|
|
31
|
+
* Returns the normalized (slash-joined, no leading/trailing slash) path — the
|
|
32
|
+
* one thing the shared predicate does not do, because normalization is not a
|
|
33
|
+
* trust decision.
|
|
34
|
+
*/
|
|
35
|
+
export function guardModulePath(world: ResolvedAdapterRoot, moduleId: string): string {
|
|
36
|
+
const fail = (reason: string): never => {
|
|
37
|
+
throw new Error(
|
|
38
|
+
`resolveRootBinding: world "${world.id}" declares { module: "${moduleId}" } — ${reason} ` +
|
|
39
|
+
'(the module path must be game-folder-relative, normalized, with no `..` escape).',
|
|
40
|
+
);
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const rejection = relativePathRejection(moduleId);
|
|
44
|
+
if (rejection === 'empty') fail('the module path is empty');
|
|
45
|
+
if (rejection === 'nul') fail('the module path contains a NUL byte');
|
|
46
|
+
if (rejection === 'absolute') fail('the module path is absolute, not game-folder-relative');
|
|
47
|
+
if (rejection === 'traversal') {
|
|
48
|
+
fail('the module path contains a `..` segment and would escape the game folder');
|
|
49
|
+
}
|
|
50
|
+
// POSIX-only beyond the shared rule: a module id reaches Vite as a URL.
|
|
51
|
+
if (moduleId.includes('\\')) fail('the module path contains a backslash (not POSIX-relative)');
|
|
52
|
+
|
|
53
|
+
const normalized = moduleId
|
|
54
|
+
.split('/')
|
|
55
|
+
.filter((seg) => seg.length > 0 && seg !== '.')
|
|
56
|
+
.join('/');
|
|
57
|
+
if (normalized === '') fail('the module path resolves to nothing (empty after normalization)');
|
|
58
|
+
|
|
59
|
+
return normalized;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Runtime shape every `RootAdapter` must satisfy — the module branch cannot
|
|
64
|
+
* trust TypeScript alone (the imported value is `unknown` until checked).
|
|
65
|
+
*
|
|
66
|
+
* Parameterized by surface: the checked SHAPE is surface-independent
|
|
67
|
+
* (`{ id, mount }` are the same members for every `K`), so the caller passes
|
|
68
|
+
* the world's DECLARED surface and gets a `RootAdapter<K>` back. The surface
|
|
69
|
+
* itself is a manifest CLAIM no structural check can verify — that is exactly
|
|
70
|
+
* what {@link withCapabilityWarning} measures post-mount, against the real
|
|
71
|
+
* mounted root, and what `module-mode.ts` re-checks against the mounted
|
|
72
|
+
* result's own `kind` tag.
|
|
73
|
+
*/
|
|
74
|
+
function guardModuleShape<K extends AdapterSurface>(
|
|
75
|
+
world: ResolvedAdapterRoot,
|
|
76
|
+
moduleId: string,
|
|
77
|
+
candidate: unknown,
|
|
78
|
+
): asserts candidate is RootAdapter<K> {
|
|
79
|
+
const fail = (reason: string): never => {
|
|
80
|
+
throw new Error(
|
|
81
|
+
`resolveRootBinding: world "${world.id}"'s custom adapter module "${moduleId}" ${reason} ` +
|
|
82
|
+
'— a { module } adapter must default-export an object shaped like `RootAdapter`: ' +
|
|
83
|
+
'`{ id: string, mount(host): Promise<MountedThreeRoot> }`.',
|
|
84
|
+
);
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
if (candidate === null || typeof candidate !== 'object') {
|
|
88
|
+
fail('has no default export (or it is not an object)');
|
|
89
|
+
}
|
|
90
|
+
const obj = candidate as Record<string, unknown>;
|
|
91
|
+
if (typeof obj['id'] !== 'string' || obj['id'].length === 0) {
|
|
92
|
+
fail('default-exports an object with no non-empty string `id`');
|
|
93
|
+
}
|
|
94
|
+
if (typeof obj['mount'] !== 'function') {
|
|
95
|
+
fail('default-exports an object with no `mount` function');
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** How a `{ module }` world reaches the editor: the project's own adapter
|
|
100
|
+
* module IS the mechanism, so anything missing is missing from that module. */
|
|
101
|
+
const MODULE_ADAPTER_MECHANISM =
|
|
102
|
+
'this world mounts through the project’s own adapter module, so every capability the editor ' +
|
|
103
|
+
'has here is one that module hands it';
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Wrap a custom adapter's `mount` so that, once it resolves, what the editor
|
|
107
|
+
* actually REACHES is measured off the real mounted surface. There is nothing
|
|
108
|
+
* to compare against and nothing to declare: the bar is a native root, and a
|
|
109
|
+
* gap keeps warning until it is closed. The warning never blocks the mount.
|
|
110
|
+
*
|
|
111
|
+
* The `editorConsole.warn(...)` call is a LAZY dynamic import: this resolver is
|
|
112
|
+
* imported by headless Node/vitest suites with no `window`, and
|
|
113
|
+
* `editor-console.ts`'s module top level touches `window` unconditionally.
|
|
114
|
+
*/
|
|
115
|
+
function withCapabilityWarning<K extends AdapterSurface>(
|
|
116
|
+
world: ResolvedAdapterRoot,
|
|
117
|
+
adapter: RootAdapter<K>,
|
|
118
|
+
): RootAdapter<K> {
|
|
119
|
+
return {
|
|
120
|
+
id: adapter.id,
|
|
121
|
+
async mount(host: HostContextFor<K>): Promise<MountedRootFor<K>> {
|
|
122
|
+
const mounted = await adapter.mount(host);
|
|
123
|
+
const warning = formatCapabilityWarning(
|
|
124
|
+
world.id,
|
|
125
|
+
measureAdapterReach(mounted),
|
|
126
|
+
MODULE_ADAPTER_MECHANISM,
|
|
127
|
+
// The MOUNT's own surface, so a provider this surface cannot have (a
|
|
128
|
+
// DOM root's 3D `transforms`) is not warned about forever. Read off
|
|
129
|
+
// `mounted`, not `world`, for the same reason the reach beside it is:
|
|
130
|
+
// this is what actually mounted, not what was declared.
|
|
131
|
+
mounted.kind,
|
|
132
|
+
);
|
|
133
|
+
if (warning) {
|
|
134
|
+
const { editorConsole } = await import('@volter/editor-core/editor-console');
|
|
135
|
+
editorConsole.warn(warning, 'adapter');
|
|
136
|
+
}
|
|
137
|
+
return mounted;
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Resolve a `{ module }` adapter world: import the game folder's declared
|
|
144
|
+
* module through the same project-import route every other root entry takes
|
|
145
|
+
* (so it inherits the root `vite.config.ts`'s `resolve.dedupe: ['three',
|
|
146
|
+
* 'pixi.js']` collapse), guard it, and tag it with the manifest's declared
|
|
147
|
+
* surface.
|
|
148
|
+
*
|
|
149
|
+
* The module is imported through `projectEntryImportUrl` (`/@fs/`, mount-epoch
|
|
150
|
+
* cache-busted), like every other project module.
|
|
151
|
+
*
|
|
152
|
+
* Returns the module's own namespace beside the tagged adapter: for a
|
|
153
|
+
* `{ module }` root THAT namespace is the root's static surface — the project
|
|
154
|
+
* declared this module as the root's mechanism — and it becomes the
|
|
155
|
+
* declaration's `entry` (see `binding-resolver.ts`).
|
|
156
|
+
*/
|
|
157
|
+
export async function resolveModuleAdapter(
|
|
158
|
+
world: ResolvedAdapterRoot,
|
|
159
|
+
projectRoot: string,
|
|
160
|
+
epoch: ProjectMountEpoch,
|
|
161
|
+
): Promise<{ readonly resolved: SurfaceAdapter; readonly module: Record<string, unknown> }> {
|
|
162
|
+
const { adapter } = world;
|
|
163
|
+
if (adapter.type !== 'module') {
|
|
164
|
+
throw new Error(`resolveModuleAdapter: world "${world.id}" is not a { module } world.`);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const normalizedPath = guardModulePath(world, adapter.module);
|
|
168
|
+
// A `{ module }` root is project-owned code that can share modules with its
|
|
169
|
+
// sibling roots, so it takes the same mount epoch they do.
|
|
170
|
+
const modulePath = projectEntryImportUrl(projectRoot, normalizedPath, epoch);
|
|
171
|
+
const mod = (await import(/* @vite-ignore */ modulePath)) as Record<string, unknown>;
|
|
172
|
+
const candidate = mod['default'];
|
|
173
|
+
|
|
174
|
+
// The manifest's declared surface is the discriminant, so each branch names
|
|
175
|
+
// the module's adapter at ITS OWN surface — `guardModuleShape<K>` performs
|
|
176
|
+
// the identical runtime check in all three, and the result is paired with
|
|
177
|
+
// the tag rather than flattened through a three-shaped channel.
|
|
178
|
+
switch (world.surface) {
|
|
179
|
+
case 'three': {
|
|
180
|
+
guardModuleShape<'three'>(world, adapter.module, candidate);
|
|
181
|
+
return {
|
|
182
|
+
resolved: { surface: 'three', adapter: withCapabilityWarning(world, candidate) },
|
|
183
|
+
module: mod,
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
case 'canvas': {
|
|
187
|
+
guardModuleShape<'canvas'>(world, adapter.module, candidate);
|
|
188
|
+
return {
|
|
189
|
+
resolved: { surface: 'canvas', adapter: withCapabilityWarning(world, candidate) },
|
|
190
|
+
module: mod,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
case 'dom': {
|
|
194
|
+
guardModuleShape<'dom'>(world, adapter.module, candidate);
|
|
195
|
+
return {
|
|
196
|
+
resolved: { surface: 'dom', adapter: withCapabilityWarning(world, candidate) },
|
|
197
|
+
module: mod,
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
default:
|
|
201
|
+
return assertNever(world.surface, 'resolveModuleAdapter');
|
|
202
|
+
}
|
|
203
|
+
}
|