@volter/editor-game 0.5.65 → 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 +3 -3
- package/contributions/generations.status.tsx +1 -1
- package/contributions/godot.palette.json +99 -0
- package/contributions/godot.style.ts +82 -0
- package/contributions/godot.view.ts +75 -0
- package/contributions/ingest.service.ts +4 -4
- 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 +11 -5
- package/contributions/react/component-board.service.ts +1 -1
- package/contributions/react/design-time-mount.service.ts +1 -1
- package/contributions/react/react-inspector.service.ts +2 -2
- package/contributions/scene-document.service.ts +5 -5
- 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 +8 -8
- package/contributions/three/three-authoring.service.ts +8 -5
- package/contributions/three-story-capture.service.ts +12 -0
- package/contributions/unity.palette.json +102 -0
- package/contributions/unity.style.ts +72 -0
- package/contributions/unity.view.ts +82 -0
- package/contributions/unreal.palette.json +105 -0
- package/contributions/unreal.style.ts +66 -0
- package/contributions/unreal.view.ts +93 -0
- package/dist-node/serving.mjs +16 -0
- package/package.json +33 -16
- package/serving/index.ts +27 -0
- package/src/asset-budget/AssetBudgetPanel.tsx +1 -1
- package/src/asset-budget/asset-budget-model.ts +4 -4
- package/src/asset-budget/optimize-apply.ts +7 -7
- package/src/audio/AudioDebuggerPanel.tsx +1 -1
- package/src/bridge/dispatch.ts +16 -16
- package/src/bridge/live-frames.ts +1 -1
- package/src/bridge/screenshot.ts +6 -6
- 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 +6 -6
- package/src/coverage/live-project-verbs.ts +2 -2
- package/src/coverage/native-system-coverage.ts +6 -6
- package/src/coverage/root-coverage.ts +1 -1
- package/src/coverage/session-coverage.ts +5 -5
- package/src/design-system-stories/ApplicationChrome.stories.tsx +5 -5
- package/src/design-system-stories/InspectorNarrowBodies.stories.tsx +13 -53
- 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 +2 -2
- package/src/game-document/GameDocument.tsx +2 -2
- package/src/game-document/GamePanel.tsx +8 -8
- 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 +6 -8
- package/src/generation/generation-documents.tsx +8 -8
- package/src/generation/generation-jobs.ts +2 -2
- package/src/generation/generation-presentation.ts +1 -1
- package/src/host/adapter-reach.ts +1 -1
- package/src/host/adapter-runtime-bindings.ts +97 -6
- package/src/host/api/configurations.ts +2 -2
- package/src/host/authoring/babylon-authoring-adapter.ts +8 -8
- package/src/host/authoring/contract-hierarchy-authoring.ts +1 -1
- package/src/host/authoring/contract-scenes-stories.ts +1 -1
- package/src/host/authoring/creation-site-related.ts +2 -2
- package/src/host/authoring/ephemeral-persistence.ts +1 -1
- package/src/host/authoring/gesture-persist.ts +2 -2
- package/src/host/authoring/ingest-data-writer.ts +1 -1
- package/src/host/authoring/ingest-source-persistence.ts +8 -8
- package/src/host/authoring/mount-isolated-pixi-screen.ts +1 -1
- 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 +42 -17
- package/src/host/authoring/pixi-creation-site-write-target.ts +2 -2
- package/src/host/authoring/pixi-live-write-target.ts +7 -7
- 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 +2 -2
- package/src/host/authoring/pixi-structure-history.ts +3 -3
- package/src/host/authoring/pixi-transform-channels.ts +16 -14
- package/src/host/authoring/source-persistence-backend.ts +6 -6
- package/src/host/authoring/struct-write-pipe.ts +3 -3
- package/src/host/binding-resolver.ts +8 -9
- package/src/host/browser-transpile.ts +2 -2
- package/src/host/canvas-entry-runtime.ts +59 -48
- package/src/host/canvas-preview-frames.ts +482 -0
- package/src/host/components/CameraAuthoringOverlay.tsx +1 -1
- package/src/host/components/HeaderTelemetry.tsx +9 -16
- package/src/host/components/PixiIsolationSceneContent.tsx +17 -16
- package/src/host/components/ThreeIsolationSceneContent.tsx +5 -5
- 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 +29 -61
- package/src/host/components/utility-view-state.ts +1 -1
- package/src/host/components/world-root-stage-binding.tsx +17 -26
- package/src/host/components/world-root-stage.ts +130 -63
- package/src/host/coverage/capability-coverage.ts +13 -1
- package/src/host/coverage/game-contract-seam-evidence.ts +1 -1
- package/src/host/coverage/project-verb-coverage.ts +2 -17
- package/src/host/coverage/system-adapter-coverage.ts +4 -5
- package/src/host/design-system-stories/fixtures/editor-runtime.tsx +14 -8
- package/src/host/document-preview-three.ts +2 -2
- package/src/host/entry-adjudication.ts +6 -6
- package/src/host/game-css-scope-transform.ts +4 -0
- package/src/host/game-module-access.ts +1 -1
- package/src/host/game-realm-page.ts +16 -8
- package/src/host/game-realm-reclaim.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 +6 -6
- package/src/host/history/json-history-resource.ts +3 -3
- package/src/host/instance-extract-actions.ts +1 -1
- package/src/host/instance-fork-actions.ts +1 -1
- package/src/host/projection/dom.ts +2 -2
- package/src/host/projection/pixi.ts +25 -3
- package/src/host/r3f-entry-runtime.ts +65 -34
- package/src/host/react-mount-runtime.ts +8 -49
- package/src/host/realm-services.ts +2 -2
- package/src/host/roots/canvas-root.tsx +361 -0
- package/src/host/roots/module-root.ts +1 -1
- package/src/host/roots/r3f-root.tsx +473 -0
- package/src/host/roots/react-root.ts +9 -43
- package/src/host/scene-view-drawability.ts +2 -2
- package/src/host/served-bundle-runtime-modules.ts +1 -19
- package/src/host/server-log-bridge.ts +3 -3
- 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/three-ingest-runtime.ts +1 -1
- package/src/host/use-active-performance-source.ts +3 -3
- package/src/host/viewport-pose-memory.ts +1 -1
- package/src/host/viewport-root-presentation.ts +6 -5
- package/src/ingest/active-ingest.ts +2 -2
- package/src/ingest/authoring/ingest-dom-surface-authoring.ts +4 -4
- package/src/ingest/authoring/ingest-root-adapter.ts +13 -13
- package/src/ingest/capture-wait-report.ts +1 -1
- package/src/ingest/deferred-ingest-play.ts +11 -10
- package/src/ingest/discovery-public-ingest.ts +2 -2
- package/src/ingest/ingest-boot-viewport.ts +6 -8
- package/src/ingest/ingest-canvas-scene-document.tsx +17 -16
- package/src/ingest/ingest-canvas-scene.ts +5 -8
- 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 +4 -4
- package/src/ingest/ingest-render-debug.ts +11 -11
- package/src/ingest/ingest-siblings.ts +16 -28
- package/src/ingest/module-mode.ts +18 -19
- package/src/ingest/mount-canvas-ingest-root.ts +27 -27
- package/src/ingest/mount-coverage.ts +4 -4
- package/src/ingest/mount-dom-ingest-root.ts +16 -16
- package/src/ingest/mount-ingest-root.ts +14 -14
- package/src/ingest/mount-three-ingest-root.ts +12 -12
- 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 +7 -7
- 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 +99 -142
- package/src/play/play-recording.ts +2 -2
- 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 +4 -4
- package/src/react/design-time-react-mount.ts +37 -85
- package/src/react/dom-authoring-adapter.ts +11 -11
- package/src/react/react-inspector-section.tsx +13 -13
- package/src/react/react-world-authoring-adapter.ts +31 -143
- package/src/react/story-documents/story-documents.tsx +18 -22
- package/src/react/ui-board-document.tsx +10 -11
- package/src/react/ui-component-board.ts +5 -5
- 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 +4 -3
- package/src/three/authoring/constraint-inspector-section.tsx +8 -7
- package/src/three/authoring/model-asset-inspector-section.tsx +14 -17
- package/src/three/authoring/oid-source-persistence.ts +13 -13
- package/src/three/authoring/r3f-design-session.ts +79 -61
- package/src/three/authoring/r3f-source-authoring-adapter.ts +112 -108
- package/src/three/authoring/reflection-probe-inspector-section.tsx +5 -4
- package/src/three/authoring/spatial-joint-handles.ts +1 -1
- package/src/three/authoring/spatial-lod-handles.ts +1 -1
- package/src/three/authoring/spatial-particle-handles.ts +1 -1
- package/src/three/authoring/three-authoring-adapter.ts +33 -33
- package/src/three/component-verbs/extract-menu.ts +8 -7
- package/src/three/component-verbs/fork-menu.ts +8 -7
- package/src/three/component-verbs/internals-menu.ts +3 -3
- package/src/three/drei-asset-caches.ts +13 -0
- package/src/three/story-documents/three-story-documents.tsx +24 -34
- package/src/three/three-board/ThreeBoardDocument.tsx +25 -25
- package/src/three/three-board/board-scene.ts +7 -7
- package/src/three/three-board/three-component-board.ts +5 -5
- package/contributions/react/pasteboard.action.ts +0 -41
- package/contributions/xstate-behavior.action.ts +0 -42
- package/contributions/xstate-behavior.document.tsx +0 -73
- package/contributions/xstate-behavior.inspector.tsx +0 -28
- package/contributions/xstate-behavior.menu.ts +0 -23
- package/src/react/pasteboard-materialize.ts +0 -154
- package/src/services/game-audio-unlock.ts +0 -48
- package/src/xstate/XStateBehaviorSection.tsx +0 -130
- package/src/xstate/XStateMachineInspector.tsx +0 -566
- package/src/xstate/character-animation-machine.fixture.ts +0 -74
- package/src/xstate/live-behaviors.ts +0 -106
- package/src/xstate/use-live-actor-state.ts +0 -44
- package/src/xstate/xstate-graph.ts +0 -235
- package/src/xstate/xstate-layout.ts +0 -76
|
@@ -0,0 +1,524 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The canvas root's DESIGN-TIME mount — the game's own native world, on screen
|
|
3
|
+
* and authorable in EDIT mode, with the host clock never advanced.
|
|
4
|
+
*
|
|
5
|
+
* ## Why this is a layer mount and not a second design session
|
|
6
|
+
*
|
|
7
|
+
* `r3f-design-session.ts` exists because a three world has to be ADOPTED into
|
|
8
|
+
* the editor's own WebGL viewport: the editor already owns a renderer, a
|
|
9
|
+
* camera and a raycaster, so the session mounts fiber against a mock renderer
|
|
10
|
+
* and hands the resulting `THREE.Scene` to the store. None of that applies
|
|
11
|
+
* here. A canvas world draws through its OWN substrate into its OWN
|
|
12
|
+
* viewport-sized canvas. Pixi receives the editor's independent 2D Scene
|
|
13
|
+
* camera; Babylon receives its native camera and GizmoManager. Neither path
|
|
14
|
+
* reparents or mirrors the native entity tree.
|
|
15
|
+
*
|
|
16
|
+
* So this module is the `mountReactLayer` sibling, and it inherits that
|
|
17
|
+
* module's whole lifecycle rather than restating one: candidate discovery,
|
|
18
|
+
* the play handoff (layers torn down and the world's Boundary node restored
|
|
19
|
+
* the instant play starts; Stop queues one edit-mode rebuild), the failure
|
|
20
|
+
* degrade to a `BoundaryAuthoringAdapter` disclosure node, the pan
|
|
21
|
+
* subscription, and the eye/interactive session toggles. `r3f-design-session`
|
|
22
|
+
* says of its own play handoff that it "mirrors design-time-layers.ts"; this
|
|
23
|
+
* lane uses the original instead of a second copy.
|
|
24
|
+
*
|
|
25
|
+
* ## The paused clock, stated once
|
|
26
|
+
*
|
|
27
|
+
* Pixi initializes its `Application` with `autoStart: false` and
|
|
28
|
+
* `sharedTicker: false`; the ticker never starts and this module calls only
|
|
29
|
+
* `app.render()`. Babylon receives `mounted.update(0)`, explicitly a zero-delta
|
|
30
|
+
* still-frame request. Both paths render inspector and gizmo edits without
|
|
31
|
+
* advancing game time. Same shape as the editor's own three viewport, which
|
|
32
|
+
* also draws every frame over a scene nothing is ticking.
|
|
33
|
+
*
|
|
34
|
+
* ## Input is off
|
|
35
|
+
*
|
|
36
|
+
* The design host carries a REAL `Game` — the world's components read
|
|
37
|
+
* `ctx.input`/`ctx.random`/`ctx.debug` at their first commit and a fabricated
|
|
38
|
+
* stand-in would throw there — but `game.input.setEnabled(false)` is called
|
|
39
|
+
* before the mount, because design time is not play. `InputManager` binds
|
|
40
|
+
* window-level listeners in its constructor; `game.dispose()` unbinds them and
|
|
41
|
+
* this module's disposer is what calls it.
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
import { setActiveSystems, updateInstanceSystems } from '@volter/editor-sdk/kit/authoring/active-systems';
|
|
45
|
+
import {
|
|
46
|
+
BabylonAuthoringAdapter,
|
|
47
|
+
type BabylonEngineLike,
|
|
48
|
+
} from '../host/authoring/babylon-authoring-adapter';
|
|
49
|
+
import { PixiAuthoringAdapter } from '../host/authoring/pixi-authoring-adapter';
|
|
50
|
+
import { createOidCanvasIdentity } from '../host/authoring/pixi-source-identity';
|
|
51
|
+
import { createSourceCanvasWriteTarget } from '../host/authoring/pixi-source-write-target';
|
|
52
|
+
import {
|
|
53
|
+
fitSceneView,
|
|
54
|
+
registerStillFramePresenter,
|
|
55
|
+
} from '../host/authoring/pixi-still-presentation';
|
|
56
|
+
import { createCreationSitePersistence } from '../host/authoring/source-persistence-backend';
|
|
57
|
+
import {
|
|
58
|
+
type RootViewController,
|
|
59
|
+
sharedRootViewController,
|
|
60
|
+
} from '@volter/editor-sdk/kit/world-pan-state';
|
|
61
|
+
import {
|
|
62
|
+
resolveCanvasEntryAdapterForEditor,
|
|
63
|
+
resolveCanvasPixiForEditor,
|
|
64
|
+
} from '../host/canvas-entry-runtime';
|
|
65
|
+
import {
|
|
66
|
+
capturePixiDisplayObjectThumbnail,
|
|
67
|
+
withApplicationCollector,
|
|
68
|
+
} from '../host/canvas-preview-frames';
|
|
69
|
+
import { editorConsole } from '@volter/editor-sdk/kit/editor-console';
|
|
70
|
+
import type { EditorShellStore } from '@volter/editor-threejs/kit/editor-shell-store';
|
|
71
|
+
import { authoringJournal } from '../host/history/json-history-resource';
|
|
72
|
+
import { getCurrentProject } from '@volter/editor-sdk/kit/project-manager';
|
|
73
|
+
import { beginProjectMountEpoch } from '@volter/editor-sdk/session/project-module-url';
|
|
74
|
+
import { activeRealmServices } from '../host/realm-services';
|
|
75
|
+
import { tierSourceWriteBackend } from '@volter/editor-sdk/kit/ui-source/tier-source-write-backend';
|
|
76
|
+
import type { AuthoringAdapter, MountedCanvasRoot } from '@volter/editor-project/adapter';
|
|
77
|
+
import {
|
|
78
|
+
installNativeDebugBindings,
|
|
79
|
+
installNativeSystemsBindings,
|
|
80
|
+
nativeDebugBindingFromEntryModule,
|
|
81
|
+
nativeSystemsBindingFromEntryModule,
|
|
82
|
+
} from '../runtime/adapter/native-debug-module';
|
|
83
|
+
import { createAssetCache } from '@volter/threejs-runtime/assets';
|
|
84
|
+
import { createGameLoop } from '../runtime/core/game-loop';
|
|
85
|
+
import { registerCanvasRoot } from '../runtime/create-runtime';
|
|
86
|
+
import { createGame, type GameInternal } from '../runtime/game';
|
|
87
|
+
import type { GameCanvasHostContext } from '../runtime/host-context';
|
|
88
|
+
import type { Application } from 'pixi.js';
|
|
89
|
+
|
|
90
|
+
/** What a design-time canvas layer hands back to `design-time-layers.ts`. */
|
|
91
|
+
export interface MountedCanvasDesignLayer {
|
|
92
|
+
readonly adapter: AuthoringAdapter;
|
|
93
|
+
dispose(): void;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** Fallback stage size when the project declares no `resolution` — the same
|
|
97
|
+
* 720p the canvas host defaults to elsewhere. */
|
|
98
|
+
const DEFAULT_STAGE = { width: 1280, height: 720 } as const;
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* The inert design-time `Game`.
|
|
102
|
+
*
|
|
103
|
+
* A REAL `createGame` whose loop is constructed and NEVER started (the same
|
|
104
|
+
* decision, for the same reason, as `r3f-design-session.ts`'s `createDesignHost`
|
|
105
|
+
* and `design-time-layers.ts`'s `createDesignTimeGame`): the design-time Game
|
|
106
|
+
* shell is what the lib-legal react doors (`useOptionalGame`,
|
|
107
|
+
* `useDebugProvider`) resolve against, so a world whose components read the
|
|
108
|
+
* game handle design-mounts exactly as it plays.
|
|
109
|
+
*/
|
|
110
|
+
function createDesignGame(): GameInternal {
|
|
111
|
+
const game = createGame({
|
|
112
|
+
loop: createGameLoop({ update: () => {} }),
|
|
113
|
+
assets: createAssetCache(),
|
|
114
|
+
});
|
|
115
|
+
return game;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** The viewport-sized surface the native stage draws into. World extents are
|
|
119
|
+
* deliberately not encoded in this element: the editor camera matrix decides
|
|
120
|
+
* which part of the unbounded authored plane is visible. */
|
|
121
|
+
function createDesignCanvas(): HTMLCanvasElement {
|
|
122
|
+
const canvas = document.createElement('canvas');
|
|
123
|
+
canvas.dataset['vgaiCanvasSceneSurface'] = 'true';
|
|
124
|
+
canvas.style.position = 'absolute';
|
|
125
|
+
canvas.style.inset = '0';
|
|
126
|
+
canvas.style.width = '100%';
|
|
127
|
+
canvas.style.height = '100%';
|
|
128
|
+
canvas.style.display = 'block';
|
|
129
|
+
return canvas;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
interface BabylonDesignMountOptions {
|
|
133
|
+
readonly worldId: string;
|
|
134
|
+
readonly entry: string;
|
|
135
|
+
readonly layer: HTMLElement;
|
|
136
|
+
readonly canvas: HTMLCanvasElement;
|
|
137
|
+
readonly game: GameInternal;
|
|
138
|
+
readonly mounted: MountedCanvasRoot;
|
|
139
|
+
readonly store: EditorShellStore;
|
|
140
|
+
readonly onFirstFrame?: (() => void) | undefined;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** Babylon's native 3D Scene document. The host asks the mounted root to draw
|
|
144
|
+
* a zero-delta still frame; simulation remains frozen, while native picking
|
|
145
|
+
* and GizmoManager author the real Babylon nodes. */
|
|
146
|
+
function mountBabylonDesignLayer(options: BabylonDesignMountOptions): MountedCanvasDesignLayer {
|
|
147
|
+
const { worldId, entry, layer, canvas, game, mounted, store, onFirstFrame } = options;
|
|
148
|
+
const backend = createCreationSitePersistence({ history: store.shell.projectHistory });
|
|
149
|
+
const adapter = new BabylonAuthoringAdapter(mounted.substrate.root as BabylonEngineLike, store, {
|
|
150
|
+
canvas,
|
|
151
|
+
api: mounted.substrate.api as never,
|
|
152
|
+
persistence: backend,
|
|
153
|
+
journal: authoringJournal(worldId),
|
|
154
|
+
provenance: {
|
|
155
|
+
source: 'source-code',
|
|
156
|
+
label: 'babylon',
|
|
157
|
+
detail:
|
|
158
|
+
'Native Babylon.js scene graph; imperative edits write to the construction site in the game’s own source.',
|
|
159
|
+
},
|
|
160
|
+
});
|
|
161
|
+
let publishedSystems = game.systemAdapters;
|
|
162
|
+
setActiveSystems(publishedSystems);
|
|
163
|
+
const unsubscribeSystemAdapters = game.subscribeSystemAdapters?.(() => {
|
|
164
|
+
publishedSystems = game.systemAdapters;
|
|
165
|
+
updateInstanceSystems(publishedSystems);
|
|
166
|
+
store.shell.notifyIngestEdit();
|
|
167
|
+
});
|
|
168
|
+
const resize = (): void => {
|
|
169
|
+
const rect = layer.getBoundingClientRect();
|
|
170
|
+
if (rect.width > 0 && rect.height > 0) mounted.resize?.(rect.width, rect.height);
|
|
171
|
+
};
|
|
172
|
+
const observer = new ResizeObserver(resize);
|
|
173
|
+
observer.observe(layer);
|
|
174
|
+
resize();
|
|
175
|
+
let disposed = false;
|
|
176
|
+
let first = false;
|
|
177
|
+
let frame = 0;
|
|
178
|
+
const draw = (): void => {
|
|
179
|
+
frame = requestAnimationFrame(draw);
|
|
180
|
+
if (disposed) return;
|
|
181
|
+
try {
|
|
182
|
+
mounted.update?.(0);
|
|
183
|
+
if (!first) {
|
|
184
|
+
first = true;
|
|
185
|
+
onFirstFrame?.();
|
|
186
|
+
}
|
|
187
|
+
} catch (error) {
|
|
188
|
+
cancelAnimationFrame(frame);
|
|
189
|
+
editorConsole.error(
|
|
190
|
+
`[babylon-design] world "${worldId}" failed to render: ${String(error)}`,
|
|
191
|
+
'authoring',
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
frame = requestAnimationFrame(draw);
|
|
196
|
+
editorConsole.log(
|
|
197
|
+
`[babylon-design] world "${worldId}" mounted as a native Babylon Scene (${entry})`,
|
|
198
|
+
'authoring',
|
|
199
|
+
);
|
|
200
|
+
return {
|
|
201
|
+
adapter,
|
|
202
|
+
dispose(): void {
|
|
203
|
+
disposed = true;
|
|
204
|
+
cancelAnimationFrame(frame);
|
|
205
|
+
observer.disconnect();
|
|
206
|
+
adapter.dispose();
|
|
207
|
+
unsubscribeSystemAdapters?.();
|
|
208
|
+
setActiveSystems(null);
|
|
209
|
+
mounted.dispose();
|
|
210
|
+
canvas.remove();
|
|
211
|
+
game.dispose();
|
|
212
|
+
},
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Mount one canvas root's world into `layer` for design-time authoring.
|
|
218
|
+
*
|
|
219
|
+
* Throws on any failure (a missing entry, a module that is neither shape, a
|
|
220
|
+
* tree that crashes before its first commit — the canvas mount already
|
|
221
|
+
* converts that last one into a named error rather than a hang). The caller
|
|
222
|
+
* degrades a throw to the world's `BoundaryAuthoringAdapter` disclosure node.
|
|
223
|
+
*/
|
|
224
|
+
/** The views a canvas world has already opened in (see the opening framing below). */
|
|
225
|
+
const openedViews = new WeakSet<RootViewController>();
|
|
226
|
+
|
|
227
|
+
export async function mountCanvasDesignLayer(
|
|
228
|
+
worldId: string,
|
|
229
|
+
entry: string | undefined,
|
|
230
|
+
layer: HTMLElement,
|
|
231
|
+
projectRoot: string,
|
|
232
|
+
store: EditorShellStore,
|
|
233
|
+
suppliedView?: RootViewController,
|
|
234
|
+
onFirstFrame?: () => void,
|
|
235
|
+
): Promise<MountedCanvasDesignLayer> {
|
|
236
|
+
if (!entry) {
|
|
237
|
+
throw new Error(`canvas root "${worldId}": no \`entry\` declared — nothing to mount.`);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// One epoch per design mount: this world shares no project module graph with
|
|
241
|
+
// anything else (`ingest-siblings.ts`'s sibling layer does the same).
|
|
242
|
+
const realm = await activeRealmServices(projectRoot, beginProjectMountEpoch());
|
|
243
|
+
const entryModule = await realm.loadEntryModule(entry, worldId, 'canvas');
|
|
244
|
+
|
|
245
|
+
const entryDebug = nativeDebugBindingFromEntryModule(worldId, entryModule);
|
|
246
|
+
const entrySystems = nativeSystemsBindingFromEntryModule(worldId, entryModule, 'canvas');
|
|
247
|
+
|
|
248
|
+
// Pixi and the reconciler are loaded ONLY when a project actually has a
|
|
249
|
+
// canvas root: a static import would put them in every editor bundle,
|
|
250
|
+
// including one opening a three-only project. Under the PACKAGED runtime the
|
|
251
|
+
// adjudicator itself comes from the project's own Vite graph, so the adapter
|
|
252
|
+
// it builds closes over the project's React/`@pixi/react`/`pixi.js` rather
|
|
253
|
+
// than the prebuilt shell's — see `canvas-entry-runtime.ts`.
|
|
254
|
+
const rootAdapter = await resolveCanvasEntryAdapterForEditor(entryModule, worldId);
|
|
255
|
+
// THE namespace for everything this mount builds — see
|
|
256
|
+
// `../../vite-plugin-module-doorways.ts`. Resolved beside the adjudicator
|
|
257
|
+
// because it comes from the same graph the adjudicator does.
|
|
258
|
+
if (!rootAdapter) {
|
|
259
|
+
throw new Error(
|
|
260
|
+
`canvas root "${worldId}": entry module "${entry}" must default-export a React component ` +
|
|
261
|
+
'(`export default function World() { … }`) — or export an `adapter` for full control.',
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const activeProject = getCurrentProject();
|
|
266
|
+
const width = activeProject?.config.resolution?.width ?? DEFAULT_STAGE.width;
|
|
267
|
+
const height = activeProject?.config.resolution?.height ?? DEFAULT_STAGE.height;
|
|
268
|
+
const view = suppliedView ?? sharedRootViewController;
|
|
269
|
+
const canvas = createDesignCanvas();
|
|
270
|
+
layer.appendChild(canvas);
|
|
271
|
+
|
|
272
|
+
const game = createDesignGame();
|
|
273
|
+
const host: GameCanvasHostContext = {
|
|
274
|
+
canvas,
|
|
275
|
+
width,
|
|
276
|
+
height,
|
|
277
|
+
game,
|
|
278
|
+
headless: false,
|
|
279
|
+
// The Scene grid is editor chrome behind native world content. Explicit
|
|
280
|
+
// source-owned backgrounds still draw normally; the renderer itself must
|
|
281
|
+
// not fabricate the runtime's opaque output surface over the grid.
|
|
282
|
+
transparent: true,
|
|
283
|
+
// Scene capture must photograph the exact PRESENTED frame, including this
|
|
284
|
+
// document's independent editor-camera matrix. Registering the app with
|
|
285
|
+
// the generic Pixi extraction seam would re-render its raw stage and omit
|
|
286
|
+
// that presentation transform. This design-only renderer therefore keeps
|
|
287
|
+
// its drawing buffer so the compositor reads the viewport canvas itself.
|
|
288
|
+
preserveDrawingBuffer: true,
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
// The Pixi `Application`, when Pixi is the declared substrate. It is
|
|
292
|
+
// collected through Pixi's own `__PIXI_APP_INIT__` hook; imperative adapters
|
|
293
|
+
// such as Babylon simply leave this handle undefined.
|
|
294
|
+
let mounted: MountedCanvasRoot;
|
|
295
|
+
let app: Application | undefined;
|
|
296
|
+
try {
|
|
297
|
+
mounted = await withApplicationCollector(async (created) => {
|
|
298
|
+
const result = await rootAdapter.mount(host);
|
|
299
|
+
app = created[0];
|
|
300
|
+
return result;
|
|
301
|
+
});
|
|
302
|
+
} catch (error) {
|
|
303
|
+
canvas.remove();
|
|
304
|
+
game.dispose();
|
|
305
|
+
throw error;
|
|
306
|
+
}
|
|
307
|
+
registerCanvasRoot(game, rootAdapter, mounted, { id: worldId });
|
|
308
|
+
installNativeDebugBindings(game, entryDebug ? [entryDebug] : []);
|
|
309
|
+
installNativeSystemsBindings(game, entrySystems ? [entrySystems] : []);
|
|
310
|
+
|
|
311
|
+
if (mounted.substrate.name === 'babylon') {
|
|
312
|
+
return mountBabylonDesignLayer({
|
|
313
|
+
worldId,
|
|
314
|
+
entry,
|
|
315
|
+
layer,
|
|
316
|
+
canvas,
|
|
317
|
+
game,
|
|
318
|
+
mounted,
|
|
319
|
+
store,
|
|
320
|
+
onFirstFrame,
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
if (mounted.substrate.name !== 'pixi') {
|
|
324
|
+
mounted.dispose();
|
|
325
|
+
canvas.remove();
|
|
326
|
+
game.dispose();
|
|
327
|
+
throw new Error(
|
|
328
|
+
`canvas root "${worldId}" mounted undeclared editor substrate ` +
|
|
329
|
+
`"${mounted.substrate.name}" — declare pixi or babylon and provide its authoring adapter.`,
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
const stage = mounted.substrate.root as import('pixi.js').Container;
|
|
333
|
+
const pixi = await resolveCanvasPixiForEditor();
|
|
334
|
+
|
|
335
|
+
// A MOUNT WITHOUT AN APPLICATION IS A DESIGN VIEW THAT CAN NEVER DRAW, and it
|
|
336
|
+
// used to say nothing at all: `renderScene()` below simply answers `false`
|
|
337
|
+
// forever, the rAF loop spins, the capture door hands back a canvas nothing
|
|
338
|
+
// ever rendered into, and the Scene reads as an empty grid over a world that
|
|
339
|
+
// mounted perfectly. The hook is Pixi's own devtools global, so anything that
|
|
340
|
+
// moves it (a Pixi upgrade, a second collector, a mount that never reaches
|
|
341
|
+
// `Application.init`) breaks the handle without breaking the mount. Say so.
|
|
342
|
+
if (app === undefined) {
|
|
343
|
+
editorConsole.error(
|
|
344
|
+
`[canvas-design] world "${worldId}" mounted, but its Pixi Application was never handed to ` +
|
|
345
|
+
"the design view (Pixi's `__PIXI_APP_INIT__` hook did not report one) — this Scene will " +
|
|
346
|
+
'render no frames at all. The world itself is fine; the editor cannot draw it.',
|
|
347
|
+
'authoring',
|
|
348
|
+
);
|
|
349
|
+
}
|
|
350
|
+
// This inert editor mount is still a real Game shell. Register its native
|
|
351
|
+
// root so Game.systemAdapters sees the mounted bag, then publish that live
|
|
352
|
+
// aggregate exactly as the R3F design session does. Without this, Canvas
|
|
353
|
+
// authoring exists while system coverage reads an empty active plane and
|
|
354
|
+
// fabricates a missing-debug warning over a registry the mount did create.
|
|
355
|
+
let publishedSystems = game.systemAdapters;
|
|
356
|
+
setActiveSystems(publishedSystems);
|
|
357
|
+
const unsubscribeSystemAdapters = game.subscribeSystemAdapters?.(() => {
|
|
358
|
+
publishedSystems = game.systemAdapters;
|
|
359
|
+
updateInstanceSystems(publishedSystems);
|
|
360
|
+
store.shell.notifyIngestEdit();
|
|
361
|
+
});
|
|
362
|
+
// THE canvas authoring adapter, on its two declared axes: oid identity (this
|
|
363
|
+
// world's containers carry the `data-oid` the editor's transform stamped) and
|
|
364
|
+
// a TSX-source persistence target. The very same class projects an ingested
|
|
365
|
+
// game's stage, with the structural identity and the live-only target — the
|
|
366
|
+
// adapter is keyed on the SURFACE, never on provenance.
|
|
367
|
+
const writeBackend = await tierSourceWriteBackend();
|
|
368
|
+
const adapter = new PixiAuthoringAdapter(stage, store, {
|
|
369
|
+
pixi,
|
|
370
|
+
identity: createOidCanvasIdentity(worldId),
|
|
371
|
+
journal: authoringJournal(worldId),
|
|
372
|
+
target: createSourceCanvasWriteTarget({
|
|
373
|
+
worldId,
|
|
374
|
+
entryPath: entry,
|
|
375
|
+
pixi,
|
|
376
|
+
writeBackend,
|
|
377
|
+
}),
|
|
378
|
+
surface: () => canvas,
|
|
379
|
+
capturePreview: (object, size) => capturePixiDisplayObjectThumbnail(object, { ...size, pixi }),
|
|
380
|
+
pointFromClient: (clientX, clientY, surfaceRect) => {
|
|
381
|
+
const pose = view.get();
|
|
382
|
+
return {
|
|
383
|
+
x: (clientX - surfaceRect.left - pose.x) / pose.zoom,
|
|
384
|
+
y: (clientY - surfaceRect.top - pose.y) / pose.zoom,
|
|
385
|
+
};
|
|
386
|
+
},
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
// The Pixi renderer belongs to the document viewport, not the game's
|
|
390
|
+
// declared output resolution. Resize only presentation; the world mounted
|
|
391
|
+
// against the real project width/height above and keeps those semantics.
|
|
392
|
+
const resize = (): void => {
|
|
393
|
+
const rect = layer.getBoundingClientRect();
|
|
394
|
+
const renderer = app?.renderer;
|
|
395
|
+
if (renderer && rect.width > 0 && rect.height > 0) renderer.resize(rect.width, rect.height);
|
|
396
|
+
};
|
|
397
|
+
const resizeObserver = new ResizeObserver(resize);
|
|
398
|
+
resizeObserver.observe(layer);
|
|
399
|
+
resize();
|
|
400
|
+
|
|
401
|
+
const renderScene = (): boolean => {
|
|
402
|
+
const renderer = app?.renderer;
|
|
403
|
+
if (!renderer) return false;
|
|
404
|
+
const rect = layer.getBoundingClientRect();
|
|
405
|
+
if (
|
|
406
|
+
rect.width > 0 &&
|
|
407
|
+
rect.height > 0 &&
|
|
408
|
+
(renderer.screen.width !== rect.width || renderer.screen.height !== rect.height)
|
|
409
|
+
) {
|
|
410
|
+
renderer.resize(rect.width, rect.height);
|
|
411
|
+
}
|
|
412
|
+
const pose = view.get();
|
|
413
|
+
renderer.render({
|
|
414
|
+
container: stage,
|
|
415
|
+
// The world's OWN `Matrix` — this value is handed to the world's own
|
|
416
|
+
// renderer, which is free to type-check what it is given.
|
|
417
|
+
transform: new pixi.Matrix(pose.zoom, 0, 0, pose.zoom, pose.x, pose.y),
|
|
418
|
+
});
|
|
419
|
+
return true;
|
|
420
|
+
};
|
|
421
|
+
|
|
422
|
+
const unregisterPresented = registerStillFramePresenter(canvas, renderScene);
|
|
423
|
+
|
|
424
|
+
// THE PAUSED CLOCK (see this module's header): draw, never advance. A frame
|
|
425
|
+
// costs one render of a still stage and stops the moment the tab is hidden,
|
|
426
|
+
// because that is what `requestAnimationFrame` does.
|
|
427
|
+
let frame = 0;
|
|
428
|
+
let disposed = false;
|
|
429
|
+
let firstFrameReported = false;
|
|
430
|
+
const draw = (): void => {
|
|
431
|
+
frame = requestAnimationFrame(draw);
|
|
432
|
+
if (disposed) return;
|
|
433
|
+
try {
|
|
434
|
+
const rendered = renderScene();
|
|
435
|
+
if (rendered && !firstFrameReported) {
|
|
436
|
+
firstFrameReported = true;
|
|
437
|
+
onFirstFrame?.();
|
|
438
|
+
}
|
|
439
|
+
} catch (error) {
|
|
440
|
+
cancelAnimationFrame(frame);
|
|
441
|
+
frame = 0;
|
|
442
|
+
editorConsole.error(
|
|
443
|
+
`[canvas-design] world "${worldId}" failed to render — the design view is frozen: ${String(error)}`,
|
|
444
|
+
'authoring',
|
|
445
|
+
);
|
|
446
|
+
}
|
|
447
|
+
};
|
|
448
|
+
frame = requestAnimationFrame(draw);
|
|
449
|
+
|
|
450
|
+
// Open on the authored WORLD, not on the declared output rectangle. Content
|
|
451
|
+
// may arrive after an atlas resolves, so keep a short frame window open
|
|
452
|
+
// until real bounds exist. Any user navigation cancels it immediately.
|
|
453
|
+
//
|
|
454
|
+
// ONCE PER VIEW. The world remounts whenever its source changes (every committed gesture writes
|
|
455
|
+
// source), and a view the person has already been looking at keeps its pose across that: a
|
|
456
|
+
// resize that re-framed the whole scene on commit moved everything under the pointer.
|
|
457
|
+
const reopening = openedViews.has(view);
|
|
458
|
+
openedViews.add(view);
|
|
459
|
+
const initialRect = layer.getBoundingClientRect();
|
|
460
|
+
if (!reopening && initialRect.width > 0 && initialRect.height > 0) {
|
|
461
|
+
view.setView(initialRect.width / 2, initialRect.height / 2, 1);
|
|
462
|
+
}
|
|
463
|
+
let openingFit = 0;
|
|
464
|
+
let openingAttempts = 0;
|
|
465
|
+
let openingCancelled = reopening;
|
|
466
|
+
const stopOpeningOnNavigation = view.subscribe(() => {
|
|
467
|
+
openingCancelled = true;
|
|
468
|
+
});
|
|
469
|
+
// …but never SMALLER than the player's own view of it. Fitting the whole
|
|
470
|
+
// world let a 1792×1280 floor open the survivor example at 26–34 %, where
|
|
471
|
+
// the 48 px hero is ~13 screen px on a busy grid: a tester read the scene as
|
|
472
|
+
// "only an infinite grid, nothing selectable" while the hero was drawn at
|
|
473
|
+
// the origin the whole time (runhuman pass 123; reproduced 2026-09-02). The
|
|
474
|
+
// declared resolution is what the player sees, so the zoom that frames IT
|
|
475
|
+
// is the floor: a world larger than the screen opens at screen scale,
|
|
476
|
+
// centred on the world; a smaller world still fills the pane as before.
|
|
477
|
+
// MEASURED after the first cut (which floored at the declared-resolution
|
|
478
|
+
// fit): the default dock leaves a 700×898 pane, the 1280×720 frame fits it
|
|
479
|
+
// at 47 %, and the 48 px hero was a 6 px speck — an improvement on 26 %
|
|
480
|
+
// and still a bare grid (build-45 gate, 2026-09-02). The player's view is
|
|
481
|
+
// 1:1 pixels, so the floor is 1:1: a big world opens at native scale,
|
|
482
|
+
// cropped to the pane around the world's centre — the hero at the origin,
|
|
483
|
+
// the floor art at its own size — and the author zooms out on purpose. A
|
|
484
|
+
// world that fits the pane at more than 1:1 still fills it, as before.
|
|
485
|
+
const declaredFitZoom = (): number => {
|
|
486
|
+
const rect = layer.getBoundingClientRect();
|
|
487
|
+
if (rect.width < 2 || rect.height < 2) return 1;
|
|
488
|
+
return Math.max(1, Math.min((rect.width - 96) / width, (rect.height - 96) / height));
|
|
489
|
+
};
|
|
490
|
+
const tryOpeningFit = (): void => {
|
|
491
|
+
if (openingCancelled || disposed || openingAttempts++ > 180) return;
|
|
492
|
+
const bounds = stage.getBounds();
|
|
493
|
+
if (fitSceneView(layer, bounds, view, declaredFitZoom())) return;
|
|
494
|
+
openingFit = requestAnimationFrame(tryOpeningFit);
|
|
495
|
+
};
|
|
496
|
+
openingFit = requestAnimationFrame(tryOpeningFit);
|
|
497
|
+
|
|
498
|
+
editorConsole.log(
|
|
499
|
+
`[canvas-design] world "${worldId}" mounted for design-time authoring (${entry})`,
|
|
500
|
+
'authoring',
|
|
501
|
+
);
|
|
502
|
+
|
|
503
|
+
return {
|
|
504
|
+
adapter,
|
|
505
|
+
dispose(): void {
|
|
506
|
+
disposed = true;
|
|
507
|
+
cancelAnimationFrame(frame);
|
|
508
|
+
cancelAnimationFrame(openingFit);
|
|
509
|
+
stopOpeningOnNavigation();
|
|
510
|
+
resizeObserver.disconnect();
|
|
511
|
+
unregisterPresented();
|
|
512
|
+
adapter.dispose();
|
|
513
|
+
unsubscribeSystemAdapters?.();
|
|
514
|
+
setActiveSystems(null);
|
|
515
|
+
try {
|
|
516
|
+
mounted.dispose();
|
|
517
|
+
} catch (error) {
|
|
518
|
+
editorConsole.error(`[canvas-design] dispose failed: ${String(error)}`, 'authoring');
|
|
519
|
+
}
|
|
520
|
+
canvas.remove();
|
|
521
|
+
game.dispose();
|
|
522
|
+
},
|
|
523
|
+
};
|
|
524
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THE CANVAS MEDIUM'S DESIGN-TIME MOUNT, as the SDK's design-time mount
|
|
3
|
+
* registry asks for it — the layer-shaped wrapper around
|
|
4
|
+
* `canvas-design-mount.ts`, registered by
|
|
5
|
+
* `contributions/canvas/design-time-mount.service.ts`. The design-time layer
|
|
6
|
+
* stack (`@volter/editor-sdk/kit/authoring/design-time-layers`) owns the
|
|
7
|
+
* failure, teardown and Play lifecycle for every medium.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type {
|
|
11
|
+
DesignTimeRootDescriptor,
|
|
12
|
+
LayerMountResult,
|
|
13
|
+
} from '@volter/editor-sdk/kit/authoring/design-time-layers';
|
|
14
|
+
import type { DesignTimeMountContext } from '@volter/editor-sdk/kit/authoring/design-time-mount-registry';
|
|
15
|
+
import { connectSourceFileEvents } from '@volter/editor-sdk/kit/asset-events';
|
|
16
|
+
import { recordViewportFirstFrame } from '@volter/editor-sdk/kit/viewport-activation-timings';
|
|
17
|
+
import { threeStoreForHost } from '@volter/editor-threejs/kit/three-state';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* A canvas world mounts its own `@pixi/react` `<Application>` (or its
|
|
21
|
+
* Babylon equivalent) into this layer and is never ticked — see
|
|
22
|
+
* `canvas-design-mount.ts` for the paused clock and the disabled input.
|
|
23
|
+
*
|
|
24
|
+
* `canvas-design-mount.ts` is imported dynamically so `pixi.js`/`@pixi/react`
|
|
25
|
+
* enter a bundle only for a project that actually declares a canvas root. It
|
|
26
|
+
* has been that way since the dispatch was a ternary in the host, and it is
|
|
27
|
+
* the reason the contribution module beside this one stays a handful of lines.
|
|
28
|
+
*/
|
|
29
|
+
export async function mountCanvasDesignTimeLayer(
|
|
30
|
+
candidate: DesignTimeRootDescriptor,
|
|
31
|
+
layer: HTMLElement,
|
|
32
|
+
context: DesignTimeMountContext,
|
|
33
|
+
): Promise<LayerMountResult> {
|
|
34
|
+
const { mountCanvasDesignLayer } = await import('./canvas-design-mount');
|
|
35
|
+
const { projectRootPath, view, activationDocumentId } = context;
|
|
36
|
+
// The Pixi authoring adapter edits through the Three store's selection and
|
|
37
|
+
// history, the same store Play's canvas adapter is handed.
|
|
38
|
+
const store = threeStoreForHost();
|
|
39
|
+
if (!store) throw new Error(`canvas root "${candidate.worldId}": the editor store is not bound yet.`);
|
|
40
|
+
const mounted = await mountCanvasDesignLayer(
|
|
41
|
+
candidate.worldId,
|
|
42
|
+
candidate.path,
|
|
43
|
+
layer,
|
|
44
|
+
projectRootPath,
|
|
45
|
+
store,
|
|
46
|
+
view,
|
|
47
|
+
activationDocumentId ? () => recordViewportFirstFrame(activationDocumentId) : undefined,
|
|
48
|
+
);
|
|
49
|
+
return { adapter: mounted.adapter, dispose: () => mounted.dispose() };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* ABSORB-BY-REMOUNT (`DesignTimeMount.remountWhen`): a canvas world is re-executed from its source
|
|
54
|
+
* whenever a project source module changes, whoever wrote it. Nothing else re-runs it: the design
|
|
55
|
+
* layer's entry is imported once per mount, the packaged editor has no Vite client to deliver Fast
|
|
56
|
+
* Refresh, and an external edit to the world left the scene (and every value the Inspector and a
|
|
57
|
+
* gesture read from it) on the old source until the document was reopened. The server's source
|
|
58
|
+
* watcher reports every `src/` module change as `source-files-changed` (a hosted project with no
|
|
59
|
+
* path reports `''`, which still means something changed).
|
|
60
|
+
*
|
|
61
|
+
* REMOUNT, not re-project: a canvas layer's content is one native `<Application>` the disposer must
|
|
62
|
+
* tear down before a second one draws into the same box.
|
|
63
|
+
*/
|
|
64
|
+
export function remountWhenSourceIsWritten(invalidate: () => void): () => void {
|
|
65
|
+
let timer: ReturnType<typeof setTimeout> | null = null;
|
|
66
|
+
const unwatch = connectSourceFileEvents((path) => {
|
|
67
|
+
if (path !== '' && !/^src\/.+\.(?:[cm]?tsx?|jsx?)$/.test(path)) return;
|
|
68
|
+
if (timer !== null) clearTimeout(timer);
|
|
69
|
+
timer = setTimeout(() => {
|
|
70
|
+
timer = null;
|
|
71
|
+
invalidate();
|
|
72
|
+
}, 150);
|
|
73
|
+
});
|
|
74
|
+
return () => {
|
|
75
|
+
if (timer !== null) clearTimeout(timer);
|
|
76
|
+
timer = null;
|
|
77
|
+
unwatch();
|
|
78
|
+
};
|
|
79
|
+
}
|
|
@@ -9,12 +9,12 @@ import {
|
|
|
9
9
|
type CapabilityCoverageReport,
|
|
10
10
|
deriveCapabilityCoverage,
|
|
11
11
|
} from '../host/coverage/capability-coverage';
|
|
12
|
-
import { projectAdapterFacet } from '@volter/editor-
|
|
13
|
-
import { getCurrentProject } from '@volter/editor-
|
|
14
|
-
import { authoringSurfaceFromTable } from '@volter/editor-
|
|
15
|
-
import { projectStoriesReady } from '@volter/editor-
|
|
16
|
-
import { availableWorkspaceDocuments } from '@volter/editor-
|
|
17
|
-
import { openWorkspaceDocuments } from '@volter/editor-
|
|
12
|
+
import { projectAdapterFacet } from '@volter/editor-sdk/kit/project-adapter';
|
|
13
|
+
import { getCurrentProject } from '@volter/editor-sdk/kit/active-project';
|
|
14
|
+
import { authoringSurfaceFromTable } from '@volter/editor-sdk/kit/scene-document-plan';
|
|
15
|
+
import { projectStoriesReady } from '@volter/editor-sdk/kit/stories/story-registry';
|
|
16
|
+
import { availableWorkspaceDocuments } from '@volter/editor-sdk/kit/workspace-available-documents';
|
|
17
|
+
import { openWorkspaceDocuments } from '@volter/editor-sdk/kit/workspace-document-registry';
|
|
18
18
|
|
|
19
19
|
export function authoringSurfaceCoverage(): CapabilityCoverageReport | null {
|
|
20
20
|
const facet = projectAdapterFacet();
|
|
@@ -38,8 +38,8 @@ import {
|
|
|
38
38
|
type ProjectRootFact,
|
|
39
39
|
type ProjectVerbFacts,
|
|
40
40
|
} from '../host/coverage/project-verb-coverage';
|
|
41
|
-
import { editorServerJson } from '@volter/editor-
|
|
42
|
-
import { getCurrentProject } from '@volter/editor-
|
|
41
|
+
import { editorServerJson } from '@volter/editor-sdk/kit/editor-server-response';
|
|
42
|
+
import { getCurrentProject } from '@volter/editor-sdk/kit/active-project';
|
|
43
43
|
|
|
44
44
|
/** What the server answers on `/__editor/project-verbs` — facts only. */
|
|
45
45
|
interface ProjectFileFacts {
|
|
@@ -28,17 +28,17 @@
|
|
|
28
28
|
* for the mounts that have no contract to read.
|
|
29
29
|
*/
|
|
30
30
|
|
|
31
|
-
import { getActiveSystems } from '@volter/editor-
|
|
32
|
-
import { mountedRootSubjects } from '@volter/editor-
|
|
31
|
+
import { getActiveSystems } from '@volter/editor-sdk/kit/authoring/active-systems';
|
|
32
|
+
import { mountedRootSubjects } from '@volter/editor-sdk/kit/authoring/mounted-root-subjects';
|
|
33
33
|
import {
|
|
34
34
|
type CapabilityCoverageReport,
|
|
35
35
|
deriveCapabilityCoverage,
|
|
36
36
|
} from '../host/coverage/capability-coverage';
|
|
37
37
|
import { measureNativeSystemAdapters } from '../host/coverage/system-adapter-coverage';
|
|
38
|
-
import { liveCoverage } from '@volter/editor-
|
|
39
|
-
import { getCurrentProject } from '@volter/editor-
|
|
40
|
-
import { toolContributionPlay } from '@volter/editor-
|
|
41
|
-
import type { DeclaredSystemAbsence, Game } from '
|
|
38
|
+
import { liveCoverage } from '@volter/editor-sdk/kit/live-session-registry';
|
|
39
|
+
import { getCurrentProject } from '@volter/editor-sdk/kit/active-project';
|
|
40
|
+
import { toolContributionPlay } from '@volter/editor-sdk/kit/tool-contribution-play';
|
|
41
|
+
import type { DeclaredSystemAbsence, Game } from '../runtime/game';
|
|
42
42
|
import { projectDependencyNames } from './live-project-verbs';
|
|
43
43
|
|
|
44
44
|
/**
|
|
@@ -36,7 +36,7 @@ import { measureAdapter } from '../host/adapter-reach';
|
|
|
36
36
|
import {
|
|
37
37
|
type MountedRootSubject,
|
|
38
38
|
mountedRootSubjects,
|
|
39
|
-
} from '@volter/editor-
|
|
39
|
+
} from '@volter/editor-sdk/kit/authoring/mounted-root-subjects';
|
|
40
40
|
import {
|
|
41
41
|
type CapabilityCoverageReport,
|
|
42
42
|
deriveCapabilityCoverage,
|