@vulfram/engine 0.14.8-alpha → 0.19.2-alpha
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 +106 -0
- package/package.json +60 -4
- package/src/core.ts +14 -0
- package/src/ecs.ts +1 -0
- package/src/engine/api.ts +222 -24
- package/src/engine/bridge/dispatch.ts +260 -40
- package/src/engine/bridge/guards.ts +4 -1
- package/src/engine/bridge/protocol.ts +69 -52
- package/src/engine/ecs/components.ts +340 -0
- package/src/engine/ecs/index.ts +3 -518
- package/src/engine/ecs/intents.ts +184 -0
- package/src/engine/ecs/systems.ts +26 -0
- package/src/engine/intents/store.ts +72 -0
- package/src/engine/state.ts +136 -5
- package/src/engine/systems/command-intent.ts +159 -14
- package/src/engine/systems/constraint-solve.ts +167 -0
- package/src/engine/systems/core-command-builder.ts +9 -268
- package/src/engine/systems/diagnostics.ts +20 -29
- package/src/engine/systems/index.ts +3 -1
- package/src/engine/systems/input-mirror.ts +257 -21
- package/src/engine/systems/resource-upload.ts +108 -58
- package/src/engine/systems/response-decode.ts +86 -15
- package/src/engine/systems/scene-sync.ts +305 -0
- package/src/engine/systems/ui-bridge.ts +381 -0
- package/src/engine/systems/utils.ts +86 -1
- package/src/engine/systems/world-lifecycle.ts +43 -114
- package/src/engine/window/manager.ts +168 -0
- package/src/engine/world/entities.ts +998 -91
- package/src/engine/world/mount.ts +195 -0
- package/src/engine/world/types.ts +71 -0
- package/src/engine/world/world-ui.ts +313 -0
- package/src/engine/world/world3d.ts +529 -0
- package/src/helpers/collision.ts +487 -0
- package/src/helpers/index.ts +2 -0
- package/src/helpers/raycast.ts +442 -0
- package/src/index.ts +30 -1
- package/src/mount.ts +2 -0
- package/src/types/cmds/audio.ts +73 -48
- package/src/types/cmds/camera.ts +12 -8
- package/src/types/cmds/environment.ts +9 -3
- package/src/types/cmds/geometry.ts +15 -16
- package/src/types/cmds/index.ts +234 -162
- package/src/types/cmds/input.ts +39 -0
- package/src/types/cmds/light.ts +12 -11
- package/src/types/cmds/material.ts +19 -21
- package/src/types/cmds/model.ts +17 -15
- package/src/types/cmds/realm.ts +23 -0
- package/src/types/cmds/system.ts +29 -0
- package/src/types/cmds/target.ts +96 -0
- package/src/types/cmds/texture.ts +13 -3
- package/src/types/cmds/ui.ts +220 -0
- package/src/types/cmds/window.ts +41 -204
- package/src/types/events/index.ts +4 -1
- package/src/types/events/keyboard.ts +2 -2
- package/src/types/events/pointer.ts +85 -13
- package/src/types/events/system.ts +188 -30
- package/src/types/events/ui.ts +21 -0
- package/src/types/index.ts +1 -0
- package/src/types/json.ts +15 -0
- package/src/window.ts +8 -0
- package/src/world-ui.ts +2 -0
- package/src/world3d.ts +10 -0
- package/tsconfig.json +0 -29
|
@@ -1,26 +1,72 @@
|
|
|
1
1
|
import type { ShadowConfig } from '../../types/cmds/shadow';
|
|
2
2
|
import type { EnvironmentConfig } from '../../types/cmds/environment';
|
|
3
|
+
import type {
|
|
4
|
+
CmdEnvironmentDisposeArgs,
|
|
5
|
+
} from '../../types/cmds/environment';
|
|
6
|
+
import type {
|
|
7
|
+
CmdTargetMeasurementArgs,
|
|
8
|
+
CmdTargetDisposeArgs,
|
|
9
|
+
CmdTargetLayerDisposeArgs,
|
|
10
|
+
CmdTargetLayerUpsertArgs,
|
|
11
|
+
CmdTargetUpsertArgs,
|
|
12
|
+
TargetLayerLayout,
|
|
13
|
+
} from '../../types/cmds/target';
|
|
3
14
|
import type {
|
|
4
15
|
CmdAudioListenerCreateArgs,
|
|
5
16
|
CmdAudioListenerDisposeArgs,
|
|
17
|
+
CmdAudioListenerUpsertArgs,
|
|
6
18
|
CmdAudioListenerUpdateArgs,
|
|
7
|
-
|
|
19
|
+
CmdAudioResourceUpsertArgs,
|
|
8
20
|
CmdAudioResourceDisposeArgs,
|
|
9
|
-
|
|
21
|
+
CmdAudioSourceTransportArgs,
|
|
10
22
|
CmdAudioSourceCreateArgs,
|
|
11
23
|
CmdAudioSourceDisposeArgs,
|
|
12
|
-
|
|
13
|
-
CmdAudioSourcePlayArgs,
|
|
14
|
-
CmdAudioSourceStopArgs,
|
|
24
|
+
CmdAudioSourceUpsertArgs,
|
|
15
25
|
CmdAudioSourceUpdateArgs,
|
|
26
|
+
CmdAudioStateGetArgs,
|
|
16
27
|
} from '../../types/cmds/audio';
|
|
17
28
|
import type { CmdPoseUpdateArgs } from '../../types/cmds/model';
|
|
18
|
-
import type {
|
|
29
|
+
import type {
|
|
30
|
+
CmdSystemDiagnosticsSetArgs,
|
|
31
|
+
CmdUploadBufferDiscardAllArgs,
|
|
32
|
+
} from '../../types/cmds/system';
|
|
33
|
+
import type {
|
|
34
|
+
CmdInputTargetListenerDisposeArgs,
|
|
35
|
+
CmdInputTargetListenerListArgs,
|
|
36
|
+
CmdInputTargetListenerUpsertArgs,
|
|
37
|
+
} from '../../types/cmds/input';
|
|
38
|
+
import type { CmdTextureBindTargetArgs } from '../../types/cmds/texture';
|
|
39
|
+
import type {
|
|
40
|
+
CmdUiAccessKitActionRequestArgs,
|
|
41
|
+
CmdUiApplyOpsArgs,
|
|
42
|
+
CmdUiClipboardPasteArgs,
|
|
43
|
+
CmdUiDebugSetArgs,
|
|
44
|
+
CmdUiDocumentCreateArgs,
|
|
45
|
+
CmdUiDocumentDisposeArgs,
|
|
46
|
+
CmdUiDocumentGetLayoutRectsArgs,
|
|
47
|
+
CmdUiDocumentGetTreeArgs,
|
|
48
|
+
CmdUiDocumentSetRectArgs,
|
|
49
|
+
CmdUiDocumentSetThemeArgs,
|
|
50
|
+
CmdUiEventTraceSetArgs,
|
|
51
|
+
CmdUiFocusGetArgs,
|
|
52
|
+
CmdUiFocusSetArgs,
|
|
53
|
+
CmdUiImageCreateFromBufferArgs,
|
|
54
|
+
CmdUiImageDisposeArgs,
|
|
55
|
+
CmdUiScreenshotReplyArgs,
|
|
56
|
+
CmdUiThemeDefineArgs,
|
|
57
|
+
CmdUiThemeDisposeArgs,
|
|
58
|
+
} from '../../types/cmds/ui';
|
|
59
|
+
import type { NotificationLevel } from '../../types/kinds';
|
|
60
|
+
import { EngineError } from '../errors';
|
|
19
61
|
import { getWorldOrThrow, requireInitialized } from '../bridge/guards';
|
|
20
|
-
import {
|
|
62
|
+
import {
|
|
63
|
+
enqueueCommand,
|
|
64
|
+
enqueueGlobalCommand,
|
|
65
|
+
markRoutingIndexDirty,
|
|
66
|
+
} from '../bridge/dispatch';
|
|
21
67
|
import type {
|
|
68
|
+
CameraComponent,
|
|
22
69
|
CameraProps,
|
|
23
|
-
CreateWindowProps,
|
|
24
70
|
GeometryProps,
|
|
25
71
|
InputStateComponent,
|
|
26
72
|
Intent,
|
|
@@ -29,11 +75,37 @@ import type {
|
|
|
29
75
|
ModelProps,
|
|
30
76
|
TagProps,
|
|
31
77
|
TextureProps,
|
|
78
|
+
TransformComponent,
|
|
32
79
|
TransformProps,
|
|
33
|
-
|
|
80
|
+
UiFocusCycleMode,
|
|
34
81
|
WindowStateComponent,
|
|
35
82
|
} from '../ecs';
|
|
36
83
|
import { engineState } from '../state';
|
|
84
|
+
import type { GamepadEvent, SystemEvent, UiEvent } from '../../types/events';
|
|
85
|
+
|
|
86
|
+
function allocateGlobalId(): number {
|
|
87
|
+
return engineState.nextGlobalId++;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function recalculateWorldWindowBindings(world: ReturnType<typeof getWorldOrThrow>): void {
|
|
91
|
+
world.boundWindowIds.clear();
|
|
92
|
+
for (const windowId of world.targetWindowBindings.values()) {
|
|
93
|
+
world.boundWindowIds.add(windowId);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (world.boundWindowIds.size === 0) {
|
|
97
|
+
world.primaryWindowId = undefined;
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
let primary = Number.POSITIVE_INFINITY;
|
|
102
|
+
for (const windowId of world.boundWindowIds) {
|
|
103
|
+
if (windowId < primary) {
|
|
104
|
+
primary = windowId;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
world.primaryWindowId = primary;
|
|
108
|
+
}
|
|
37
109
|
|
|
38
110
|
/**
|
|
39
111
|
* Emits an intent to the specified world.
|
|
@@ -42,7 +114,7 @@ import { engineState } from '../state';
|
|
|
42
114
|
export function emitIntent(worldId: number, intent: Intent): void {
|
|
43
115
|
requireInitialized();
|
|
44
116
|
const world = getWorldOrThrow(worldId);
|
|
45
|
-
world.
|
|
117
|
+
world.intentStore.enqueue(intent);
|
|
46
118
|
}
|
|
47
119
|
|
|
48
120
|
/**
|
|
@@ -57,6 +129,308 @@ export function getModelId(worldId: number, entityId: number): number | null {
|
|
|
57
129
|
return model?.id ?? null;
|
|
58
130
|
}
|
|
59
131
|
|
|
132
|
+
/**
|
|
133
|
+
* Returns the core realm id associated with this world, if already created.
|
|
134
|
+
*/
|
|
135
|
+
export function getWorldRealmId(worldId: number): number | null {
|
|
136
|
+
requireInitialized();
|
|
137
|
+
const world = getWorldOrThrow(worldId);
|
|
138
|
+
return world.coreRealmId ?? null;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Returns true when the world has a resolved core realm id.
|
|
143
|
+
*/
|
|
144
|
+
export function isWorldReady(worldId: number): boolean {
|
|
145
|
+
return getWorldRealmId(worldId) !== null;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Upserts a target used to present world output.
|
|
150
|
+
*/
|
|
151
|
+
export function upsertTarget(worldId: number, args: CmdTargetUpsertArgs): number {
|
|
152
|
+
const id = enqueueCommand(worldId, 'cmd-target-upsert', args);
|
|
153
|
+
const world = getWorldOrThrow(worldId);
|
|
154
|
+
if (args.kind === 'window' && args.windowId !== undefined) {
|
|
155
|
+
world.targetWindowBindings.set(args.targetId, args.windowId);
|
|
156
|
+
recalculateWorldWindowBindings(world);
|
|
157
|
+
markRoutingIndexDirty();
|
|
158
|
+
} else {
|
|
159
|
+
if (world.targetWindowBindings.delete(args.targetId)) {
|
|
160
|
+
recalculateWorldWindowBindings(world);
|
|
161
|
+
}
|
|
162
|
+
markRoutingIndexDirty();
|
|
163
|
+
}
|
|
164
|
+
return id;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Requests size measurements for a target.
|
|
169
|
+
*/
|
|
170
|
+
export function measureTarget(
|
|
171
|
+
worldId: number,
|
|
172
|
+
args: CmdTargetMeasurementArgs,
|
|
173
|
+
): number {
|
|
174
|
+
return enqueueCommand(worldId, 'cmd-target-measurement', args);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Creates or updates an input listener bound to a routed target.
|
|
179
|
+
*/
|
|
180
|
+
export function upsertInputTargetListener(
|
|
181
|
+
worldId: number,
|
|
182
|
+
args: CmdInputTargetListenerUpsertArgs,
|
|
183
|
+
): number {
|
|
184
|
+
return enqueueCommand(worldId, 'cmd-input-target-listener-upsert', args);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Disposes an input listener bound to routed targets.
|
|
189
|
+
*/
|
|
190
|
+
export function disposeInputTargetListener(
|
|
191
|
+
worldId: number,
|
|
192
|
+
args: CmdInputTargetListenerDisposeArgs,
|
|
193
|
+
): number {
|
|
194
|
+
return enqueueCommand(worldId, 'cmd-input-target-listener-dispose', args);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Lists input listeners (optionally filtered by target id).
|
|
199
|
+
*/
|
|
200
|
+
export function listInputTargetListeners(
|
|
201
|
+
worldId: number,
|
|
202
|
+
args: CmdInputTargetListenerListArgs = {},
|
|
203
|
+
): number {
|
|
204
|
+
return enqueueCommand(worldId, 'cmd-input-target-listener-list', args);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Disposes a target.
|
|
209
|
+
*/
|
|
210
|
+
export function disposeTarget(worldId: number, args: CmdTargetDisposeArgs): number {
|
|
211
|
+
const world = getWorldOrThrow(worldId);
|
|
212
|
+
world.targetLayerBindings.delete(args.targetId);
|
|
213
|
+
if (world.targetWindowBindings.delete(args.targetId)) {
|
|
214
|
+
recalculateWorldWindowBindings(world);
|
|
215
|
+
}
|
|
216
|
+
markRoutingIndexDirty();
|
|
217
|
+
return enqueueCommand(worldId, 'cmd-target-dispose', args);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Binds this world's realm to a target layer.
|
|
222
|
+
*/
|
|
223
|
+
export function bindWorldToTarget(
|
|
224
|
+
worldId: number,
|
|
225
|
+
args: Omit<CmdTargetLayerUpsertArgs, 'realmId'>,
|
|
226
|
+
): number {
|
|
227
|
+
const world = getWorldOrThrow(worldId);
|
|
228
|
+
|
|
229
|
+
const resolvedCameraId = args.cameraId ?? findPreferredCameraId(worldId);
|
|
230
|
+
|
|
231
|
+
world.targetLayerBindings.set(args.targetId, {
|
|
232
|
+
targetId: args.targetId,
|
|
233
|
+
layout: args.layout,
|
|
234
|
+
cameraId: resolvedCameraId,
|
|
235
|
+
environmentId: args.environmentId,
|
|
236
|
+
});
|
|
237
|
+
markRoutingIndexDirty();
|
|
238
|
+
|
|
239
|
+
const realmId = getWorldRealmId(worldId);
|
|
240
|
+
if (realmId === null) {
|
|
241
|
+
// Realm not ready yet: keep binding cached and let response-decode flush later.
|
|
242
|
+
return 0;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return enqueueCommand(worldId, 'cmd-target-layer-upsert', {
|
|
246
|
+
realmId,
|
|
247
|
+
...args,
|
|
248
|
+
cameraId: resolvedCameraId,
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Unbinds this world's realm from a target layer.
|
|
254
|
+
*/
|
|
255
|
+
export function unbindWorldFromTarget(
|
|
256
|
+
worldId: number,
|
|
257
|
+
args: Omit<CmdTargetLayerDisposeArgs, 'realmId'>,
|
|
258
|
+
): number {
|
|
259
|
+
const world = getWorldOrThrow(worldId);
|
|
260
|
+
world.targetLayerBindings.delete(args.targetId);
|
|
261
|
+
if (world.targetWindowBindings.delete(args.targetId)) {
|
|
262
|
+
recalculateWorldWindowBindings(world);
|
|
263
|
+
}
|
|
264
|
+
markRoutingIndexDirty();
|
|
265
|
+
|
|
266
|
+
const realmId = getWorldRealmId(worldId);
|
|
267
|
+
if (realmId === null) {
|
|
268
|
+
// Realm not ready yet: nothing to dispose in core.
|
|
269
|
+
return 0;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return enqueueCommand(worldId, 'cmd-target-layer-dispose', {
|
|
273
|
+
realmId,
|
|
274
|
+
...args,
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function findPreferredCameraId(worldId: number): number | undefined {
|
|
279
|
+
const world = getWorldOrThrow(worldId);
|
|
280
|
+
let bestId: number | undefined;
|
|
281
|
+
let bestOrder = Number.POSITIVE_INFINITY;
|
|
282
|
+
for (const store of world.components.values()) {
|
|
283
|
+
const camera = store.get('Camera') as CameraComponent | undefined;
|
|
284
|
+
if (!camera) continue;
|
|
285
|
+
if (
|
|
286
|
+
camera.order < bestOrder ||
|
|
287
|
+
(camera.order === bestOrder &&
|
|
288
|
+
(bestId === undefined || camera.id < bestId))
|
|
289
|
+
) {
|
|
290
|
+
bestOrder = camera.order;
|
|
291
|
+
bestId = camera.id;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
return bestId;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Convenience helper: presents this world in a window via target/layer bind.
|
|
299
|
+
*/
|
|
300
|
+
export function presentWorldInWindow(
|
|
301
|
+
worldId: number,
|
|
302
|
+
args: {
|
|
303
|
+
windowId: number;
|
|
304
|
+
targetId?: number;
|
|
305
|
+
layout?: TargetLayerLayout;
|
|
306
|
+
cameraId?: number;
|
|
307
|
+
environmentId?: number;
|
|
308
|
+
},
|
|
309
|
+
): { targetId: number; upsertCommandId: number; bindCommandId: number } {
|
|
310
|
+
requireInitialized();
|
|
311
|
+
const targetId = args.targetId ?? allocateGlobalId();
|
|
312
|
+
const upsertCommandId = upsertTarget(worldId, {
|
|
313
|
+
targetId,
|
|
314
|
+
kind: 'window',
|
|
315
|
+
windowId: args.windowId,
|
|
316
|
+
});
|
|
317
|
+
const bindCommandId = bindWorldToTarget(worldId, {
|
|
318
|
+
targetId,
|
|
319
|
+
layout: args.layout ?? {
|
|
320
|
+
left: { unit: 'percent', value: 0 },
|
|
321
|
+
top: { unit: 'percent', value: 0 },
|
|
322
|
+
width: { unit: 'percent', value: 100 },
|
|
323
|
+
height: { unit: 'percent', value: 100 },
|
|
324
|
+
zIndex: 0,
|
|
325
|
+
blendMode: 0,
|
|
326
|
+
},
|
|
327
|
+
cameraId: args.cameraId,
|
|
328
|
+
environmentId: args.environmentId,
|
|
329
|
+
});
|
|
330
|
+
return { targetId, upsertCommandId, bindCommandId };
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Disposes a world and optionally releases its bound realm/targets.
|
|
335
|
+
* This operation is immediate on host state; core-side disposal commands are queued globally.
|
|
336
|
+
*/
|
|
337
|
+
export function disposeWorld(
|
|
338
|
+
worldId: number,
|
|
339
|
+
opts: {
|
|
340
|
+
disposeRealm?: boolean;
|
|
341
|
+
disposeTargets?: boolean;
|
|
342
|
+
warnOnUndisposedResources?: boolean;
|
|
343
|
+
strictResourceLifecycle?: boolean;
|
|
344
|
+
} = {},
|
|
345
|
+
): void {
|
|
346
|
+
requireInitialized();
|
|
347
|
+
const world = getWorldOrThrow(worldId);
|
|
348
|
+
const disposeRealm = opts.disposeRealm ?? true;
|
|
349
|
+
const disposeTargets = opts.disposeTargets ?? true;
|
|
350
|
+
const strictResourceLifecycle = opts.strictResourceLifecycle ?? false;
|
|
351
|
+
const warnOnUndisposedResources = opts.warnOnUndisposedResources ?? true;
|
|
352
|
+
|
|
353
|
+
let retainedCoreObjectCount = 0;
|
|
354
|
+
for (const store of world.components.values()) {
|
|
355
|
+
for (const comp of store.values()) {
|
|
356
|
+
if ('id' in comp && typeof comp.id === 'number') {
|
|
357
|
+
retainedCoreObjectCount++;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
const hasRetainedTargets = world.targetLayerBindings.size > 0;
|
|
362
|
+
const hasPendingWork =
|
|
363
|
+
world.intentStore.size() > 0 || world.pendingCommands.length > 0;
|
|
364
|
+
|
|
365
|
+
if (
|
|
366
|
+
(!disposeRealm && (retainedCoreObjectCount > 0 || hasPendingWork)) ||
|
|
367
|
+
(!disposeTargets && hasRetainedTargets)
|
|
368
|
+
) {
|
|
369
|
+
const message =
|
|
370
|
+
`disposeWorld(${worldId}) called without fully releasing resources. ` +
|
|
371
|
+
`retainRealm=${!disposeRealm} retainTargets=${!disposeTargets} ` +
|
|
372
|
+
`trackedCoreObjects=${retainedCoreObjectCount} ` +
|
|
373
|
+
`targetBindings=${world.targetLayerBindings.size} ` +
|
|
374
|
+
`pendingIntents=${world.intentStore.size()} ` +
|
|
375
|
+
`pendingCommands=${world.pendingCommands.length}`;
|
|
376
|
+
if (strictResourceLifecycle) {
|
|
377
|
+
throw new EngineError('WorldDisposeLifecycleRisk', message);
|
|
378
|
+
}
|
|
379
|
+
if (warnOnUndisposedResources) {
|
|
380
|
+
console.warn(`[World ${worldId}] ${message}`);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
if (disposeTargets) {
|
|
385
|
+
for (const targetId of world.targetLayerBindings.keys()) {
|
|
386
|
+
let isShared = false;
|
|
387
|
+
for (const [otherWorldId, otherWorld] of engineState.worlds) {
|
|
388
|
+
if (otherWorldId === worldId) continue;
|
|
389
|
+
if (otherWorld.targetLayerBindings.has(targetId)) {
|
|
390
|
+
isShared = true;
|
|
391
|
+
break;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
if (!isShared) {
|
|
395
|
+
enqueueGlobalCommand('cmd-target-dispose', { targetId });
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
if (disposeRealm && world.coreRealmId !== undefined) {
|
|
401
|
+
enqueueGlobalCommand('cmd-realm-dispose', { realmId: world.coreRealmId });
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
for (const [cmdId, trackedWorldId] of engineState.commandTracker) {
|
|
405
|
+
if (trackedWorldId === worldId) {
|
|
406
|
+
engineState.commandTracker.delete(cmdId);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
engineState.worlds.delete(worldId);
|
|
411
|
+
markRoutingIndexDirty();
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/**
|
|
415
|
+
* Configures global runtime diagnostics and pointer tracing.
|
|
416
|
+
*/
|
|
417
|
+
export function setSystemDiagnostics(
|
|
418
|
+
args: CmdSystemDiagnosticsSetArgs,
|
|
419
|
+
): number {
|
|
420
|
+
requireInitialized();
|
|
421
|
+
return enqueueGlobalCommand('cmd-system-diagnostics-set', args);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* Requests the core to discard all pending upload buffers.
|
|
426
|
+
*/
|
|
427
|
+
export function discardAllUploadBuffers(
|
|
428
|
+
args: CmdUploadBufferDiscardAllArgs = {},
|
|
429
|
+
): number {
|
|
430
|
+
requireInitialized();
|
|
431
|
+
return enqueueGlobalCommand('cmd-upload-buffer-discard-all', args);
|
|
432
|
+
}
|
|
433
|
+
|
|
60
434
|
/**
|
|
61
435
|
* Sends an audio listener update command.
|
|
62
436
|
*/
|
|
@@ -64,7 +438,17 @@ export function audioListenerUpdate(
|
|
|
64
438
|
worldId: number,
|
|
65
439
|
args: CmdAudioListenerUpdateArgs,
|
|
66
440
|
): number {
|
|
67
|
-
return enqueueCommand(worldId, 'cmd-audio-listener-
|
|
441
|
+
return enqueueCommand(worldId, 'cmd-audio-listener-upsert', args);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/**
|
|
445
|
+
* Binds a texture id to a texture target output.
|
|
446
|
+
*/
|
|
447
|
+
export function bindTextureToTarget(
|
|
448
|
+
worldId: number,
|
|
449
|
+
args: CmdTextureBindTargetArgs,
|
|
450
|
+
): number {
|
|
451
|
+
return enqueueCommand(worldId, 'cmd-texture-bind-target', args);
|
|
68
452
|
}
|
|
69
453
|
|
|
70
454
|
/**
|
|
@@ -74,7 +458,17 @@ export function audioListenerCreate(
|
|
|
74
458
|
worldId: number,
|
|
75
459
|
args: CmdAudioListenerCreateArgs,
|
|
76
460
|
): number {
|
|
77
|
-
return enqueueCommand(worldId, 'cmd-audio-listener-
|
|
461
|
+
return enqueueCommand(worldId, 'cmd-audio-listener-upsert', args);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/**
|
|
465
|
+
* Upserts audio listener params or binding.
|
|
466
|
+
*/
|
|
467
|
+
export function audioListenerUpsert(
|
|
468
|
+
worldId: number,
|
|
469
|
+
args: CmdAudioListenerUpsertArgs,
|
|
470
|
+
): number {
|
|
471
|
+
return enqueueCommand(worldId, 'cmd-audio-listener-upsert', args);
|
|
78
472
|
}
|
|
79
473
|
|
|
80
474
|
/**
|
|
@@ -92,9 +486,9 @@ export function audioListenerDispose(
|
|
|
92
486
|
*/
|
|
93
487
|
export function audioResourceCreate(
|
|
94
488
|
worldId: number,
|
|
95
|
-
args:
|
|
489
|
+
args: CmdAudioResourceUpsertArgs,
|
|
96
490
|
): number {
|
|
97
|
-
return enqueueCommand(worldId, 'cmd-audio-resource-
|
|
491
|
+
return enqueueCommand(worldId, 'cmd-audio-resource-upsert', args);
|
|
98
492
|
}
|
|
99
493
|
|
|
100
494
|
/**
|
|
@@ -102,9 +496,9 @@ export function audioResourceCreate(
|
|
|
102
496
|
*/
|
|
103
497
|
export function audioResourcePush(
|
|
104
498
|
worldId: number,
|
|
105
|
-
args:
|
|
499
|
+
args: CmdAudioResourceUpsertArgs,
|
|
106
500
|
): number {
|
|
107
|
-
return enqueueCommand(worldId, 'cmd-audio-resource-
|
|
501
|
+
return enqueueCommand(worldId, 'cmd-audio-resource-upsert', args);
|
|
108
502
|
}
|
|
109
503
|
|
|
110
504
|
/**
|
|
@@ -124,7 +518,7 @@ export function audioSourceCreate(
|
|
|
124
518
|
worldId: number,
|
|
125
519
|
args: CmdAudioSourceCreateArgs,
|
|
126
520
|
): number {
|
|
127
|
-
return enqueueCommand(worldId, 'cmd-audio-source-
|
|
521
|
+
return enqueueCommand(worldId, 'cmd-audio-source-upsert', args);
|
|
128
522
|
}
|
|
129
523
|
|
|
130
524
|
/**
|
|
@@ -134,7 +528,17 @@ export function audioSourceUpdate(
|
|
|
134
528
|
worldId: number,
|
|
135
529
|
args: CmdAudioSourceUpdateArgs,
|
|
136
530
|
): number {
|
|
137
|
-
return enqueueCommand(worldId, 'cmd-audio-source-
|
|
531
|
+
return enqueueCommand(worldId, 'cmd-audio-source-upsert', args);
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
/**
|
|
535
|
+
* Upserts audio source params or binding.
|
|
536
|
+
*/
|
|
537
|
+
export function audioSourceUpsert(
|
|
538
|
+
worldId: number,
|
|
539
|
+
args: CmdAudioSourceUpsertArgs,
|
|
540
|
+
): number {
|
|
541
|
+
return enqueueCommand(worldId, 'cmd-audio-source-upsert', args);
|
|
138
542
|
}
|
|
139
543
|
|
|
140
544
|
/**
|
|
@@ -142,9 +546,12 @@ export function audioSourceUpdate(
|
|
|
142
546
|
*/
|
|
143
547
|
export function audioSourcePlay(
|
|
144
548
|
worldId: number,
|
|
145
|
-
args:
|
|
549
|
+
args: Omit<CmdAudioSourceTransportArgs, 'action'>,
|
|
146
550
|
): number {
|
|
147
|
-
return enqueueCommand(worldId, 'cmd-audio-source-
|
|
551
|
+
return enqueueCommand(worldId, 'cmd-audio-source-transport', {
|
|
552
|
+
...args,
|
|
553
|
+
action: 'play',
|
|
554
|
+
});
|
|
148
555
|
}
|
|
149
556
|
|
|
150
557
|
/**
|
|
@@ -152,9 +559,12 @@ export function audioSourcePlay(
|
|
|
152
559
|
*/
|
|
153
560
|
export function audioSourcePause(
|
|
154
561
|
worldId: number,
|
|
155
|
-
args:
|
|
562
|
+
args: Omit<CmdAudioSourceTransportArgs, 'action'>,
|
|
156
563
|
): number {
|
|
157
|
-
return enqueueCommand(worldId, 'cmd-audio-source-
|
|
564
|
+
return enqueueCommand(worldId, 'cmd-audio-source-transport', {
|
|
565
|
+
...args,
|
|
566
|
+
action: 'pause',
|
|
567
|
+
});
|
|
158
568
|
}
|
|
159
569
|
|
|
160
570
|
/**
|
|
@@ -162,9 +572,22 @@ export function audioSourcePause(
|
|
|
162
572
|
*/
|
|
163
573
|
export function audioSourceStop(
|
|
164
574
|
worldId: number,
|
|
165
|
-
args:
|
|
575
|
+
args: Omit<CmdAudioSourceTransportArgs, 'action'>,
|
|
576
|
+
): number {
|
|
577
|
+
return enqueueCommand(worldId, 'cmd-audio-source-transport', {
|
|
578
|
+
...args,
|
|
579
|
+
action: 'stop',
|
|
580
|
+
});
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
/**
|
|
584
|
+
* Requests a snapshot of audio runtime state.
|
|
585
|
+
*/
|
|
586
|
+
export function audioStateGet(
|
|
587
|
+
worldId: number,
|
|
588
|
+
args: CmdAudioStateGetArgs = {},
|
|
166
589
|
): number {
|
|
167
|
-
return enqueueCommand(worldId, 'cmd-audio-
|
|
590
|
+
return enqueueCommand(worldId, 'cmd-audio-state-get', args);
|
|
168
591
|
}
|
|
169
592
|
|
|
170
593
|
/**
|
|
@@ -185,72 +608,77 @@ export function poseUpdate(worldId: number, args: CmdPoseUpdateArgs): number {
|
|
|
185
608
|
}
|
|
186
609
|
|
|
187
610
|
/**
|
|
188
|
-
*
|
|
611
|
+
* Requests a list of resources from the engine for debugging.
|
|
189
612
|
*/
|
|
190
|
-
export function
|
|
613
|
+
export function requestResourceList(
|
|
614
|
+
worldId: number,
|
|
615
|
+
resourceType:
|
|
616
|
+
| 'model'
|
|
617
|
+
| 'material'
|
|
618
|
+
| 'texture'
|
|
619
|
+
| 'geometry'
|
|
620
|
+
| 'light'
|
|
621
|
+
| 'camera',
|
|
622
|
+
): void {
|
|
191
623
|
emitIntent(worldId, {
|
|
192
|
-
type: '
|
|
193
|
-
|
|
624
|
+
type: 'request-resource-list',
|
|
625
|
+
resourceType,
|
|
194
626
|
});
|
|
195
627
|
}
|
|
196
628
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
629
|
+
function resolveWorldWindowId(worldId: number): number {
|
|
630
|
+
const world = getWorldOrThrow(worldId);
|
|
631
|
+
if (world.primaryWindowId === undefined) {
|
|
632
|
+
for (const windowId of world.targetWindowBindings.values()) {
|
|
633
|
+
return windowId;
|
|
634
|
+
}
|
|
635
|
+
throw new EngineError(
|
|
636
|
+
'WindowNotFound',
|
|
637
|
+
`World ${worldId} has no window binding available for this command.`,
|
|
638
|
+
);
|
|
639
|
+
}
|
|
640
|
+
return world.primaryWindowId;
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
/** Requests model list for a world window context. */
|
|
644
|
+
export function listModels(worldId: number): number {
|
|
645
|
+
return enqueueCommand(worldId, 'cmd-model-list', {
|
|
646
|
+
windowId: resolveWorldWindowId(worldId),
|
|
203
647
|
});
|
|
204
648
|
}
|
|
205
649
|
|
|
206
|
-
/**
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
emitIntent(worldId, {
|
|
211
|
-
type: 'update-window',
|
|
212
|
-
props,
|
|
650
|
+
/** Requests material list for a world window context. */
|
|
651
|
+
export function listMaterials(worldId: number): number {
|
|
652
|
+
return enqueueCommand(worldId, 'cmd-material-list', {
|
|
653
|
+
windowId: resolveWorldWindowId(worldId),
|
|
213
654
|
});
|
|
214
655
|
}
|
|
215
656
|
|
|
216
|
-
/**
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
worldId: number,
|
|
221
|
-
attentionType?: UserAttentionType,
|
|
222
|
-
): void {
|
|
223
|
-
emitIntent(worldId, {
|
|
224
|
-
type: 'request-attention',
|
|
225
|
-
attentionType,
|
|
657
|
+
/** Requests texture list for a world window context. */
|
|
658
|
+
export function listTextures(worldId: number): number {
|
|
659
|
+
return enqueueCommand(worldId, 'cmd-texture-list', {
|
|
660
|
+
windowId: resolveWorldWindowId(worldId),
|
|
226
661
|
});
|
|
227
662
|
}
|
|
228
663
|
|
|
229
|
-
/**
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
emitIntent(worldId, {
|
|
234
|
-
type: 'focus-window',
|
|
664
|
+
/** Requests geometry list for a world window context. */
|
|
665
|
+
export function listGeometries(worldId: number): number {
|
|
666
|
+
return enqueueCommand(worldId, 'cmd-geometry-list', {
|
|
667
|
+
windowId: resolveWorldWindowId(worldId),
|
|
235
668
|
});
|
|
236
669
|
}
|
|
237
670
|
|
|
238
|
-
/**
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
| 'camera',
|
|
250
|
-
): void {
|
|
251
|
-
emitIntent(worldId, {
|
|
252
|
-
type: 'request-resource-list',
|
|
253
|
-
resourceType,
|
|
671
|
+
/** Requests light list for a world window context. */
|
|
672
|
+
export function listLights(worldId: number): number {
|
|
673
|
+
return enqueueCommand(worldId, 'cmd-light-list', {
|
|
674
|
+
windowId: resolveWorldWindowId(worldId),
|
|
675
|
+
});
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
/** Requests camera list for a world window context. */
|
|
679
|
+
export function listCameras(worldId: number): number {
|
|
680
|
+
return enqueueCommand(worldId, 'cmd-camera-list', {
|
|
681
|
+
windowId: resolveWorldWindowId(worldId),
|
|
254
682
|
});
|
|
255
683
|
}
|
|
256
684
|
|
|
@@ -317,6 +745,237 @@ export function configureEnvironment(
|
|
|
317
745
|
});
|
|
318
746
|
}
|
|
319
747
|
|
|
748
|
+
/**
|
|
749
|
+
* Disposes an environment profile.
|
|
750
|
+
* If omitted, defaults to this world id (engine convention).
|
|
751
|
+
*/
|
|
752
|
+
export function disposeEnvironment(
|
|
753
|
+
worldId: number,
|
|
754
|
+
args: CmdEnvironmentDisposeArgs = { environmentId: worldId },
|
|
755
|
+
): number {
|
|
756
|
+
return enqueueCommand(worldId, 'cmd-environment-dispose', args);
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
/**
|
|
760
|
+
* Defines or updates a UI theme.
|
|
761
|
+
*/
|
|
762
|
+
export function uiDefineTheme(worldId: number, args: CmdUiThemeDefineArgs): void {
|
|
763
|
+
emitIntent(worldId, { type: 'ui-theme-define', args });
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
/**
|
|
767
|
+
* Disposes a UI theme.
|
|
768
|
+
*/
|
|
769
|
+
export function uiDisposeTheme(worldId: number, args: CmdUiThemeDisposeArgs): void {
|
|
770
|
+
emitIntent(worldId, { type: 'ui-theme-dispose', args });
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
/**
|
|
774
|
+
* Creates a UI document.
|
|
775
|
+
*/
|
|
776
|
+
export function uiCreateDocument(worldId: number, args: CmdUiDocumentCreateArgs): void {
|
|
777
|
+
emitIntent(worldId, { type: 'ui-document-create', args });
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
/**
|
|
781
|
+
* Disposes a UI document.
|
|
782
|
+
*/
|
|
783
|
+
export function uiDisposeDocument(worldId: number, args: CmdUiDocumentDisposeArgs): void {
|
|
784
|
+
emitIntent(worldId, { type: 'ui-document-dispose', args });
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
/**
|
|
788
|
+
* Updates document rectangle.
|
|
789
|
+
*/
|
|
790
|
+
export function uiSetDocumentRect(worldId: number, args: CmdUiDocumentSetRectArgs): void {
|
|
791
|
+
emitIntent(worldId, { type: 'ui-document-set-rect', args });
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
/**
|
|
795
|
+
* Updates document theme.
|
|
796
|
+
*/
|
|
797
|
+
export function uiSetDocumentTheme(worldId: number, args: CmdUiDocumentSetThemeArgs): void {
|
|
798
|
+
emitIntent(worldId, { type: 'ui-document-set-theme', args });
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
/**
|
|
802
|
+
* Applies document ops.
|
|
803
|
+
*/
|
|
804
|
+
export function uiApplyOps(worldId: number, args: CmdUiApplyOpsArgs): void {
|
|
805
|
+
emitIntent(worldId, { type: 'ui-apply-ops', args });
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
/**
|
|
809
|
+
* Requests UI document tree for introspection.
|
|
810
|
+
*/
|
|
811
|
+
export function uiGetDocumentTree(worldId: number, args: CmdUiDocumentGetTreeArgs): void {
|
|
812
|
+
emitIntent(worldId, { type: 'ui-document-get-tree', args });
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
/**
|
|
816
|
+
* Requests UI layout rects for introspection.
|
|
817
|
+
*/
|
|
818
|
+
export function uiGetLayoutRects(
|
|
819
|
+
worldId: number,
|
|
820
|
+
args: CmdUiDocumentGetLayoutRectsArgs,
|
|
821
|
+
): void {
|
|
822
|
+
emitIntent(worldId, { type: 'ui-document-get-layout-rects', args });
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
/**
|
|
826
|
+
* Enables/disables runtime UI debug overlays.
|
|
827
|
+
*/
|
|
828
|
+
export function uiSetDebug(worldId: number, args: CmdUiDebugSetArgs): void {
|
|
829
|
+
emitIntent(worldId, { type: 'ui-debug-set', args });
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
/**
|
|
833
|
+
* Sets focused UI node.
|
|
834
|
+
*/
|
|
835
|
+
export function uiSetFocus(worldId: number, args: CmdUiFocusSetArgs): void {
|
|
836
|
+
emitIntent(worldId, { type: 'ui-focus-set', args });
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
/**
|
|
840
|
+
* Requests current UI focus state.
|
|
841
|
+
*/
|
|
842
|
+
export function uiGetFocus(worldId: number, args: CmdUiFocusGetArgs = {}): void {
|
|
843
|
+
emitIntent(worldId, { type: 'ui-focus-get', args });
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
/**
|
|
847
|
+
* Configures UI event trace level/sampling.
|
|
848
|
+
*/
|
|
849
|
+
export function uiSetEventTrace(worldId: number, args: CmdUiEventTraceSetArgs): void {
|
|
850
|
+
emitIntent(worldId, { type: 'ui-event-trace-set', args });
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
/**
|
|
854
|
+
* Creates a UI image from uploaded bytes.
|
|
855
|
+
*/
|
|
856
|
+
export function uiCreateImageFromBuffer(
|
|
857
|
+
worldId: number,
|
|
858
|
+
args: CmdUiImageCreateFromBufferArgs,
|
|
859
|
+
): void {
|
|
860
|
+
emitIntent(worldId, { type: 'ui-image-create-from-buffer', args });
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
/**
|
|
864
|
+
* Disposes a UI image.
|
|
865
|
+
*/
|
|
866
|
+
export function uiDisposeImage(worldId: number, args: CmdUiImageDisposeArgs): void {
|
|
867
|
+
emitIntent(worldId, { type: 'ui-image-dispose', args });
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
/**
|
|
871
|
+
* Delivers host clipboard paste event to UI.
|
|
872
|
+
*/
|
|
873
|
+
export function uiClipboardPaste(worldId: number, args: CmdUiClipboardPasteArgs): void {
|
|
874
|
+
emitIntent(worldId, { type: 'ui-clipboard-paste', args });
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
/**
|
|
878
|
+
* Delivers screenshot response bytes to UI.
|
|
879
|
+
*/
|
|
880
|
+
export function uiScreenshotReply(worldId: number, args: CmdUiScreenshotReplyArgs): void {
|
|
881
|
+
emitIntent(worldId, { type: 'ui-screenshot-reply', args });
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
/**
|
|
885
|
+
* Delivers AccessKit action request to UI.
|
|
886
|
+
*/
|
|
887
|
+
export function uiAccessKitActionRequest(
|
|
888
|
+
worldId: number,
|
|
889
|
+
args: CmdUiAccessKitActionRequestArgs,
|
|
890
|
+
): void {
|
|
891
|
+
emitIntent(worldId, { type: 'ui-access-kit-action-request', args });
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
/**
|
|
895
|
+
* Registers or updates a UI form scope used for tab navigation.
|
|
896
|
+
*/
|
|
897
|
+
export function uiFormUpsert(
|
|
898
|
+
worldId: number,
|
|
899
|
+
form: {
|
|
900
|
+
formId: string;
|
|
901
|
+
windowId: number;
|
|
902
|
+
realmId: number;
|
|
903
|
+
documentId: number;
|
|
904
|
+
disabled?: boolean;
|
|
905
|
+
cycleMode?: UiFocusCycleMode;
|
|
906
|
+
activeFieldsetId?: string;
|
|
907
|
+
},
|
|
908
|
+
): void {
|
|
909
|
+
emitIntent(worldId, { type: 'ui-form-upsert', form });
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
/**
|
|
913
|
+
* Disposes a registered UI form scope.
|
|
914
|
+
*/
|
|
915
|
+
export function uiFormDispose(worldId: number, formId: string): void {
|
|
916
|
+
emitIntent(worldId, { type: 'ui-form-dispose', formId });
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
/**
|
|
920
|
+
* Registers or updates fieldset metadata.
|
|
921
|
+
*/
|
|
922
|
+
export function uiFieldsetUpsert(
|
|
923
|
+
worldId: number,
|
|
924
|
+
fieldset: {
|
|
925
|
+
formId: string;
|
|
926
|
+
fieldsetId: string;
|
|
927
|
+
disabled?: boolean;
|
|
928
|
+
legendNodeId?: number;
|
|
929
|
+
},
|
|
930
|
+
): void {
|
|
931
|
+
emitIntent(worldId, { type: 'ui-fieldset-upsert', fieldset });
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
/**
|
|
935
|
+
* Disposes fieldset metadata.
|
|
936
|
+
*/
|
|
937
|
+
export function uiFieldsetDispose(
|
|
938
|
+
worldId: number,
|
|
939
|
+
formId: string,
|
|
940
|
+
fieldsetId: string,
|
|
941
|
+
): void {
|
|
942
|
+
emitIntent(worldId, { type: 'ui-fieldset-dispose', formId, fieldsetId });
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
/**
|
|
946
|
+
* Registers or updates focusable node metadata.
|
|
947
|
+
*/
|
|
948
|
+
export function uiFocusableUpsert(
|
|
949
|
+
worldId: number,
|
|
950
|
+
focusable: {
|
|
951
|
+
formId: string;
|
|
952
|
+
nodeId: number;
|
|
953
|
+
tabIndex?: number;
|
|
954
|
+
fieldsetId?: string;
|
|
955
|
+
disabled?: boolean;
|
|
956
|
+
orderHint?: number;
|
|
957
|
+
},
|
|
958
|
+
): void {
|
|
959
|
+
emitIntent(worldId, { type: 'ui-focusable-upsert', focusable });
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
/**
|
|
963
|
+
* Disposes focusable node metadata.
|
|
964
|
+
*/
|
|
965
|
+
export function uiFocusableDispose(worldId: number, nodeId: number): void {
|
|
966
|
+
emitIntent(worldId, { type: 'ui-focusable-dispose', nodeId });
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
/**
|
|
970
|
+
* Advances focus within a form scope using tab ordering.
|
|
971
|
+
*/
|
|
972
|
+
export function uiFocusNext(
|
|
973
|
+
worldId: number,
|
|
974
|
+
args: { windowId: number; backwards?: boolean; formId?: string },
|
|
975
|
+
): void {
|
|
976
|
+
emitIntent(worldId, { type: 'ui-focus-next', ...args });
|
|
977
|
+
}
|
|
978
|
+
|
|
320
979
|
/**
|
|
321
980
|
* Draws a debug gizmo line for one frame.
|
|
322
981
|
*/
|
|
@@ -435,6 +1094,44 @@ export function updateTransform(
|
|
|
435
1094
|
entityId: number,
|
|
436
1095
|
props: TransformProps,
|
|
437
1096
|
): void {
|
|
1097
|
+
// Apply immediately to local ECS snapshot so same-frame queries
|
|
1098
|
+
// (for example gizmo/collision helpers) observe latest transform.
|
|
1099
|
+
// Intent is still emitted to keep the standard system pipeline authoritative.
|
|
1100
|
+
const world = getWorldOrThrow(worldId);
|
|
1101
|
+
const store = world.components.get(entityId);
|
|
1102
|
+
const transform = store?.get('Transform') as TransformComponent | undefined;
|
|
1103
|
+
if (transform) {
|
|
1104
|
+
if (props.position) {
|
|
1105
|
+
transform.position = [
|
|
1106
|
+
props.position[0] ?? transform.position[0],
|
|
1107
|
+
props.position[1] ?? transform.position[1],
|
|
1108
|
+
props.position[2] ?? transform.position[2],
|
|
1109
|
+
];
|
|
1110
|
+
}
|
|
1111
|
+
if (props.rotation) {
|
|
1112
|
+
transform.rotation = [
|
|
1113
|
+
props.rotation[0] ?? transform.rotation[0],
|
|
1114
|
+
props.rotation[1] ?? transform.rotation[1],
|
|
1115
|
+
props.rotation[2] ?? transform.rotation[2],
|
|
1116
|
+
props.rotation[3] ?? transform.rotation[3],
|
|
1117
|
+
];
|
|
1118
|
+
}
|
|
1119
|
+
if (props.scale) {
|
|
1120
|
+
transform.scale = [
|
|
1121
|
+
props.scale[0] ?? transform.scale[0],
|
|
1122
|
+
props.scale[1] ?? transform.scale[1],
|
|
1123
|
+
props.scale[2] ?? transform.scale[2],
|
|
1124
|
+
];
|
|
1125
|
+
}
|
|
1126
|
+
if (props.layerMask !== undefined) {
|
|
1127
|
+
transform.layerMask = props.layerMask;
|
|
1128
|
+
}
|
|
1129
|
+
if (props.visible !== undefined) {
|
|
1130
|
+
transform.visible = props.visible;
|
|
1131
|
+
}
|
|
1132
|
+
world.constraintDirtyEntities.add(entityId);
|
|
1133
|
+
}
|
|
1134
|
+
|
|
438
1135
|
emitIntent(worldId, {
|
|
439
1136
|
type: 'update-transform',
|
|
440
1137
|
entityId,
|
|
@@ -465,6 +1162,12 @@ export function setParent(
|
|
|
465
1162
|
entityId: number,
|
|
466
1163
|
parentId: number | null,
|
|
467
1164
|
): void {
|
|
1165
|
+
if (parentId !== null && parentId === entityId) {
|
|
1166
|
+
throw new EngineError(
|
|
1167
|
+
'InvalidParent',
|
|
1168
|
+
`Entity ${entityId} cannot be parent of itself.`,
|
|
1169
|
+
);
|
|
1170
|
+
}
|
|
468
1171
|
emitIntent(worldId, {
|
|
469
1172
|
type: 'set-parent',
|
|
470
1173
|
entityId,
|
|
@@ -481,8 +1184,8 @@ export function setParent(
|
|
|
481
1184
|
*/
|
|
482
1185
|
export function createMaterial(worldId: number, props: MaterialProps): number {
|
|
483
1186
|
requireInitialized();
|
|
484
|
-
|
|
485
|
-
const resourceId =
|
|
1187
|
+
getWorldOrThrow(worldId);
|
|
1188
|
+
const resourceId = allocateGlobalId();
|
|
486
1189
|
|
|
487
1190
|
emitIntent(worldId, {
|
|
488
1191
|
type: 'create-material',
|
|
@@ -498,8 +1201,8 @@ export function createMaterial(worldId: number, props: MaterialProps): number {
|
|
|
498
1201
|
*/
|
|
499
1202
|
export function createGeometry(worldId: number, props: GeometryProps): number {
|
|
500
1203
|
requireInitialized();
|
|
501
|
-
|
|
502
|
-
const resourceId =
|
|
1204
|
+
getWorldOrThrow(worldId);
|
|
1205
|
+
const resourceId = allocateGlobalId();
|
|
503
1206
|
|
|
504
1207
|
emitIntent(worldId, {
|
|
505
1208
|
type: 'create-geometry',
|
|
@@ -515,8 +1218,8 @@ export function createGeometry(worldId: number, props: GeometryProps): number {
|
|
|
515
1218
|
*/
|
|
516
1219
|
export function createTexture(worldId: number, props: TextureProps): number {
|
|
517
1220
|
requireInitialized();
|
|
518
|
-
|
|
519
|
-
const resourceId =
|
|
1221
|
+
getWorldOrThrow(worldId);
|
|
1222
|
+
const resourceId = allocateGlobalId();
|
|
520
1223
|
|
|
521
1224
|
emitIntent(worldId, {
|
|
522
1225
|
type: 'create-texture',
|
|
@@ -580,38 +1283,92 @@ export function isKeyJustReleased(worldId: number, keyCode: number): boolean {
|
|
|
580
1283
|
}
|
|
581
1284
|
|
|
582
1285
|
/**
|
|
583
|
-
* Gets the current
|
|
1286
|
+
* Gets the current pointer position in window space.
|
|
1287
|
+
*/
|
|
1288
|
+
export function getPointerPosition(worldId: number): [number, number] {
|
|
1289
|
+
const state = getInputState(worldId);
|
|
1290
|
+
return state?.pointerPosition ?? [0, 0];
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1293
|
+
/**
|
|
1294
|
+
* Gets the real drawn window area associated with the latest pointer event.
|
|
1295
|
+
*/
|
|
1296
|
+
export function getPointerWindowSize(
|
|
1297
|
+
worldId: number,
|
|
1298
|
+
): [number, number] | null {
|
|
1299
|
+
const state = getInputState(worldId);
|
|
1300
|
+
return state?.pointerWindowSize ?? null;
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1303
|
+
/**
|
|
1304
|
+
* Gets the pointer movement delta for this frame in window space.
|
|
1305
|
+
*/
|
|
1306
|
+
export function getPointerDelta(worldId: number): [number, number] {
|
|
1307
|
+
const state = getInputState(worldId);
|
|
1308
|
+
return state?.pointerDelta ?? [0, 0];
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
/**
|
|
1312
|
+
* Gets the pointer position relative to the current routed target.
|
|
584
1313
|
*/
|
|
585
|
-
export function
|
|
1314
|
+
export function getPointerTargetPosition(
|
|
1315
|
+
worldId: number,
|
|
1316
|
+
): [number, number] | null {
|
|
586
1317
|
const state = getInputState(worldId);
|
|
587
|
-
return state?.
|
|
1318
|
+
return state?.pointerPositionTarget ?? null;
|
|
588
1319
|
}
|
|
589
1320
|
|
|
590
1321
|
/**
|
|
591
|
-
* Gets the
|
|
1322
|
+
* Gets the real drawn target area associated with the latest pointer event.
|
|
592
1323
|
*/
|
|
593
|
-
export function
|
|
1324
|
+
export function getPointerTargetSize(
|
|
1325
|
+
worldId: number,
|
|
1326
|
+
): [number, number] | null {
|
|
594
1327
|
const state = getInputState(worldId);
|
|
595
|
-
return state?.
|
|
1328
|
+
return state?.pointerTargetSize ?? null;
|
|
596
1329
|
}
|
|
597
1330
|
|
|
598
1331
|
/**
|
|
599
|
-
*
|
|
1332
|
+
* Gets the pointer movement delta relative to the current routed target.
|
|
600
1333
|
*/
|
|
601
|
-
export function
|
|
1334
|
+
export function getPointerTargetDelta(worldId: number): [number, number] | null {
|
|
602
1335
|
const state = getInputState(worldId);
|
|
603
|
-
return state?.
|
|
1336
|
+
return state?.pointerTargetDelta ?? null;
|
|
604
1337
|
}
|
|
605
1338
|
|
|
606
1339
|
/**
|
|
607
|
-
*
|
|
1340
|
+
* Gets the routed target under pointer, when available.
|
|
608
1341
|
*/
|
|
609
|
-
export function
|
|
1342
|
+
export function getPointerTargetId(worldId: number): number | null {
|
|
1343
|
+
const state = getInputState(worldId);
|
|
1344
|
+
return state?.pointerTargetId ?? null;
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1347
|
+
/**
|
|
1348
|
+
* Gets pointer UV (0..1) in routed target space, when available.
|
|
1349
|
+
*/
|
|
1350
|
+
export function getPointerTargetUv(worldId: number): [number, number] | null {
|
|
1351
|
+
const state = getInputState(worldId);
|
|
1352
|
+
return state?.pointerTargetUv ?? null;
|
|
1353
|
+
}
|
|
1354
|
+
|
|
1355
|
+
/**
|
|
1356
|
+
* Checks if a pointer button is currently pressed.
|
|
1357
|
+
*/
|
|
1358
|
+
export function isPointerButtonPressed(worldId: number, button: number): boolean {
|
|
1359
|
+
const state = getInputState(worldId);
|
|
1360
|
+
return state?.pointerButtons.has(button) ?? false;
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1363
|
+
/**
|
|
1364
|
+
* Checks if a pointer button was just pressed this frame.
|
|
1365
|
+
*/
|
|
1366
|
+
export function isPointerButtonJustPressed(
|
|
610
1367
|
worldId: number,
|
|
611
1368
|
button: number,
|
|
612
1369
|
): boolean {
|
|
613
1370
|
const state = getInputState(worldId);
|
|
614
|
-
return state?.
|
|
1371
|
+
return state?.pointerJustPressed.has(button) ?? false;
|
|
615
1372
|
}
|
|
616
1373
|
|
|
617
1374
|
/**
|
|
@@ -622,6 +1379,38 @@ export function getScrollDelta(worldId: number): [number, number] {
|
|
|
622
1379
|
return state?.scrollDelta ?? [0, 0];
|
|
623
1380
|
}
|
|
624
1381
|
|
|
1382
|
+
/**
|
|
1383
|
+
* Returns true while IME composition is active for this world.
|
|
1384
|
+
*/
|
|
1385
|
+
export function isImeEnabled(worldId: number): boolean {
|
|
1386
|
+
const state = getInputState(worldId);
|
|
1387
|
+
return state?.imeEnabled ?? false;
|
|
1388
|
+
}
|
|
1389
|
+
|
|
1390
|
+
/**
|
|
1391
|
+
* Returns current IME preedit text, if any.
|
|
1392
|
+
*/
|
|
1393
|
+
export function getImePreeditText(worldId: number): string | null {
|
|
1394
|
+
const state = getInputState(worldId);
|
|
1395
|
+
return state?.imePreeditText ?? null;
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1398
|
+
/**
|
|
1399
|
+
* Returns current IME cursor range inside preedit text, if available.
|
|
1400
|
+
*/
|
|
1401
|
+
export function getImeCursorRange(worldId: number): [number, number] | null {
|
|
1402
|
+
const state = getInputState(worldId);
|
|
1403
|
+
return state?.imeCursorRange ?? null;
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1406
|
+
/**
|
|
1407
|
+
* Returns last IME committed text for the current frame, if any.
|
|
1408
|
+
*/
|
|
1409
|
+
export function getImeCommitText(worldId: number): string | null {
|
|
1410
|
+
const state = getInputState(worldId);
|
|
1411
|
+
return state?.imeCommitText ?? null;
|
|
1412
|
+
}
|
|
1413
|
+
|
|
625
1414
|
/**
|
|
626
1415
|
* Gets the current window size.
|
|
627
1416
|
*/
|
|
@@ -669,3 +1458,121 @@ export function getWindowScaleFactor(worldId: number): number {
|
|
|
669
1458
|
const state = getWindowState(worldId);
|
|
670
1459
|
return state?.scaleFactor ?? 1.0;
|
|
671
1460
|
}
|
|
1461
|
+
|
|
1462
|
+
function getGamepadState(worldId: number):
|
|
1463
|
+
| {
|
|
1464
|
+
connected: Map<number, { name: string }>;
|
|
1465
|
+
buttons: Map<number, Map<number, { pressed: boolean; value: number }>>;
|
|
1466
|
+
axes: Map<number, Map<number, number>>;
|
|
1467
|
+
eventsThisFrame: GamepadEvent[];
|
|
1468
|
+
}
|
|
1469
|
+
| undefined {
|
|
1470
|
+
requireInitialized();
|
|
1471
|
+
const world = getWorldOrThrow(worldId);
|
|
1472
|
+
const worldStore = world.components.get(WORLD_ENTITY_ID);
|
|
1473
|
+
return worldStore?.get('GamepadState') as
|
|
1474
|
+
| {
|
|
1475
|
+
connected: Map<number, { name: string }>;
|
|
1476
|
+
buttons: Map<number, Map<number, { pressed: boolean; value: number }>>;
|
|
1477
|
+
axes: Map<number, Map<number, number>>;
|
|
1478
|
+
eventsThisFrame: GamepadEvent[];
|
|
1479
|
+
}
|
|
1480
|
+
| undefined;
|
|
1481
|
+
}
|
|
1482
|
+
|
|
1483
|
+
function getSystemEventState(worldId: number):
|
|
1484
|
+
| {
|
|
1485
|
+
eventsThisFrame: SystemEvent[];
|
|
1486
|
+
lastError?: {
|
|
1487
|
+
scope: string;
|
|
1488
|
+
message: string;
|
|
1489
|
+
commandId?: number;
|
|
1490
|
+
commandType?: string;
|
|
1491
|
+
};
|
|
1492
|
+
}
|
|
1493
|
+
| undefined {
|
|
1494
|
+
requireInitialized();
|
|
1495
|
+
const world = getWorldOrThrow(worldId);
|
|
1496
|
+
const worldStore = world.components.get(WORLD_ENTITY_ID);
|
|
1497
|
+
return worldStore?.get('SystemEventState') as
|
|
1498
|
+
| {
|
|
1499
|
+
eventsThisFrame: SystemEvent[];
|
|
1500
|
+
lastError?: {
|
|
1501
|
+
scope: string;
|
|
1502
|
+
message: string;
|
|
1503
|
+
commandId?: number;
|
|
1504
|
+
commandType?: string;
|
|
1505
|
+
};
|
|
1506
|
+
}
|
|
1507
|
+
| undefined;
|
|
1508
|
+
}
|
|
1509
|
+
|
|
1510
|
+
function getUiEventState(worldId: number):
|
|
1511
|
+
| { eventsThisFrame: UiEvent[] }
|
|
1512
|
+
| undefined {
|
|
1513
|
+
requireInitialized();
|
|
1514
|
+
const world = getWorldOrThrow(worldId);
|
|
1515
|
+
const worldStore = world.components.get(WORLD_ENTITY_ID);
|
|
1516
|
+
return worldStore?.get('UiEventState') as
|
|
1517
|
+
| { eventsThisFrame: UiEvent[] }
|
|
1518
|
+
| undefined;
|
|
1519
|
+
}
|
|
1520
|
+
|
|
1521
|
+
export function getGamepadEvents(worldId: number): GamepadEvent[] {
|
|
1522
|
+
return getGamepadState(worldId)?.eventsThisFrame ?? [];
|
|
1523
|
+
}
|
|
1524
|
+
|
|
1525
|
+
/** Lists currently connected gamepads sorted by id. */
|
|
1526
|
+
export function getConnectedGamepads(
|
|
1527
|
+
worldId: number,
|
|
1528
|
+
): Array<{ gamepadId: number; name: string }> {
|
|
1529
|
+
const connected = getGamepadState(worldId)?.connected;
|
|
1530
|
+
if (!connected) return [];
|
|
1531
|
+
const out: Array<{ gamepadId: number; name: string }> = [];
|
|
1532
|
+
for (const [gamepadId, info] of connected) {
|
|
1533
|
+
out.push({ gamepadId, name: info.name });
|
|
1534
|
+
}
|
|
1535
|
+
out.sort((a, b) => a.gamepadId - b.gamepadId);
|
|
1536
|
+
return out;
|
|
1537
|
+
}
|
|
1538
|
+
|
|
1539
|
+
/** Returns current value of a gamepad axis or 0 when unavailable. */
|
|
1540
|
+
export function getGamepadAxis(
|
|
1541
|
+
worldId: number,
|
|
1542
|
+
gamepadId: number,
|
|
1543
|
+
axis: number,
|
|
1544
|
+
): number {
|
|
1545
|
+
return getGamepadState(worldId)?.axes.get(gamepadId)?.get(axis) ?? 0;
|
|
1546
|
+
}
|
|
1547
|
+
|
|
1548
|
+
/** Returns whether a gamepad button is currently pressed. */
|
|
1549
|
+
export function isGamepadButtonPressed(
|
|
1550
|
+
worldId: number,
|
|
1551
|
+
gamepadId: number,
|
|
1552
|
+
button: number,
|
|
1553
|
+
): boolean {
|
|
1554
|
+
return (
|
|
1555
|
+
getGamepadState(worldId)?.buttons.get(gamepadId)?.get(button)?.pressed ??
|
|
1556
|
+
false
|
|
1557
|
+
);
|
|
1558
|
+
}
|
|
1559
|
+
|
|
1560
|
+
/** Returns system events mirrored in the current frame. */
|
|
1561
|
+
export function getSystemEvents(worldId: number): SystemEvent[] {
|
|
1562
|
+
return getSystemEventState(worldId)?.eventsThisFrame ?? [];
|
|
1563
|
+
}
|
|
1564
|
+
|
|
1565
|
+
/** Returns the last system error seen by the world, if any. */
|
|
1566
|
+
export function getLastSystemError(worldId: number): {
|
|
1567
|
+
scope: string;
|
|
1568
|
+
message: string;
|
|
1569
|
+
commandId?: number;
|
|
1570
|
+
commandType?: string;
|
|
1571
|
+
} | null {
|
|
1572
|
+
return getSystemEventState(worldId)?.lastError ?? null;
|
|
1573
|
+
}
|
|
1574
|
+
|
|
1575
|
+
/** Returns UI events mirrored in the current frame. */
|
|
1576
|
+
export function getUiEvents(worldId: number): UiEvent[] {
|
|
1577
|
+
return getUiEventState(worldId)?.eventsThisFrame ?? [];
|
|
1578
|
+
}
|