@vgai/engine 0.5.15 → 0.5.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapter/adapter-module.d.ts +229 -33
- package/dist/adapter/adapter-module.d.ts.map +1 -1
- package/dist/adapter/adapter-module.js +222 -50
- package/dist/adapter/authoring-seam-contract.d.ts +530 -0
- package/dist/adapter/authoring-seam-contract.d.ts.map +1 -0
- package/dist/adapter/authoring-seam-contract.js +162 -0
- package/dist/adapter/authoring.d.ts +237 -50
- package/dist/adapter/authoring.d.ts.map +1 -1
- package/dist/adapter/authoring.js +2 -1
- package/dist/adapter/entrypoint-selection-source.d.ts +35 -0
- package/dist/adapter/entrypoint-selection-source.d.ts.map +1 -0
- package/dist/adapter/entrypoint-selection-source.js +151 -0
- package/dist/adapter/finders/scenes-from-entrypoint-selection.d.ts +7 -3
- package/dist/adapter/finders/scenes-from-entrypoint-selection.d.ts.map +1 -1
- package/dist/adapter/finders/scenes-from-entrypoint-selection.js +145 -27
- package/dist/adapter/first-party-systems.d.ts +13 -3
- package/dist/adapter/first-party-systems.d.ts.map +1 -1
- package/dist/adapter/first-party-systems.js +10 -3
- package/dist/adapter/index.d.ts +7 -2
- package/dist/adapter/index.d.ts.map +1 -1
- package/dist/adapter/index.js +4 -0
- package/dist/adapter/ingest/contract-system-adapters.d.ts +1 -1
- package/dist/adapter/ingest/contract-system-adapters.d.ts.map +1 -1
- package/dist/adapter/ingest/contract-system-adapters.js +13 -25
- package/dist/adapter/ingest/game-contract-seams.d.ts +214 -0
- package/dist/adapter/ingest/game-contract-seams.d.ts.map +1 -0
- package/dist/adapter/ingest/game-contract-seams.js +62 -0
- package/dist/adapter/ingest/game-contract.d.ts +5 -3
- package/dist/adapter/ingest/game-contract.d.ts.map +1 -1
- package/dist/adapter/ingest/game-contract.js +4 -1
- package/dist/adapter/ingest/merge-debug-adapters.d.ts +52 -0
- package/dist/adapter/ingest/merge-debug-adapters.d.ts.map +1 -0
- package/dist/adapter/ingest/merge-debug-adapters.js +141 -0
- package/dist/adapter/ingest/observation-debug-adapter.d.ts +56 -0
- package/dist/adapter/ingest/observation-debug-adapter.d.ts.map +1 -0
- package/dist/adapter/ingest/observation-debug-adapter.js +131 -0
- package/dist/adapter/ingest/scene-capture.d.ts +38 -13
- package/dist/adapter/ingest/scene-capture.d.ts.map +1 -1
- package/dist/adapter/ingest/scene-capture.js +101 -24
- package/dist/adapter/ingest/visible-capture-window.d.ts +22 -5
- package/dist/adapter/ingest/visible-capture-window.d.ts.map +1 -1
- package/dist/adapter/ingest/visible-capture-window.js +46 -7
- package/dist/adapter/native-debug-module.d.ts +43 -0
- package/dist/adapter/native-debug-module.d.ts.map +1 -0
- package/dist/adapter/native-debug-module.js +230 -0
- package/dist/adapter/object3d-authoring-subject.d.ts +32 -0
- package/dist/adapter/object3d-authoring-subject.d.ts.map +1 -0
- package/dist/adapter/object3d-authoring-subject.js +25 -0
- package/dist/adapter/root-adapter.d.ts +25 -56
- package/dist/adapter/root-adapter.d.ts.map +1 -1
- package/dist/adapter/root-adapter.js +4 -20
- package/dist/adapter/root-seam-contract.d.ts +129 -0
- package/dist/adapter/root-seam-contract.d.ts.map +1 -0
- package/dist/adapter/root-seam-contract.js +37 -0
- package/dist/adapter/seam-evidence.d.ts +118 -0
- package/dist/adapter/seam-evidence.d.ts.map +1 -0
- package/dist/adapter/seam-evidence.js +231 -0
- package/dist/adapter/setup-three-root-adapter.d.ts +2 -3
- package/dist/adapter/setup-three-root-adapter.d.ts.map +1 -1
- package/dist/adapter/setup-three-root-adapter.js +11 -12
- package/dist/adapter/system-adapter.d.ts +6 -1
- package/dist/adapter/system-adapter.d.ts.map +1 -1
- package/dist/adapter/system-seam-contract.d.ts +354 -0
- package/dist/adapter/system-seam-contract.d.ts.map +1 -0
- package/dist/adapter/system-seam-contract.js +95 -0
- package/dist/animation/xstate-animation-binding.d.ts +6 -0
- package/dist/animation/xstate-animation-binding.d.ts.map +1 -1
- package/dist/animation/xstate-animation-binding.js +142 -4
- package/dist/asset-formats/collider.d.ts +1 -1
- package/dist/asset-formats/mesh.d.ts +2 -2
- package/dist/asset-formats/particles.d.ts +1 -1
- package/dist/canvas-react/engine-bridge.d.ts +6 -6
- package/dist/canvas-react/engine-bridge.d.ts.map +1 -1
- package/dist/canvas-react/engine-bridge.js +2 -2
- package/dist/canvas-react/index.d.ts +2 -2
- package/dist/canvas-react/index.d.ts.map +1 -1
- package/dist/canvas-react/index.js +1 -1
- package/dist/canvas-react/pixi-react-adapter.d.ts +15 -9
- package/dist/canvas-react/pixi-react-adapter.d.ts.map +1 -1
- package/dist/canvas-react/pixi-react-adapter.js +129 -64
- package/dist/canvas-react/pixi-react-root-factory.d.ts.map +1 -1
- package/dist/canvas-react/pixi-react-root-factory.js +4 -0
- package/dist/canvas-react/world-context.d.ts +22 -12
- package/dist/canvas-react/world-context.d.ts.map +1 -1
- package/dist/canvas-react/world-context.js +12 -51
- package/dist/data/curve.d.ts +1 -1
- package/dist/data/curve.js +2 -2
- package/dist/dev/instruments.d.ts +4 -4
- package/dist/dev/instruments.js +4 -4
- package/dist/dev/register-render-vitals.d.ts +2 -2
- package/dist/dev/register-render-vitals.js +2 -2
- package/dist/dev/static-batch-advisor.d.ts +1 -1
- package/dist/dev/static-batch-advisor.js +2 -2
- package/dist/ecs/user-data.d.ts +5 -0
- package/dist/ecs/user-data.d.ts.map +1 -1
- package/dist/ecs/user-data.js +3 -0
- package/dist/input/input-manager.d.ts +64 -0
- package/dist/input/input-manager.d.ts.map +1 -1
- package/dist/input/input-manager.js +149 -0
- package/dist/manifest/load.d.ts +0 -3
- package/dist/manifest/load.d.ts.map +1 -1
- package/dist/manifest/load.js +0 -4
- package/dist/manifest/schema.d.ts +9 -11
- package/dist/manifest/schema.d.ts.map +1 -1
- package/dist/manifest/schema.js +2 -23
- package/dist/pixi/authoring.d.ts +33 -0
- package/dist/pixi/authoring.d.ts.map +1 -1
- package/dist/pixi/authoring.js +301 -2
- package/dist/pixi/ingest.d.ts +2 -1
- package/dist/pixi/ingest.d.ts.map +1 -1
- package/dist/pixi/ingest.js +4 -2
- package/dist/pixi/scene-capture.d.ts +9 -1
- package/dist/pixi/scene-capture.d.ts.map +1 -1
- package/dist/pixi/scene-capture.js +9 -6
- package/dist/react/world-state.d.ts +12 -0
- package/dist/react/world-state.d.ts.map +1 -1
- package/dist/react/world-state.js +14 -0
- package/dist/render/particles-factory.d.ts +13 -6
- package/dist/render/particles-factory.d.ts.map +1 -1
- package/dist/render/particles-factory.js +6 -3
- package/dist/render/soft-particle-depth.d.ts +3 -3
- package/dist/render/soft-particle-depth.d.ts.map +1 -1
- package/dist/render/soft-particle-depth.js +8 -13
- package/dist/runtime/create-runtime.d.ts +25 -62
- package/dist/runtime/create-runtime.d.ts.map +1 -1
- package/dist/runtime/create-runtime.js +58 -105
- package/dist/runtime/dev-build.d.ts +37 -0
- package/dist/runtime/dev-build.d.ts.map +1 -0
- package/dist/runtime/dev-build.js +49 -0
- package/dist/runtime/game.d.ts +38 -72
- package/dist/runtime/game.d.ts.map +1 -1
- package/dist/runtime/game.js +40 -55
- package/dist/runtime/mount-game.d.ts.map +1 -1
- package/dist/runtime/mount-game.js +12 -1
- package/dist/runtime/mount-manifest.d.ts +0 -13
- package/dist/runtime/mount-manifest.d.ts.map +1 -1
- package/dist/runtime/mount-manifest.js +5 -25
- package/dist/runtime/types.d.ts +18 -0
- package/dist/runtime/types.d.ts.map +1 -1
- package/dist/setup/setup-renderer.d.ts +3 -4
- package/dist/setup/setup-renderer.d.ts.map +1 -1
- package/dist/setup/setup-renderer.js +3 -4
- package/dist/world3d-react/engine-bridge.d.ts +1 -1
- package/dist/world3d-react/engine-bridge.d.ts.map +1 -1
- package/dist/world3d-react/engine-bridge.js +29 -4
- package/dist/world3d-react/index.d.ts +1 -1
- package/dist/world3d-react/index.js +1 -1
- package/dist/world3d-react/r3f-adapter.d.ts +7 -0
- package/dist/world3d-react/r3f-adapter.d.ts.map +1 -1
- package/dist/world3d-react/r3f-adapter.js +132 -28
- package/dist/world3d-react/r3f-root-factory.d.ts.map +1 -1
- package/dist/world3d-react/r3f-root-factory.js +14 -0
- package/dist/world3d-react/rapier-physics-bridge.d.ts +4 -4
- package/dist/world3d-react/rapier-physics-bridge.js +3 -3
- package/dist/world3d-react/world-context.d.ts +52 -3
- package/dist/world3d-react/world-context.d.ts.map +1 -1
- package/dist/world3d-react/world-context.js +94 -64
- package/dist-config/config.js.map +1 -1
- package/package.json +1 -1
- package/schemas/engine-capabilities.json +8 -8
- package/schemas/vgai-project.schema.json +1 -6
- package/src/adapter/adapter-module.ts +405 -72
- package/src/adapter/authoring-seam-contract.ts +211 -0
- package/src/adapter/authoring.ts +242 -50
- package/src/adapter/entrypoint-selection-source.ts +159 -0
- package/src/adapter/finders/scenes-from-entrypoint-selection.ts +162 -36
- package/src/adapter/first-party-systems.ts +17 -3
- package/src/adapter/index.ts +51 -1
- package/src/adapter/ingest/contract-system-adapters.ts +26 -30
- package/src/adapter/ingest/game-contract-seams.ts +85 -0
- package/src/adapter/ingest/game-contract.ts +9 -4
- package/src/adapter/ingest/merge-debug-adapters.ts +193 -0
- package/src/adapter/ingest/observation-debug-adapter.ts +158 -0
- package/src/adapter/ingest/scene-capture.ts +126 -23
- package/src/adapter/ingest/visible-capture-window.ts +63 -9
- package/src/adapter/native-debug-module.ts +307 -0
- package/src/adapter/object3d-authoring-subject.ts +50 -0
- package/src/adapter/root-adapter.ts +25 -56
- package/src/adapter/root-seam-contract.ts +57 -0
- package/src/adapter/seam-evidence.ts +336 -0
- package/src/adapter/setup-three-root-adapter.ts +13 -15
- package/src/adapter/system-adapter.ts +6 -1
- package/src/adapter/system-seam-contract.ts +121 -0
- package/src/animation/xstate-animation-binding.ts +151 -4
- package/src/canvas-react/engine-bridge.ts +6 -6
- package/src/canvas-react/index.ts +2 -1
- package/src/canvas-react/pixi-react-adapter.tsx +143 -77
- package/src/canvas-react/pixi-react-root-factory.tsx +4 -0
- package/src/canvas-react/world-context.ts +26 -101
- package/src/data/curve.ts +2 -2
- package/src/dev/instruments.ts +4 -4
- package/src/dev/register-render-vitals.ts +2 -2
- package/src/dev/static-batch-advisor.ts +2 -2
- package/src/ecs/user-data.ts +5 -0
- package/src/input/input-manager.ts +145 -0
- package/src/manifest/load.ts +0 -7
- package/src/manifest/schema.ts +2 -25
- package/src/pixi/authoring.ts +329 -2
- package/src/pixi/ingest.ts +9 -2
- package/src/pixi/scene-capture.ts +20 -5
- package/src/react/world-state.tsx +15 -0
- package/src/render/particles-factory.ts +16 -6
- package/src/render/soft-particle-depth.ts +8 -13
- package/src/runtime/create-runtime.ts +79 -158
- package/src/runtime/dev-build.ts +49 -0
- package/src/runtime/game.ts +72 -113
- package/src/runtime/mount-game.ts +19 -1
- package/src/runtime/mount-manifest.ts +5 -37
- package/src/runtime/types.ts +22 -0
- package/src/setup/setup-renderer.ts +3 -4
- package/src/world3d-react/engine-bridge.ts +29 -4
- package/src/world3d-react/index.ts +1 -1
- package/src/world3d-react/r3f-adapter.tsx +140 -31
- package/src/world3d-react/r3f-root-factory.tsx +14 -0
- package/src/world3d-react/rapier-physics-bridge.tsx +4 -4
- package/src/world3d-react/world-context.ts +111 -87
- package/dist/runtime/dev-layers.d.ts +0 -34
- package/dist/runtime/dev-layers.d.ts.map +0 -1
- package/dist/runtime/dev-layers.js +0 -46
- package/src/runtime/dev-layers.ts +0 -46
|
@@ -4,7 +4,10 @@ import type { createSystemRunner } from '../core/system-runner';
|
|
|
4
4
|
import { deleteUserData, getUserData, setUserData } from '../ecs/user-data';
|
|
5
5
|
import type { BlendTreeDef } from './anim-graph-types';
|
|
6
6
|
import { evaluateBlendTree } from './blend-node';
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
type AnimationPreviewTransport,
|
|
9
|
+
attachAnimationRuntimeInspection,
|
|
10
|
+
} from './runtime-inspection';
|
|
8
11
|
import {
|
|
9
12
|
type AnimationBoneMask,
|
|
10
13
|
type AnimationMetaStateNodeLike,
|
|
@@ -109,6 +112,12 @@ export interface XStateAnimationBindingOptions {
|
|
|
109
112
|
* omitted, the caller owns ticking (useful for standalone Three tests).
|
|
110
113
|
*/
|
|
111
114
|
systems?: Pick<ReturnType<typeof createSystemRunner>, 'add' | 'remove'>;
|
|
115
|
+
/**
|
|
116
|
+
* Publish a seekable transport for an isolated design-time mount such as a
|
|
117
|
+
* CSF story. Never enable this on the live game binding: runtime inspection
|
|
118
|
+
* stays read-only there, while the story deliberately owns this preview.
|
|
119
|
+
*/
|
|
120
|
+
previewTransport?: boolean;
|
|
112
121
|
}
|
|
113
122
|
|
|
114
123
|
export interface XStateAnimationLayerState {
|
|
@@ -368,6 +377,89 @@ export function bindXStateAnimation(
|
|
|
368
377
|
const activeLayers = new Map<string, ActiveState>();
|
|
369
378
|
let disposed = false;
|
|
370
379
|
let detachRuntimeInspection = () => {};
|
|
380
|
+
let previewClip: string | null = null;
|
|
381
|
+
let previewAction: THREE.AnimationAction | null = null;
|
|
382
|
+
let previewPlaying = true;
|
|
383
|
+
let previewLoop = true;
|
|
384
|
+
let previewSpeed = 1;
|
|
385
|
+
|
|
386
|
+
function controllerPreviewSubject(): {
|
|
387
|
+
clip: string;
|
|
388
|
+
action: THREE.AnimationAction | null;
|
|
389
|
+
} {
|
|
390
|
+
const layers = [activeLayers.get('base'), ...activeLayers.values()].filter(
|
|
391
|
+
(active, index, all): active is ActiveState =>
|
|
392
|
+
Boolean(active) && all.indexOf(active) === index,
|
|
393
|
+
);
|
|
394
|
+
for (const active of layers) {
|
|
395
|
+
const weighted = [...active.natural].sort((left, right) => {
|
|
396
|
+
const leftWeight = actionCache.get(left.actionKey)?.action.getEffectiveWeight() ?? 0;
|
|
397
|
+
const rightWeight = actionCache.get(right.actionKey)?.action.getEffectiveWeight() ?? 0;
|
|
398
|
+
return rightWeight - leftWeight;
|
|
399
|
+
})[0];
|
|
400
|
+
if (weighted) {
|
|
401
|
+
return {
|
|
402
|
+
clip: weighted.clip,
|
|
403
|
+
action: actionCache.get(weighted.actionKey)?.action ?? null,
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
return { clip: clips.keys().next().value ?? '', action: null };
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
function actionIsControllerOwned(action: THREE.AnimationAction): boolean {
|
|
411
|
+
for (const cached of actionCache.values()) {
|
|
412
|
+
if (cached.action === action) return true;
|
|
413
|
+
}
|
|
414
|
+
return false;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
function releasePreviewAction(): void {
|
|
418
|
+
const action = previewAction;
|
|
419
|
+
if (!action) return;
|
|
420
|
+
action.stop();
|
|
421
|
+
if (!actionIsControllerOwned(action)) {
|
|
422
|
+
mixer.uncacheAction(action.getClip(), action.getRoot());
|
|
423
|
+
}
|
|
424
|
+
previewAction = null;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
function enterClipPreview(name: string): void {
|
|
428
|
+
const clip = clips.get(name);
|
|
429
|
+
if (!clip) return;
|
|
430
|
+
releasePreviewAction();
|
|
431
|
+
previewClip = name;
|
|
432
|
+
for (const cached of actionCache.values()) cached.action.setEffectiveWeight(0);
|
|
433
|
+
const action = mixer.clipAction(clip).reset().play();
|
|
434
|
+
action.setEffectiveWeight(1);
|
|
435
|
+
action.setEffectiveTimeScale(1);
|
|
436
|
+
action.setLoop(previewLoop ? THREE.LoopRepeat : THREE.LoopOnce, previewLoop ? Infinity : 1);
|
|
437
|
+
action.clampWhenFinished = !previewLoop;
|
|
438
|
+
previewAction = action;
|
|
439
|
+
mixer.update(0);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
function restoreControllerPreview(): void {
|
|
443
|
+
releasePreviewAction();
|
|
444
|
+
previewClip = null;
|
|
445
|
+
const snapshot = actor.getSnapshot();
|
|
446
|
+
reconcile(snapshot);
|
|
447
|
+
const context = snapshot.context;
|
|
448
|
+
for (const cached of actionCache.values()) cached.action.setEffectiveWeight(0);
|
|
449
|
+
for (const active of activeLayers.values()) {
|
|
450
|
+
active.crossfadeRemaining = 0;
|
|
451
|
+
active.natural = weightsOf(active.stateId, active.meta, context);
|
|
452
|
+
const loop = isBlendTreeAnimationMeta(active.meta) ? true : active.meta.loop;
|
|
453
|
+
const speed = isBlendTreeAnimationMeta(active.meta) ? 1 : active.meta.speed;
|
|
454
|
+
const layerWeight = layerWeightOf(active.meta);
|
|
455
|
+
for (const weighted of active.natural) {
|
|
456
|
+
ensureAction(weighted.actionKey, loop, speed, layerWeight)
|
|
457
|
+
.play()
|
|
458
|
+
.setEffectiveWeight(weighted.weight * layerWeight);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
mixer.update(0);
|
|
462
|
+
}
|
|
371
463
|
|
|
372
464
|
function silence(weights: WeightedClip[], except: string | undefined, duration: number): void {
|
|
373
465
|
for (const weighted of weights) {
|
|
@@ -465,23 +557,76 @@ export function bindXStateAnimation(
|
|
|
465
557
|
const initialSnapshot = actor.getSnapshot();
|
|
466
558
|
reconcile(initialSnapshot);
|
|
467
559
|
const subscription = actor.subscribe((snapshot) => {
|
|
468
|
-
|
|
560
|
+
// Isolated clip review deliberately holds behavior output. The actor may
|
|
561
|
+
// keep receiving story args, but its latest state is reconciled only when
|
|
562
|
+
// the user returns to Controller.
|
|
563
|
+
if (!disposed && !previewClip) reconcile(snapshot);
|
|
469
564
|
});
|
|
470
565
|
|
|
471
566
|
function tick(dt: number): void {
|
|
472
567
|
if (disposed) return;
|
|
568
|
+
if (options.previewTransport && !previewPlaying) return;
|
|
569
|
+
const elapsed = dt * (options.previewTransport ? previewSpeed : 1);
|
|
570
|
+
if (options.previewTransport && previewClip && previewAction) {
|
|
571
|
+
mixer.update(elapsed);
|
|
572
|
+
const clip = clips.get(previewClip);
|
|
573
|
+
if (!previewLoop && clip && previewAction.time >= clip.duration) previewPlaying = false;
|
|
574
|
+
return;
|
|
575
|
+
}
|
|
473
576
|
const context = actor.getSnapshot().context;
|
|
474
577
|
for (const active of activeLayers.values()) {
|
|
475
578
|
if (active.crossfadeRemaining > 0) {
|
|
476
|
-
active.crossfadeRemaining = Math.max(0, active.crossfadeRemaining -
|
|
579
|
+
active.crossfadeRemaining = Math.max(0, active.crossfadeRemaining - elapsed);
|
|
477
580
|
if (active.crossfadeRemaining <= 0) applyLiveBlendWeights(active, context);
|
|
478
581
|
} else {
|
|
479
582
|
applyLiveBlendWeights(active, context);
|
|
480
583
|
}
|
|
481
584
|
}
|
|
482
|
-
mixer.update(
|
|
585
|
+
mixer.update(elapsed);
|
|
483
586
|
}
|
|
484
587
|
|
|
588
|
+
const previewTransport: AnimationPreviewTransport | undefined = options.previewTransport
|
|
589
|
+
? {
|
|
590
|
+
getSnapshot: () => {
|
|
591
|
+
const controller = controllerPreviewSubject();
|
|
592
|
+
return {
|
|
593
|
+
clip: previewClip ?? controller.clip,
|
|
594
|
+
time: previewClip ? (previewAction?.time ?? 0) : (controller.action?.time ?? 0),
|
|
595
|
+
playing: previewPlaying,
|
|
596
|
+
loop: previewLoop,
|
|
597
|
+
speed: previewSpeed,
|
|
598
|
+
mode: previewClip ? 'clip' : 'controller',
|
|
599
|
+
};
|
|
600
|
+
},
|
|
601
|
+
setPlaying(playing) {
|
|
602
|
+
previewPlaying = playing;
|
|
603
|
+
},
|
|
604
|
+
setLoop(loop) {
|
|
605
|
+
previewLoop = loop;
|
|
606
|
+
if (!previewClip) enterClipPreview(controllerPreviewSubject().clip);
|
|
607
|
+
previewAction?.setLoop(loop ? THREE.LoopRepeat : THREE.LoopOnce, loop ? Infinity : 1);
|
|
608
|
+
if (previewAction) previewAction.clampWhenFinished = !loop;
|
|
609
|
+
},
|
|
610
|
+
setSpeed(speed) {
|
|
611
|
+
previewSpeed = THREE.MathUtils.clamp(speed, 0.05, 4);
|
|
612
|
+
},
|
|
613
|
+
seek(time) {
|
|
614
|
+
if (!previewClip) enterClipPreview(controllerPreviewSubject().clip);
|
|
615
|
+
const clip = previewClip ? clips.get(previewClip) : undefined;
|
|
616
|
+
if (!clip || !previewAction) return;
|
|
617
|
+
previewAction.time = THREE.MathUtils.clamp(time, 0, Math.max(clip.duration, 0));
|
|
618
|
+
mixer.update(0);
|
|
619
|
+
},
|
|
620
|
+
setClip(name) {
|
|
621
|
+
if (name === null) {
|
|
622
|
+
if (previewClip) restoreControllerPreview();
|
|
623
|
+
return;
|
|
624
|
+
}
|
|
625
|
+
enterClipPreview(name);
|
|
626
|
+
},
|
|
627
|
+
}
|
|
628
|
+
: undefined;
|
|
629
|
+
|
|
485
630
|
const binding: XStateAnimationBinding = {
|
|
486
631
|
tick,
|
|
487
632
|
actor,
|
|
@@ -503,6 +648,7 @@ export function bindXStateAnimation(
|
|
|
503
648
|
if (disposed) return;
|
|
504
649
|
disposed = true;
|
|
505
650
|
detachRuntimeInspection();
|
|
651
|
+
releasePreviewAction();
|
|
506
652
|
options.systems?.remove('animation', tick);
|
|
507
653
|
subscription.unsubscribe();
|
|
508
654
|
for (const { action, clip } of actionCache.values()) {
|
|
@@ -520,6 +666,7 @@ export function bindXStateAnimation(
|
|
|
520
666
|
detachRuntimeInspection = attachAnimationRuntimeInspection(owner, {
|
|
521
667
|
mixer,
|
|
522
668
|
clips,
|
|
669
|
+
...(previewTransport ? { previewTransport } : {}),
|
|
523
670
|
getSnapshot: () => ({
|
|
524
671
|
layers: [...activeLayers.values()].map((active) => {
|
|
525
672
|
const parameters = readParameters(actor.getSnapshot().context);
|
|
@@ -14,20 +14,20 @@
|
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
import { createContext, useContext } from 'react';
|
|
17
|
-
import type {
|
|
17
|
+
import type { CanvasGameContext } from './world-context';
|
|
18
18
|
|
|
19
19
|
export interface EngineBridgeValue {
|
|
20
|
-
/** This root's `
|
|
20
|
+
/** This root's `CanvasGameContext` — `input`, `debug`, `random`, `clock`, `game`,
|
|
21
21
|
* `roots`, `systems`, `registerSystemAdapter`. Present so ordinary
|
|
22
22
|
* components can reach engine services with a HOOK (see
|
|
23
23
|
* {@link useGameContext}). */
|
|
24
|
-
readonly ctx?:
|
|
24
|
+
readonly ctx?: CanvasGameContext | undefined;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
export const EngineBridge = createContext<EngineBridgeValue | null>(null);
|
|
28
28
|
|
|
29
29
|
/**
|
|
30
|
-
* This canvas root's `
|
|
30
|
+
* This canvas root's `CanvasGameContext`, for use inside any component in the tree.
|
|
31
31
|
*
|
|
32
32
|
* An ordinary component does what a model would write:
|
|
33
33
|
*
|
|
@@ -36,7 +36,7 @@ export const EngineBridge = createContext<EngineBridgeValue | null>(null);
|
|
|
36
36
|
* const { input } = useGameContext();
|
|
37
37
|
* const ref = useRef<Sprite>(null);
|
|
38
38
|
* useTick(({ deltaMS }) => {
|
|
39
|
-
* if (input
|
|
39
|
+
* if (input?.isPressed('move_right')) ref.current!.x += speed * (deltaMS / 1000);
|
|
40
40
|
* });
|
|
41
41
|
* return <pixiSprite ref={ref} texture={Texture.WHITE} />;
|
|
42
42
|
* }
|
|
@@ -46,7 +46,7 @@ export const EngineBridge = createContext<EngineBridgeValue | null>(null);
|
|
|
46
46
|
* `createPixiReactAdapter` — absent context is a wiring bug, and a silent
|
|
47
47
|
* `null` would surface as a mystery `undefined` deep in a tick callback.
|
|
48
48
|
*/
|
|
49
|
-
export function useGameContext():
|
|
49
|
+
export function useGameContext(): CanvasGameContext {
|
|
50
50
|
const bridge = useContext(EngineBridge);
|
|
51
51
|
if (!bridge?.ctx) {
|
|
52
52
|
throw new Error(
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
* - `PixiPrimitive` / `adoptNow` — the canvas surface's `<primitive object={…}>`:
|
|
23
23
|
* render a container the game already owns, and place a spawn in the
|
|
24
24
|
* display tree ahead of the commit that renders it.
|
|
25
|
-
* - `useGameContext()` — this root's `
|
|
25
|
+
* - `useGameContext()` — this root's `CanvasGameContext` (`input`, `debug`,
|
|
26
26
|
* `random`, `clock`, `game`, `roots`) from inside any component in the tree.
|
|
27
27
|
* Behavior in a canvas world is components + hooks, never a class under a
|
|
28
28
|
* string key.
|
|
@@ -43,6 +43,7 @@ export {
|
|
|
43
43
|
} from './pixi-react-adapter';
|
|
44
44
|
export { pixiReactRootFactory, resolveCanvasEntryAdapter } from './pixi-react-root-factory';
|
|
45
45
|
export {
|
|
46
|
+
type CanvasGameContext,
|
|
46
47
|
type CanvasRootContextOptions,
|
|
47
48
|
type CanvasRootRuntime,
|
|
48
49
|
createCanvasRootContext,
|
|
@@ -6,20 +6,20 @@
|
|
|
6
6
|
* this returns a `MountedPixiRoot` (`../adapter/root-adapter.ts`) whose `stage`
|
|
7
7
|
* is the REAL `PIXI.Container` the reconciler renders into.
|
|
8
8
|
*
|
|
9
|
-
* This is the canvas surface's
|
|
9
|
+
* This is the canvas surface's sibling of `world3d-react`'s
|
|
10
10
|
* `createR3FAdapter`, with `@pixi/react` where that one has
|
|
11
|
-
* react-three-fiber.
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
11
|
+
* react-three-fiber. The explicit factory still supplies the engine context
|
|
12
|
+
* used by the current first-party Canvas examples while their app-owned input
|
|
13
|
+
* and debugger migration remains tracked in `docs/WORK.md`; `useTick` hooks
|
|
14
|
+
* stay registered on Pixi's real ticker throughout that migration.
|
|
15
15
|
*
|
|
16
16
|
* ## The loop contract
|
|
17
17
|
*
|
|
18
18
|
* `Application` is initialized with `autoStart: false` and `sharedTicker:
|
|
19
|
-
* false`, so it owns a ticker that is NEVER started and therefore
|
|
20
|
-
* requests an animation frame. `mounted.update(dt)` is the only thing
|
|
21
|
-
* advances
|
|
22
|
-
* world freeze exactly when the host freezes it, and what makes
|
|
19
|
+
* false`, so it owns a native Pixi ticker that is NEVER started and therefore
|
|
20
|
+
* never requests an animation frame. `mounted.update(dt)` is the only thing
|
|
21
|
+
* that advances that ticker, and it feeds GAME time, not wall time — which is
|
|
22
|
+
* what makes the world freeze exactly when the host freezes it, and what makes
|
|
23
23
|
* `game.waitSimTime` work in a hidden tab (a hidden tab has no rAF; the engine
|
|
24
24
|
* loop's own catch-up still calls `update`, and Pixi has no independent clock
|
|
25
25
|
* to disagree with it).
|
|
@@ -38,9 +38,11 @@
|
|
|
38
38
|
import { createRoot, extend } from '@pixi/react';
|
|
39
39
|
import type { Application, ApplicationOptions } from 'pixi.js';
|
|
40
40
|
import * as PIXI from 'pixi.js';
|
|
41
|
-
import { createElement, type ReactNode, useLayoutEffect } from 'react';
|
|
41
|
+
import { createElement, Fragment, type ReactNode, useEffect, useLayoutEffect } from 'react';
|
|
42
42
|
import type { CanvasHostContext, MountedPixiRoot, RootAdapter } from '../adapter';
|
|
43
|
+
import type { SystemAdapters } from '../adapter/system-adapter';
|
|
43
44
|
import { WorldProvider } from '../react/world-state';
|
|
45
|
+
import { getDebugRegistry } from '../runtime/debug-registry';
|
|
44
46
|
import { EngineBridge, type EngineBridgeValue } from './engine-bridge';
|
|
45
47
|
import { createCanvasRootContext } from './world-context';
|
|
46
48
|
|
|
@@ -77,6 +79,12 @@ export interface CreatePixiReactAdapterOptions {
|
|
|
77
79
|
* actions). A missing/invalid map degrades loudly without failing the
|
|
78
80
|
* mount — see `world-context.ts`. */
|
|
79
81
|
readonly inputMapPath?: string | null | undefined;
|
|
82
|
+
/**
|
|
83
|
+
* Transitional compatibility for component trees that still consume the
|
|
84
|
+
* engine context. Native trees opt out once their input, observation and
|
|
85
|
+
* debugger state are application-owned.
|
|
86
|
+
*/
|
|
87
|
+
readonly engineRuntime?: boolean | undefined;
|
|
80
88
|
/**
|
|
81
89
|
* Extra `Application.init` options merged UNDER this adapter's own — e.g.
|
|
82
90
|
* `{ antialias: false }`, `{ roundPixels: true }`. The loop-critical ones
|
|
@@ -98,21 +106,21 @@ export interface CreatePixiReactAdapterOptions {
|
|
|
98
106
|
export function createPixiReactAdapter(
|
|
99
107
|
options: CreatePixiReactAdapterOptions,
|
|
100
108
|
): RootAdapter<'canvas'> {
|
|
101
|
-
const { id, content, inputMapPath, applicationOptions } = options;
|
|
109
|
+
const { id, content, inputMapPath, engineRuntime = true, applicationOptions } = options;
|
|
102
110
|
|
|
103
111
|
return {
|
|
104
112
|
id,
|
|
105
113
|
|
|
106
114
|
async mount(host: CanvasHostContext): Promise<MountedPixiRoot> {
|
|
107
|
-
const runtime = createCanvasRootContext(host, { id, inputMapPath });
|
|
108
|
-
const bridge: EngineBridgeValue = { ctx: runtime.ctx };
|
|
115
|
+
const runtime = engineRuntime ? createCanvasRootContext(host, { id, inputMapPath }) : null;
|
|
116
|
+
const bridge: EngineBridgeValue | null = runtime ? { ctx: runtime.ctx } : null;
|
|
109
117
|
// Actions must exist before any component's first tick reads them — wait
|
|
110
118
|
// for the (never-rejecting) input-map load before the first commit.
|
|
111
|
-
await runtime
|
|
119
|
+
await runtime?.inputMapReady;
|
|
112
120
|
|
|
113
121
|
const canvas = host.canvas;
|
|
114
122
|
if (MOUNTED_CANVASES.has(canvas)) {
|
|
115
|
-
runtime
|
|
123
|
+
runtime?.dispose();
|
|
116
124
|
throw new Error(
|
|
117
125
|
`createPixiReactAdapter: root "${id}" was handed a canvas this lane has already ` +
|
|
118
126
|
'mounted. @pixi/react keys its reconciler roots off the canvas element, so re-using ' +
|
|
@@ -129,6 +137,10 @@ export function createPixiReactAdapter(
|
|
|
129
137
|
const committed = new Promise<void>((resolve) => {
|
|
130
138
|
resolveCommitted = resolve;
|
|
131
139
|
});
|
|
140
|
+
let resolveEffectsReady!: () => void;
|
|
141
|
+
const effectsReady = new Promise<void>((resolve) => {
|
|
142
|
+
resolveEffectsReady = resolve;
|
|
143
|
+
});
|
|
132
144
|
let resolveUnmounted!: () => void;
|
|
133
145
|
const unmounted = new Promise<void>((resolve) => {
|
|
134
146
|
resolveUnmounted = resolve;
|
|
@@ -152,22 +164,39 @@ export function createPixiReactAdapter(
|
|
|
152
164
|
resolveCommitted();
|
|
153
165
|
return resolveUnmounted;
|
|
154
166
|
}, []);
|
|
167
|
+
// This component is the last sibling. Reaching its passive effect
|
|
168
|
+
// means the initial tree's useDebugProvider/useDebugCommand effects
|
|
169
|
+
// have registered, so mount() can truthfully back Play's ack.
|
|
170
|
+
useEffect(resolveEffectsReady, []);
|
|
155
171
|
return null;
|
|
156
172
|
}
|
|
157
173
|
|
|
158
|
-
const game = runtime.ctx.game;
|
|
159
174
|
// The React seam every dom root already has — `useWorldState`,
|
|
160
175
|
// `useDebugProvider`/`useDebugCommand`/`useDebugEmit`
|
|
161
176
|
// (`../react/world-state`) — works identically inside a canvas root
|
|
162
177
|
// because a canvas root IS React. `host.game` is genuinely absent in
|
|
163
178
|
// bare/foreign hosts, so the provider is conditional and those hooks
|
|
164
179
|
// stay the inert no-ops they already document themselves to be.
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
)
|
|
180
|
+
//
|
|
181
|
+
// READ FROM `host.game`, NEVER FROM `runtime` — the same rule, and the
|
|
182
|
+
// same reasoning, as the three lane's (`../world3d-react/r3f-adapter.tsx`,
|
|
183
|
+
// beside its own `const game = host.game`). The two providers answer
|
|
184
|
+
// different questions: `EngineBridge` rides `bridge` (the vgai runtime
|
|
185
|
+
// CONTEXT, which must not enter an ecosystem-native tree), `WorldProvider`
|
|
186
|
+
// rides the Game handle the host already holds. Keyed off `runtime`, a
|
|
187
|
+
// world mounted with `engineRuntime: false` gets no `WorldProvider` at
|
|
188
|
+
// all and every `useDebugProvider` in it no-ops IN SILENCE — no error, and
|
|
189
|
+
// `vgai eval 'await game.providers()'` lists only the built-ins. That is
|
|
190
|
+
// exactly what happened on the three lane. It has never fired here only
|
|
191
|
+
// because `pixi-react-root-factory.tsx` still leaves the runtime on;
|
|
192
|
+
// that flag is the tracked component-root migration's to flip, and this
|
|
193
|
+
// seam must not be waiting for it.
|
|
194
|
+
const game = host.game;
|
|
195
|
+
const world = createElement(Fragment, null, content, createElement(Lifecycle));
|
|
196
|
+
const provided = game ? createElement(WorldProvider, { game }, world) : world;
|
|
197
|
+
const element = bridge
|
|
198
|
+
? createElement(EngineBridge.Provider, { value: bridge }, provided)
|
|
199
|
+
: provided;
|
|
171
200
|
|
|
172
201
|
const root: PixiReactRoot = createRoot(canvas);
|
|
173
202
|
|
|
@@ -189,50 +218,74 @@ export function createPixiReactAdapter(
|
|
|
189
218
|
sharedTicker: false,
|
|
190
219
|
};
|
|
191
220
|
|
|
192
|
-
//
|
|
193
|
-
//
|
|
194
|
-
//
|
|
195
|
-
|
|
196
|
-
|
|
221
|
+
// Install the reconcile-error listener BEFORE `root.render`: Pixi hands
|
|
222
|
+
// the Application back after `init()` but React can already have thrown
|
|
223
|
+
// during that same turn. Attaching after the await loses the real error
|
|
224
|
+
// and turns it into a misleading ten-second timeout.
|
|
225
|
+
let reportReconcileFailure!: (why: string) => void;
|
|
226
|
+
const reconcileFailure = new Promise<string>((resolve) => {
|
|
227
|
+
reportReconcileFailure = resolve;
|
|
228
|
+
});
|
|
229
|
+
const onError = (event: ErrorEvent): void => {
|
|
230
|
+
reportReconcileFailure(
|
|
231
|
+
`the Pixi tree crashed before its first commit — ${event.message} ` +
|
|
232
|
+
'(mount() fails loudly instead of hanging)',
|
|
233
|
+
);
|
|
234
|
+
};
|
|
235
|
+
globalThis.addEventListener?.('error', onError);
|
|
236
|
+
|
|
237
|
+
let app: Application | undefined;
|
|
238
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
239
|
+
try {
|
|
240
|
+
// `root.render` is async: it awaits `Application.init()` (which is what
|
|
241
|
+
// creates the renderer) and only then hands the tree to the reconciler.
|
|
242
|
+
// A crash inside init rejects here, which is the loud failure we want.
|
|
243
|
+
const renderedApp = await root.render(element, initOptions);
|
|
244
|
+
app = renderedApp;
|
|
245
|
+
runtime?.setApplication(renderedApp);
|
|
197
246
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
}
|
|
210
|
-
runtime.dispose();
|
|
211
|
-
reject(new Error(`createPixiReactAdapter: root "${id}" — ${why}`));
|
|
212
|
-
};
|
|
213
|
-
const onError = (event: ErrorEvent) =>
|
|
214
|
-
fail(
|
|
215
|
-
`the Pixi tree crashed before its first commit — ${event.message} ` +
|
|
216
|
-
'(mount() fails loudly instead of hanging)',
|
|
247
|
+
// A reconcile-time crash (e.g. an un-extended component) surfaces as
|
|
248
|
+
// an uncaught window error and the first commit never lands. Race that
|
|
249
|
+
// exact signal against the commit and the last-resort ceiling.
|
|
250
|
+
const timeout = new Promise<string>((resolve) => {
|
|
251
|
+
timer = setTimeout(
|
|
252
|
+
() =>
|
|
253
|
+
resolve(
|
|
254
|
+
`no first commit within ${FIRST_COMMIT_TIMEOUT_MS / 1000}s ` +
|
|
255
|
+
'(mount() fails loudly instead of hanging)',
|
|
256
|
+
),
|
|
257
|
+
FIRST_COMMIT_TIMEOUT_MS,
|
|
217
258
|
);
|
|
218
|
-
const timer = setTimeout(
|
|
219
|
-
() =>
|
|
220
|
-
fail(
|
|
221
|
-
`no first commit within ${FIRST_COMMIT_TIMEOUT_MS / 1000}s ` +
|
|
222
|
-
'(mount() fails loudly instead of hanging)',
|
|
223
|
-
),
|
|
224
|
-
FIRST_COMMIT_TIMEOUT_MS,
|
|
225
|
-
);
|
|
226
|
-
const cleanup = () => {
|
|
227
|
-
clearTimeout(timer);
|
|
228
|
-
globalThis.removeEventListener?.('error', onError);
|
|
229
|
-
};
|
|
230
|
-
globalThis.addEventListener?.('error', onError);
|
|
231
|
-
void committed.then(() => {
|
|
232
|
-
cleanup();
|
|
233
|
-
resolve();
|
|
234
259
|
});
|
|
235
|
-
|
|
260
|
+
const failure = await Promise.race([
|
|
261
|
+
Promise.all([committed, effectsReady]).then(() => null),
|
|
262
|
+
reconcileFailure,
|
|
263
|
+
timeout,
|
|
264
|
+
]);
|
|
265
|
+
if (failure !== null) {
|
|
266
|
+
throw new Error(`createPixiReactAdapter: root "${id}" — ${failure}`);
|
|
267
|
+
}
|
|
268
|
+
} catch (error) {
|
|
269
|
+
try {
|
|
270
|
+
app?.destroy(false, { children: true });
|
|
271
|
+
} catch {
|
|
272
|
+
/* the world never fully came up; teardown failure must not mask why */
|
|
273
|
+
}
|
|
274
|
+
runtime?.dispose();
|
|
275
|
+
throw error;
|
|
276
|
+
} finally {
|
|
277
|
+
if (timer !== undefined) clearTimeout(timer);
|
|
278
|
+
globalThis.removeEventListener?.('error', onError);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// `app` is assigned before the first-commit race. Reaching here proves
|
|
282
|
+
// both initialization and commit succeeded, so narrow once for the live
|
|
283
|
+
// mounted-root methods below.
|
|
284
|
+
if (app === undefined) {
|
|
285
|
+
runtime?.dispose();
|
|
286
|
+
throw new Error(`createPixiReactAdapter: root "${id}" initialized without an Application`);
|
|
287
|
+
}
|
|
288
|
+
const mountedApp = app;
|
|
236
289
|
|
|
237
290
|
// Pin the ticker's clock to GAME time. `Ticker.lastTime` starts at -1, so
|
|
238
291
|
// without this the first `update()` would report a 1ms phantom delta on
|
|
@@ -240,39 +293,53 @@ export function createPixiReactAdapter(
|
|
|
240
293
|
// sees is exactly the `dt` the host handed us — no wall clock anywhere,
|
|
241
294
|
// which is what makes a paused world's resume frame ordinary rather than
|
|
242
295
|
// one giant catch-up step.
|
|
243
|
-
|
|
296
|
+
mountedApp.ticker.lastTime = 0;
|
|
244
297
|
let elapsedMs = 0;
|
|
245
298
|
let disposed = false;
|
|
299
|
+
const registry = host.game ? getDebugRegistry(host.game) : null;
|
|
300
|
+
const systemAdapters: SystemAdapters =
|
|
301
|
+
runtime?.systemAdapters ?? (registry ? { debug: registry.adapter } : {});
|
|
302
|
+
const disposeComplete = unmounted.then(() => {
|
|
303
|
+
mountedApp.destroy(false, { children: true });
|
|
304
|
+
});
|
|
246
305
|
|
|
247
306
|
return {
|
|
248
307
|
kind: 'canvas',
|
|
249
308
|
// The reconciler's real root container — the same `PIXI.Container`
|
|
250
309
|
// `RootInstance.pixiStage()` hands the editor.
|
|
251
|
-
stage:
|
|
310
|
+
stage: mountedApp.stage,
|
|
252
311
|
drivesOwnLoop: false,
|
|
253
|
-
// Adapter surface:
|
|
254
|
-
//
|
|
255
|
-
|
|
312
|
+
// Adapter surface: the host's shared debug registry. A native component
|
|
313
|
+
// tree registers nothing; its entry-module `debug` export is projected
|
|
314
|
+
// after mount by the shared native adapter.
|
|
315
|
+
systems: systemAdapters,
|
|
316
|
+
disposeComplete,
|
|
256
317
|
update(dt: number): void {
|
|
257
318
|
if (disposed) return;
|
|
258
319
|
elapsedMs += dt * 1000;
|
|
259
|
-
//
|
|
260
|
-
// the
|
|
261
|
-
//
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
app.ticker.update(elapsedMs);
|
|
320
|
+
// The optional legacy runtime drains first when explicitly enabled;
|
|
321
|
+
// the native path has none. The real Pixi ticker remains the one
|
|
322
|
+
// scheduler `useTick` observes, and the host only gates/advances it.
|
|
323
|
+
runtime?.systems.run(dt);
|
|
324
|
+
mountedApp.ticker.update(elapsedMs);
|
|
265
325
|
},
|
|
266
326
|
resize(width: number, height: number): void {
|
|
267
327
|
if (disposed) return;
|
|
268
328
|
// `autoDensity: true` makes Pixi re-stamp `canvas.style.width`/
|
|
269
329
|
// `.height` from this call, so the on-screen box follows the logical
|
|
270
330
|
// size with no explicit CSS write from the host.
|
|
271
|
-
|
|
331
|
+
mountedApp.renderer.resize(width, height);
|
|
272
332
|
},
|
|
273
333
|
dispose(): void {
|
|
274
334
|
if (disposed) return;
|
|
275
335
|
disposed = true;
|
|
336
|
+
// Pixi's EventSystem owns document.pointermove + window.pointerup
|
|
337
|
+
// (and its event ticker's global listener). React's deletion commit
|
|
338
|
+
// is asynchronous, so waiting to destroy the Application leaves
|
|
339
|
+
// those listeners live when the host audits this game realm. Detach
|
|
340
|
+
// the input target synchronously; the later Application.destroy()
|
|
341
|
+
// repeats this idempotently after every useTick cleanup has landed.
|
|
342
|
+
mountedApp.renderer.events.setTargetElement(null as unknown as HTMLElement);
|
|
276
343
|
// Rendering `null` children is the only unmount path `@pixi/react`
|
|
277
344
|
// exposes publicly (its `unmountRoot` helper is internal), and React
|
|
278
345
|
// commits that deletion on its own schedule — NOT synchronously. So
|
|
@@ -281,8 +348,7 @@ export function createPixiReactAdapter(
|
|
|
281
348
|
// a ticker destroyed before it lands throws inside React's commit.
|
|
282
349
|
// `removeView` stays false — the canvas belongs to the HOST.
|
|
283
350
|
void root.render(null, initOptions);
|
|
284
|
-
|
|
285
|
-
runtime.dispose();
|
|
351
|
+
runtime?.dispose();
|
|
286
352
|
},
|
|
287
353
|
};
|
|
288
354
|
},
|
|
@@ -66,6 +66,10 @@ export function resolveCanvasEntryAdapter(
|
|
|
66
66
|
return createPixiReactAdapter({
|
|
67
67
|
id: rootId,
|
|
68
68
|
content: createElement(mod.default),
|
|
69
|
+
// The supported Canvas portfolio still consumes the compatibility
|
|
70
|
+
// context. Removing it belongs to the tracked component-root migration;
|
|
71
|
+
// disabling it here first made every current default-exported game fail
|
|
72
|
+
// to mount before that migration had happened.
|
|
69
73
|
});
|
|
70
74
|
}
|
|
71
75
|
return null;
|