react-three-game 0.0.85 → 0.0.87
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/README.md +87 -35
- package/dist/index.d.ts +5 -7
- package/dist/index.js +2 -4
- package/dist/tools/prefabeditor/GameEvents.d.ts +36 -117
- package/dist/tools/prefabeditor/GameEvents.js +44 -96
- package/dist/tools/prefabeditor/InstanceProvider.d.ts +0 -4
- package/dist/tools/prefabeditor/InstanceProvider.js +13 -44
- package/dist/tools/prefabeditor/PrefabEditor.d.ts +7 -2
- package/dist/tools/prefabeditor/PrefabEditor.js +13 -24
- package/dist/tools/prefabeditor/PrefabRoot.js +93 -44
- package/dist/tools/prefabeditor/{runtime.d.ts → assetRuntime.d.ts} +0 -25
- package/dist/tools/prefabeditor/assetRuntime.js +37 -0
- package/dist/tools/prefabeditor/components/BufferGeometryComponent.js +4 -2
- package/dist/tools/prefabeditor/components/CameraComponent.js +1 -1
- package/dist/tools/prefabeditor/components/ComponentRegistry.d.ts +0 -3
- package/dist/tools/prefabeditor/components/DataComponent.d.ts +3 -0
- package/dist/tools/prefabeditor/components/DataComponent.js +87 -0
- package/dist/tools/prefabeditor/components/DirectionalLightComponent.js +1 -1
- package/dist/tools/prefabeditor/components/EnvironmentComponent.js +1 -1
- package/dist/tools/prefabeditor/components/GeometryComponent.js +4 -2
- package/dist/tools/prefabeditor/components/Input.d.ts +2 -13
- package/dist/tools/prefabeditor/components/Input.js +0 -55
- package/dist/tools/prefabeditor/components/MaterialComponent.js +1 -1
- package/dist/tools/prefabeditor/components/ModelComponent.js +3 -3
- package/dist/tools/prefabeditor/components/PhysicsComponent.d.ts +4 -0
- package/dist/tools/prefabeditor/components/PhysicsComponent.js +64 -130
- package/dist/tools/prefabeditor/components/PointLightComponent.js +1 -1
- package/dist/tools/prefabeditor/components/SoundComponent.js +18 -11
- package/dist/tools/prefabeditor/components/SpotLightComponent.js +1 -1
- package/dist/tools/prefabeditor/components/index.js +2 -2
- package/dist/tools/prefabeditor/types.d.ts +1 -0
- package/dist/tools/prefabeditor/types.js +18 -0
- package/dist/tools/prefabeditor/usePointerEvents.d.ts +27 -0
- package/dist/tools/prefabeditor/usePointerEvents.js +52 -0
- package/package.json +1 -1
- package/dist/tools/prefabeditor/components/ClickComponent.d.ts +0 -3
- package/dist/tools/prefabeditor/components/ClickComponent.js +0 -52
- package/dist/tools/prefabeditor/runtime.js +0 -184
|
@@ -3,9 +3,9 @@ import { useEffect, useRef } from 'react';
|
|
|
3
3
|
import { useThree } from '@react-three/fiber';
|
|
4
4
|
import { SoundPicker } from '../../assetviewer/page';
|
|
5
5
|
import { sound as soundManager } from '../../../helpers/SoundManager';
|
|
6
|
+
import { useAssetRuntime, useEntityRuntime } from '../assetRuntime';
|
|
6
7
|
import { gameEvents } from '../GameEvents';
|
|
7
|
-
import {
|
|
8
|
-
import { BooleanField, EventField, FieldGroup, FieldRenderer, ListEditor, NumberField, SelectField } from './Input';
|
|
8
|
+
import { BooleanField, FieldGroup, FieldRenderer, ListEditor, NumberField, SelectField, StringField } from './Input';
|
|
9
9
|
import { colors } from '../styles';
|
|
10
10
|
import { AudioListener } from 'three';
|
|
11
11
|
const CLIP_MODE_OPTIONS = [
|
|
@@ -63,6 +63,21 @@ function pickClip(paths, mode, sequenceIndexRef) {
|
|
|
63
63
|
}
|
|
64
64
|
return paths[Math.floor(Math.random() * paths.length)];
|
|
65
65
|
}
|
|
66
|
+
function payloadMatchesNode(nodeId, payload) {
|
|
67
|
+
if (!nodeId || !payload || typeof payload !== 'object') {
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
const eventPayload = payload;
|
|
71
|
+
const relatedNodeIds = [
|
|
72
|
+
eventPayload.nodeId,
|
|
73
|
+
eventPayload.sourceEntityId,
|
|
74
|
+
eventPayload.sourceNodeId,
|
|
75
|
+
eventPayload.targetEntityId,
|
|
76
|
+
eventPayload.targetNodeId,
|
|
77
|
+
eventPayload.instanceEntityId,
|
|
78
|
+
].filter((value) => typeof value === 'string');
|
|
79
|
+
return relatedNodeIds.length > 0 ? relatedNodeIds.includes(nodeId) : true;
|
|
80
|
+
}
|
|
66
81
|
function SoundComponentEditor({ component, onUpdate, basePath = '' }) {
|
|
67
82
|
const clips = Array.isArray(component.properties.clips)
|
|
68
83
|
? component.properties.clips.map((clip) => typeof clip === 'string' ? clip : '')
|
|
@@ -84,7 +99,7 @@ function SoundComponentEditor({ component, onUpdate, basePath = '' }) {
|
|
|
84
99
|
const removeClip = (index) => {
|
|
85
100
|
setClips(clips.filter((_, clipIndex) => clipIndex !== index));
|
|
86
101
|
};
|
|
87
|
-
return (_jsxs(FieldGroup, { children: [_jsx(
|
|
102
|
+
return (_jsxs(FieldGroup, { children: [_jsx(StringField, { name: "eventName", label: "Listen Event", values: component.properties, onChange: onUpdate, placeholder: "player:footstep" }), _jsx(FieldRenderer, { fields: [
|
|
88
103
|
{
|
|
89
104
|
name: 'clipMode',
|
|
90
105
|
label: 'Clip Mode',
|
|
@@ -115,14 +130,6 @@ function SoundComponentEditor({ component, onUpdate, basePath = '' }) {
|
|
|
115
130
|
{ value: 'exponential', label: 'Exponential' },
|
|
116
131
|
] })] })) : null, _jsx(BooleanField, { name: "randomizePitch", label: "Random Pitch", values: component.properties, onChange: onUpdate, fallback: false }), randomizePitch ? (_jsxs(_Fragment, { children: [_jsx(NumberField, { name: "minPitch", label: "Min Pitch", values: component.properties, onChange: onUpdate, fallback: 0.96, step: 0.01, min: 0.1 }), _jsx(NumberField, { name: "maxPitch", label: "Max Pitch", values: component.properties, onChange: onUpdate, fallback: 1.04, step: 0.01, min: 0.1 })] })) : (_jsx(NumberField, { name: "pitch", label: "Pitch", values: component.properties, onChange: onUpdate, fallback: 1, step: 0.01, min: 0.1 })), _jsx(BooleanField, { name: "randomizeVolume", label: "Random Volume", values: component.properties, onChange: onUpdate, fallback: false }), randomizeVolume ? (_jsxs(_Fragment, { children: [_jsx(NumberField, { name: "minVolume", label: "Min Volume", values: component.properties, onChange: onUpdate, fallback: 0.9, step: 0.01, min: 0 }), _jsx(NumberField, { name: "maxVolume", label: "Max Volume", values: component.properties, onChange: onUpdate, fallback: 1, step: 0.01, min: 0 })] })) : (_jsx(NumberField, { name: "volume", label: "Volume", values: component.properties, onChange: onUpdate, fallback: 1, step: 0.01, min: 0 }))] }));
|
|
117
132
|
}
|
|
118
|
-
function payloadMatchesNode(nodeId, payload) {
|
|
119
|
-
if (!nodeId || !payload || typeof payload !== 'object')
|
|
120
|
-
return true;
|
|
121
|
-
const eventPayload = payload;
|
|
122
|
-
const ids = [eventPayload.sourceEntityId, eventPayload.targetEntityId, eventPayload.instanceEntityId];
|
|
123
|
-
const hasEntityIds = ids.some(id => typeof id === 'string');
|
|
124
|
-
return hasEntityIds ? ids.includes(nodeId) : true;
|
|
125
|
-
}
|
|
126
133
|
function SoundComponentView({ properties, children }) {
|
|
127
134
|
const { getSound } = useAssetRuntime();
|
|
128
135
|
const { editMode, nodeId } = useEntityRuntime();
|
|
@@ -3,7 +3,7 @@ import { useHelper } from "@react-three/drei";
|
|
|
3
3
|
import { useRef, useEffect } from "react";
|
|
4
4
|
import { BooleanField, ColorField, Label, NumberField, Vector3Input } from "./Input";
|
|
5
5
|
import { SpotLightHelper } from "three";
|
|
6
|
-
import { useAssetRuntime, useEntityRuntime } from "../
|
|
6
|
+
import { useAssetRuntime, useEntityRuntime } from "../assetRuntime";
|
|
7
7
|
import { useFrame } from "@react-three/fiber";
|
|
8
8
|
import { TexturePicker } from "../../assetviewer/page";
|
|
9
9
|
import { LightSection, ShadowBiasField, mergeWithDefaults } from "./lightUtils";
|
|
@@ -11,8 +11,8 @@ import DirectionalLightComponent from './DirectionalLightComponent';
|
|
|
11
11
|
import AmbientLightComponent from './AmbientLightComponent';
|
|
12
12
|
import EnvironmentComponent from './EnvironmentComponent';
|
|
13
13
|
import CameraComponent from './CameraComponent';
|
|
14
|
-
import ClickComponent from './ClickComponent';
|
|
15
14
|
import SoundComponent from './SoundComponent';
|
|
15
|
+
import DataComponent from './DataComponent';
|
|
16
16
|
export const builtinComponents = [
|
|
17
17
|
TransformComponent,
|
|
18
18
|
GeometryComponent,
|
|
@@ -27,6 +27,6 @@ export const builtinComponents = [
|
|
|
27
27
|
AmbientLightComponent,
|
|
28
28
|
EnvironmentComponent,
|
|
29
29
|
CameraComponent,
|
|
30
|
-
ClickComponent,
|
|
31
30
|
SoundComponent,
|
|
31
|
+
DataComponent,
|
|
32
32
|
];
|
|
@@ -29,4 +29,5 @@ export declare function findComponent(node: ComponentHost | null | undefined, na
|
|
|
29
29
|
export declare function findComponentEntry(node: ComponentHost | null | undefined, name: string): [string, ComponentData] | undefined;
|
|
30
30
|
/** Check if a node has a component of the given type. */
|
|
31
31
|
export declare function hasComponent(node: ComponentHost | null | undefined, typeName: string): boolean;
|
|
32
|
+
export declare function getNodeUserData(node: GameObject | null | undefined): Record<string, unknown>;
|
|
32
33
|
export {};
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
const RESERVED_USER_DATA_KEYS = new Set([
|
|
2
|
+
'prefabNodeId',
|
|
3
|
+
'prefabNodeName',
|
|
4
|
+
]);
|
|
1
5
|
/** Find a component on a node by type name or key (e.g. "Model", "transform"). */
|
|
2
6
|
export function findComponent(node, name) {
|
|
3
7
|
var _a;
|
|
@@ -26,3 +30,17 @@ export function findComponentEntry(node, name) {
|
|
|
26
30
|
export function hasComponent(node, typeName) {
|
|
27
31
|
return findComponentEntry(node, typeName) !== undefined;
|
|
28
32
|
}
|
|
33
|
+
export function getNodeUserData(node) {
|
|
34
|
+
var _a, _b;
|
|
35
|
+
const data = (_b = (_a = findComponent(node, 'Data')) === null || _a === void 0 ? void 0 : _a.properties) === null || _b === void 0 ? void 0 : _b.data;
|
|
36
|
+
if (!data || typeof data !== 'object' || Array.isArray(data)) {
|
|
37
|
+
return {};
|
|
38
|
+
}
|
|
39
|
+
return Object.entries(data).reduce((result, [key, value]) => {
|
|
40
|
+
if (!key.trim() || RESERVED_USER_DATA_KEYS.has(key) || value === undefined) {
|
|
41
|
+
return result;
|
|
42
|
+
}
|
|
43
|
+
result[key] = value;
|
|
44
|
+
return result;
|
|
45
|
+
}, {});
|
|
46
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { ThreeEvent } from "@react-three/fiber";
|
|
2
|
+
export type PointerHandler<T> = (event: ThreeEvent<PointerEvent>, entity: T) => void;
|
|
3
|
+
export interface PointerEventHandlers<T> {
|
|
4
|
+
onClick?: PointerHandler<T>;
|
|
5
|
+
onPointerDown?: PointerHandler<T>;
|
|
6
|
+
onPointerUp?: PointerHandler<T>;
|
|
7
|
+
onPointerMove?: PointerHandler<T>;
|
|
8
|
+
onPointerEnter?: PointerHandler<T>;
|
|
9
|
+
onPointerLeave?: PointerHandler<T>;
|
|
10
|
+
onPointerOver?: PointerHandler<T>;
|
|
11
|
+
onPointerOut?: PointerHandler<T>;
|
|
12
|
+
}
|
|
13
|
+
export interface UsePointerEventsOptions<T> extends PointerEventHandlers<T> {
|
|
14
|
+
enabled: boolean;
|
|
15
|
+
entity: T | null | undefined;
|
|
16
|
+
}
|
|
17
|
+
export declare function hasPointerEventHandlers<T>(handlers: PointerEventHandlers<T>): boolean;
|
|
18
|
+
export declare function usePointerEvents<T>({ enabled, entity, onClick, onPointerDown, onPointerUp, onPointerMove, onPointerEnter, onPointerLeave, onPointerOver, onPointerOut, }: UsePointerEventsOptions<T>): {
|
|
19
|
+
onClick: ((event: ThreeEvent<PointerEvent>) => void) | undefined;
|
|
20
|
+
onPointerDown: ((event: ThreeEvent<PointerEvent>) => void) | undefined;
|
|
21
|
+
onPointerMove: ((event: ThreeEvent<PointerEvent>) => void) | undefined;
|
|
22
|
+
onPointerUp: ((event: ThreeEvent<PointerEvent>) => void) | undefined;
|
|
23
|
+
onPointerEnter: ((event: ThreeEvent<PointerEvent>) => void) | undefined;
|
|
24
|
+
onPointerLeave: ((event: ThreeEvent<PointerEvent>) => void) | undefined;
|
|
25
|
+
onPointerOver: ((event: ThreeEvent<PointerEvent>) => void) | undefined;
|
|
26
|
+
onPointerOut: ((event: ThreeEvent<PointerEvent>) => void) | undefined;
|
|
27
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
export function hasPointerEventHandlers(handlers) {
|
|
2
|
+
return Boolean(handlers.onClick
|
|
3
|
+
|| handlers.onPointerDown
|
|
4
|
+
|| handlers.onPointerUp
|
|
5
|
+
|| handlers.onPointerMove
|
|
6
|
+
|| handlers.onPointerEnter
|
|
7
|
+
|| handlers.onPointerLeave
|
|
8
|
+
|| handlers.onPointerOver
|
|
9
|
+
|| handlers.onPointerOut);
|
|
10
|
+
}
|
|
11
|
+
export function usePointerEvents({ enabled, entity, onClick, onPointerDown, onPointerUp, onPointerMove, onPointerEnter, onPointerLeave, onPointerOver, onPointerOut, }) {
|
|
12
|
+
if (!enabled) {
|
|
13
|
+
return {
|
|
14
|
+
onClick: undefined,
|
|
15
|
+
onPointerDown: undefined,
|
|
16
|
+
onPointerMove: undefined,
|
|
17
|
+
onPointerUp: undefined,
|
|
18
|
+
onPointerEnter: undefined,
|
|
19
|
+
onPointerLeave: undefined,
|
|
20
|
+
onPointerOver: undefined,
|
|
21
|
+
onPointerOut: undefined,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
const forward = (handler) => {
|
|
25
|
+
if (!handler)
|
|
26
|
+
return undefined;
|
|
27
|
+
return (event) => {
|
|
28
|
+
event.stopPropagation();
|
|
29
|
+
if (!entity)
|
|
30
|
+
return;
|
|
31
|
+
handler(event, entity);
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
const forwardMove = onPointerMove
|
|
35
|
+
? (event) => {
|
|
36
|
+
event.stopPropagation();
|
|
37
|
+
if (!entity)
|
|
38
|
+
return;
|
|
39
|
+
onPointerMove(event, entity);
|
|
40
|
+
}
|
|
41
|
+
: undefined;
|
|
42
|
+
return {
|
|
43
|
+
onClick: forward(onClick),
|
|
44
|
+
onPointerDown: forward(onPointerDown),
|
|
45
|
+
onPointerMove: forwardMove,
|
|
46
|
+
onPointerUp: forward(onPointerUp),
|
|
47
|
+
onPointerEnter: forward(onPointerEnter),
|
|
48
|
+
onPointerLeave: forward(onPointerLeave),
|
|
49
|
+
onPointerOver: forward(onPointerOver),
|
|
50
|
+
onPointerOut: forward(onPointerOut),
|
|
51
|
+
};
|
|
52
|
+
}
|
package/package.json
CHANGED
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { useRef } from 'react';
|
|
3
|
-
import { gameEvents } from '../GameEvents';
|
|
4
|
-
import { useEntityRuntime } from '../runtime';
|
|
5
|
-
import { EventField, FieldGroup } from './Input';
|
|
6
|
-
function ClickComponentEditor({ component, onUpdate }) {
|
|
7
|
-
return (_jsxs(FieldGroup, { children: [_jsx("div", { style: { fontSize: 12, opacity: 0.8 }, children: "Emits a game event in play mode when this entity is clicked." }), _jsx(EventField, { name: "eventName", label: "Emit Event", values: component.properties, onChange: onUpdate, placeholder: "click" })] }));
|
|
8
|
-
}
|
|
9
|
-
function ClickComponentView({ children, properties }) {
|
|
10
|
-
const clickValid = useRef(false);
|
|
11
|
-
const { editMode, nodeId } = useEntityRuntime();
|
|
12
|
-
const eventName = (properties === null || properties === void 0 ? void 0 : properties.eventName) || 'click';
|
|
13
|
-
const emitClick = (event) => {
|
|
14
|
-
if (!nodeId)
|
|
15
|
-
return;
|
|
16
|
-
gameEvents.emit(eventName, {
|
|
17
|
-
sourceEntityId: nodeId,
|
|
18
|
-
point: [event.point.x, event.point.y, event.point.z],
|
|
19
|
-
button: event.button,
|
|
20
|
-
altKey: event.nativeEvent.altKey,
|
|
21
|
-
ctrlKey: event.nativeEvent.ctrlKey,
|
|
22
|
-
metaKey: event.nativeEvent.metaKey,
|
|
23
|
-
shiftKey: event.nativeEvent.shiftKey,
|
|
24
|
-
});
|
|
25
|
-
};
|
|
26
|
-
if (editMode) {
|
|
27
|
-
return _jsx(_Fragment, { children: children });
|
|
28
|
-
}
|
|
29
|
-
return (_jsx("group", { onPointerDown: (event) => {
|
|
30
|
-
event.stopPropagation();
|
|
31
|
-
clickValid.current = true;
|
|
32
|
-
}, onClick: (event) => {
|
|
33
|
-
event.stopPropagation();
|
|
34
|
-
}, onPointerMove: () => {
|
|
35
|
-
clickValid.current = false;
|
|
36
|
-
}, onPointerUp: (event) => {
|
|
37
|
-
if (!clickValid.current)
|
|
38
|
-
return;
|
|
39
|
-
event.stopPropagation();
|
|
40
|
-
emitClick(event);
|
|
41
|
-
clickValid.current = false;
|
|
42
|
-
}, children: children }));
|
|
43
|
-
}
|
|
44
|
-
const ClickComponent = {
|
|
45
|
-
name: 'Click',
|
|
46
|
-
Editor: ClickComponentEditor,
|
|
47
|
-
View: ClickComponentView,
|
|
48
|
-
defaultProperties: {
|
|
49
|
-
eventName: 'click',
|
|
50
|
-
},
|
|
51
|
-
};
|
|
52
|
-
export default ClickComponent;
|
|
@@ -1,184 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { createContext, useContext, useMemo } from "react";
|
|
3
|
-
import { getComponentDef } from "./components/ComponentRegistry";
|
|
4
|
-
import { createScene } from "./scene";
|
|
5
|
-
export const AssetRuntimeContext = createContext(null);
|
|
6
|
-
const EntityRuntimeContext = createContext(null);
|
|
7
|
-
export function useAssetRuntime() {
|
|
8
|
-
const ctx = useContext(AssetRuntimeContext);
|
|
9
|
-
if (!ctx)
|
|
10
|
-
throw new Error("useAssetRuntime must be used inside <PrefabRoot>");
|
|
11
|
-
return ctx;
|
|
12
|
-
}
|
|
13
|
-
export function useEntityRuntime() {
|
|
14
|
-
const ctx = useContext(EntityRuntimeContext);
|
|
15
|
-
if (!ctx)
|
|
16
|
-
throw new Error("useEntityRuntime must be used inside a component View rendered by <PrefabRoot>");
|
|
17
|
-
return ctx;
|
|
18
|
-
}
|
|
19
|
-
export function useEntityObjectRef() {
|
|
20
|
-
const { getObject } = useEntityRuntime();
|
|
21
|
-
return useMemo(() => ({ get current() { return getObject(); } }), [getObject]);
|
|
22
|
-
}
|
|
23
|
-
export function useEntityRigidBodyRef() {
|
|
24
|
-
const { getRigidBody } = useEntityRuntime();
|
|
25
|
-
return useMemo(() => ({ get current() { return getRigidBody(); } }), [getRigidBody]);
|
|
26
|
-
}
|
|
27
|
-
export function EntityRuntimeScope({ nodeId, editMode, isSelected, children, }) {
|
|
28
|
-
const asset = useContext(AssetRuntimeContext);
|
|
29
|
-
if (!asset)
|
|
30
|
-
throw new Error("EntityRuntimeScope must be used inside <PrefabRoot>");
|
|
31
|
-
const value = useMemo(() => ({
|
|
32
|
-
nodeId,
|
|
33
|
-
editMode,
|
|
34
|
-
isSelected,
|
|
35
|
-
getObject: () => asset.getObject(nodeId),
|
|
36
|
-
getRigidBody: () => asset.getRigidBody(nodeId),
|
|
37
|
-
}), [asset, editMode, isSelected, nodeId]);
|
|
38
|
-
return _jsx(EntityRuntimeContext.Provider, { value: value, children: children });
|
|
39
|
-
}
|
|
40
|
-
export function createRuntimeEngine({ store, getObject, getRigidBody, }) {
|
|
41
|
-
const scene = createScene({
|
|
42
|
-
getRootId: () => store.getState().rootId,
|
|
43
|
-
getNode: (id) => { var _a; return (_a = store.getState().nodesById[id]) !== null && _a !== void 0 ? _a : null; },
|
|
44
|
-
getChildIds: (id) => { var _a; return (_a = store.getState().childIdsById[id]) !== null && _a !== void 0 ? _a : []; },
|
|
45
|
-
getParentId: (id) => { var _a; return (_a = store.getState().parentIdById[id]) !== null && _a !== void 0 ? _a : null; },
|
|
46
|
-
updateNode: (id, update) => store.getState().updateNode(id, update),
|
|
47
|
-
updateNodes: (updates) => {
|
|
48
|
-
store.getState().updateNodes(Object.entries(updates).map(([id, update]) => ({ id, update })));
|
|
49
|
-
},
|
|
50
|
-
addNode: (node, options) => {
|
|
51
|
-
var _a;
|
|
52
|
-
const parentId = (_a = options === null || options === void 0 ? void 0 : options.parentId) !== null && _a !== void 0 ? _a : store.getState().rootId;
|
|
53
|
-
store.getState().addChild(parentId, node);
|
|
54
|
-
return node.id;
|
|
55
|
-
},
|
|
56
|
-
removeNode: (id) => store.getState().deleteNode(id),
|
|
57
|
-
getObject,
|
|
58
|
-
getRigidBody,
|
|
59
|
-
});
|
|
60
|
-
// key = `${nodeId}:${componentKey}:${componentType}`
|
|
61
|
-
const instances = new Map();
|
|
62
|
-
let active = false;
|
|
63
|
-
let dirty = true;
|
|
64
|
-
let lastRevision = store.getState().revision;
|
|
65
|
-
const unsubscribe = store.subscribe((state) => {
|
|
66
|
-
if (state.revision === lastRevision)
|
|
67
|
-
return;
|
|
68
|
-
lastRevision = state.revision;
|
|
69
|
-
dirty = true;
|
|
70
|
-
});
|
|
71
|
-
function destroy(key) {
|
|
72
|
-
var _a, _b;
|
|
73
|
-
const record = instances.get(key);
|
|
74
|
-
if (!record)
|
|
75
|
-
return;
|
|
76
|
-
try {
|
|
77
|
-
(_b = (_a = record.instance).destroy) === null || _b === void 0 ? void 0 : _b.call(_a);
|
|
78
|
-
}
|
|
79
|
-
catch (error) {
|
|
80
|
-
console.error(`[runtime] destroy ${key}`, error);
|
|
81
|
-
}
|
|
82
|
-
instances.delete(key);
|
|
83
|
-
}
|
|
84
|
-
function sync() {
|
|
85
|
-
const state = store.getState();
|
|
86
|
-
const live = new Set();
|
|
87
|
-
let hasPendingMount = false;
|
|
88
|
-
const visit = (nodeId) => {
|
|
89
|
-
var _a, _b, _c;
|
|
90
|
-
const node = state.nodesById[nodeId];
|
|
91
|
-
if (!node)
|
|
92
|
-
return;
|
|
93
|
-
if (!node.disabled && node.components) {
|
|
94
|
-
for (const componentKey in node.components) {
|
|
95
|
-
const data = node.components[componentKey];
|
|
96
|
-
if (!(data === null || data === void 0 ? void 0 : data.type))
|
|
97
|
-
continue;
|
|
98
|
-
const def = getComponentDef(data.type);
|
|
99
|
-
if (!(def === null || def === void 0 ? void 0 : def.create))
|
|
100
|
-
continue;
|
|
101
|
-
const key = `${nodeId}:${componentKey}:${data.type}`;
|
|
102
|
-
live.add(key);
|
|
103
|
-
const object = getObject(nodeId);
|
|
104
|
-
if (!object) {
|
|
105
|
-
hasPendingMount = true;
|
|
106
|
-
continue;
|
|
107
|
-
}
|
|
108
|
-
const rigidBody = getRigidBody(nodeId);
|
|
109
|
-
const existing = instances.get(key);
|
|
110
|
-
if (existing && existing.object === object && existing.rigidBody === rigidBody) {
|
|
111
|
-
continue;
|
|
112
|
-
}
|
|
113
|
-
if (existing) {
|
|
114
|
-
destroy(key);
|
|
115
|
-
}
|
|
116
|
-
const entity = scene.find(nodeId);
|
|
117
|
-
const component = entity === null || entity === void 0 ? void 0 : entity.getComponent(componentKey);
|
|
118
|
-
if (!entity || !component)
|
|
119
|
-
continue;
|
|
120
|
-
let instance;
|
|
121
|
-
try {
|
|
122
|
-
instance = (_a = def.create({ scene, component, object, rigidBody })) !== null && _a !== void 0 ? _a : {};
|
|
123
|
-
}
|
|
124
|
-
catch (error) {
|
|
125
|
-
console.error(`[runtime] create ${key}`, error);
|
|
126
|
-
continue;
|
|
127
|
-
}
|
|
128
|
-
instances.set(key, { instance, object, rigidBody });
|
|
129
|
-
try {
|
|
130
|
-
(_b = instance.start) === null || _b === void 0 ? void 0 : _b.call(instance);
|
|
131
|
-
}
|
|
132
|
-
catch (error) {
|
|
133
|
-
console.error(`[runtime] start ${key}`, error);
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
for (const childId of (_c = state.childIdsById[nodeId]) !== null && _c !== void 0 ? _c : [])
|
|
138
|
-
visit(childId);
|
|
139
|
-
};
|
|
140
|
-
visit(state.rootId);
|
|
141
|
-
for (const key of Array.from(instances.keys())) {
|
|
142
|
-
if (!live.has(key))
|
|
143
|
-
destroy(key);
|
|
144
|
-
}
|
|
145
|
-
dirty = hasPendingMount;
|
|
146
|
-
}
|
|
147
|
-
return {
|
|
148
|
-
tick(dt) {
|
|
149
|
-
if (!active)
|
|
150
|
-
return;
|
|
151
|
-
if (dirty)
|
|
152
|
-
sync();
|
|
153
|
-
for (const [key, record] of instances) {
|
|
154
|
-
if (!record.instance.update)
|
|
155
|
-
continue;
|
|
156
|
-
try {
|
|
157
|
-
record.instance.update(dt);
|
|
158
|
-
}
|
|
159
|
-
catch (error) {
|
|
160
|
-
console.error(`[runtime] update ${key}`, error);
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
},
|
|
164
|
-
setActive(nextActive) {
|
|
165
|
-
if (active === nextActive)
|
|
166
|
-
return;
|
|
167
|
-
active = nextActive;
|
|
168
|
-
dirty = true;
|
|
169
|
-
if (!active) {
|
|
170
|
-
for (const key of Array.from(instances.keys()))
|
|
171
|
-
destroy(key);
|
|
172
|
-
}
|
|
173
|
-
},
|
|
174
|
-
invalidate() {
|
|
175
|
-
dirty = true;
|
|
176
|
-
},
|
|
177
|
-
dispose() {
|
|
178
|
-
active = false;
|
|
179
|
-
for (const key of Array.from(instances.keys()))
|
|
180
|
-
destroy(key);
|
|
181
|
-
unsubscribe();
|
|
182
|
-
},
|
|
183
|
-
};
|
|
184
|
-
}
|