@vulfram/engine 0.19.2-alpha → 0.20.1-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.
@@ -1,1578 +1,8 @@
1
- import type { ShadowConfig } from '../../types/cmds/shadow';
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';
14
- import type {
15
- CmdAudioListenerCreateArgs,
16
- CmdAudioListenerDisposeArgs,
17
- CmdAudioListenerUpsertArgs,
18
- CmdAudioListenerUpdateArgs,
19
- CmdAudioResourceUpsertArgs,
20
- CmdAudioResourceDisposeArgs,
21
- CmdAudioSourceTransportArgs,
22
- CmdAudioSourceCreateArgs,
23
- CmdAudioSourceDisposeArgs,
24
- CmdAudioSourceUpsertArgs,
25
- CmdAudioSourceUpdateArgs,
26
- CmdAudioStateGetArgs,
27
- } from '../../types/cmds/audio';
28
- import type { CmdPoseUpdateArgs } from '../../types/cmds/model';
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';
61
- import { getWorldOrThrow, requireInitialized } from '../bridge/guards';
62
- import {
63
- enqueueCommand,
64
- enqueueGlobalCommand,
65
- markRoutingIndexDirty,
66
- } from '../bridge/dispatch';
67
- import type {
68
- CameraComponent,
69
- CameraProps,
70
- GeometryProps,
71
- InputStateComponent,
72
- Intent,
73
- LightProps,
74
- MaterialProps,
75
- ModelProps,
76
- TagProps,
77
- TextureProps,
78
- TransformComponent,
79
- TransformProps,
80
- UiFocusCycleMode,
81
- WindowStateComponent,
82
- } from '../ecs';
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
- }
109
-
110
- /**
111
- * Emits an intent to the specified world.
112
- * Intents are processed by systems at the beginning of each tick.
113
- */
114
- export function emitIntent(worldId: number, intent: Intent): void {
115
- requireInitialized();
116
- const world = getWorldOrThrow(worldId);
117
- world.intentStore.enqueue(intent);
118
- }
119
-
120
- /**
121
- * Returns the core model ID for an entity, if available.
122
- */
123
- export function getModelId(worldId: number, entityId: number): number | null {
124
- requireInitialized();
125
- const world = getWorldOrThrow(worldId);
126
- const store = world.components.get(entityId);
127
- if (!store) return null;
128
- const model = store.get('Model') as { id: number } | undefined;
129
- return model?.id ?? null;
130
- }
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
-
434
- /**
435
- * Sends an audio listener update command.
436
- */
437
- export function audioListenerUpdate(
438
- worldId: number,
439
- args: CmdAudioListenerUpdateArgs,
440
- ): number {
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);
452
- }
453
-
454
- /**
455
- * Binds the audio listener to a model.
456
- */
457
- export function audioListenerCreate(
458
- worldId: number,
459
- args: CmdAudioListenerCreateArgs,
460
- ): number {
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);
472
- }
473
-
474
- /**
475
- * Disposes the audio listener binding.
476
- */
477
- export function audioListenerDispose(
478
- worldId: number,
479
- args: CmdAudioListenerDisposeArgs,
480
- ): number {
481
- return enqueueCommand(worldId, 'cmd-audio-listener-dispose', args);
482
- }
483
-
484
- /**
485
- * Creates an audio resource from an uploaded buffer.
486
- */
487
- export function audioResourceCreate(
488
- worldId: number,
489
- args: CmdAudioResourceUpsertArgs,
490
- ): number {
491
- return enqueueCommand(worldId, 'cmd-audio-resource-upsert', args);
492
- }
493
-
494
- /**
495
- * Pushes a chunk into a streaming audio resource.
496
- */
497
- export function audioResourcePush(
498
- worldId: number,
499
- args: CmdAudioResourceUpsertArgs,
500
- ): number {
501
- return enqueueCommand(worldId, 'cmd-audio-resource-upsert', args);
502
- }
503
-
504
- /**
505
- * Disposes an audio resource.
506
- */
507
- export function audioResourceDispose(
508
- worldId: number,
509
- args: CmdAudioResourceDisposeArgs,
510
- ): number {
511
- return enqueueCommand(worldId, 'cmd-audio-resource-dispose', args);
512
- }
513
-
514
- /**
515
- * Creates an audio source bound to a model.
516
- */
517
- export function audioSourceCreate(
518
- worldId: number,
519
- args: CmdAudioSourceCreateArgs,
520
- ): number {
521
- return enqueueCommand(worldId, 'cmd-audio-source-upsert', args);
522
- }
523
-
524
- /**
525
- * Updates an audio source.
526
- */
527
- export function audioSourceUpdate(
528
- worldId: number,
529
- args: CmdAudioSourceUpdateArgs,
530
- ): number {
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);
542
- }
543
-
544
- /**
545
- * Starts playback for an audio source.
546
- */
547
- export function audioSourcePlay(
548
- worldId: number,
549
- args: Omit<CmdAudioSourceTransportArgs, 'action'>,
550
- ): number {
551
- return enqueueCommand(worldId, 'cmd-audio-source-transport', {
552
- ...args,
553
- action: 'play',
554
- });
555
- }
556
-
557
- /**
558
- * Pauses playback for an audio source.
559
- */
560
- export function audioSourcePause(
561
- worldId: number,
562
- args: Omit<CmdAudioSourceTransportArgs, 'action'>,
563
- ): number {
564
- return enqueueCommand(worldId, 'cmd-audio-source-transport', {
565
- ...args,
566
- action: 'pause',
567
- });
568
- }
569
-
570
- /**
571
- * Stops playback for an audio source.
572
- */
573
- export function audioSourceStop(
574
- worldId: number,
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 = {},
589
- ): number {
590
- return enqueueCommand(worldId, 'cmd-audio-state-get', args);
591
- }
592
-
593
- /**
594
- * Disposes an audio source.
595
- */
596
- export function audioSourceDispose(
597
- worldId: number,
598
- args: CmdAudioSourceDisposeArgs,
599
- ): number {
600
- return enqueueCommand(worldId, 'cmd-audio-source-dispose', args);
601
- }
602
-
603
- /**
604
- * Updates a model pose (skinning) using an uploaded matrices buffer.
605
- */
606
- export function poseUpdate(worldId: number, args: CmdPoseUpdateArgs): number {
607
- return enqueueCommand(worldId, 'cmd-pose-update', args);
608
- }
609
-
610
- /**
611
- * Requests a list of resources from the engine for debugging.
612
- */
613
- export function requestResourceList(
614
- worldId: number,
615
- resourceType:
616
- | 'model'
617
- | 'material'
618
- | 'texture'
619
- | 'geometry'
620
- | 'light'
621
- | 'camera',
622
- ): void {
623
- emitIntent(worldId, {
624
- type: 'request-resource-list',
625
- resourceType,
626
- });
627
- }
628
-
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),
647
- });
648
- }
649
-
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),
654
- });
655
- }
656
-
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),
661
- });
662
- }
663
-
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),
668
- });
669
- }
670
-
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),
682
- });
683
- }
684
-
685
- /**
686
- * Disposes a material.
687
- */
688
- export function disposeMaterial(worldId: number, resourceId: number): void {
689
- emitIntent(worldId, { type: 'dispose-material', resourceId });
690
- }
691
-
692
- /**
693
- * Disposes a texture.
694
- */
695
- export function disposeTexture(worldId: number, resourceId: number): void {
696
- emitIntent(worldId, { type: 'dispose-texture', resourceId });
697
- }
698
-
699
- /**
700
- * Disposes a geometry.
701
- */
702
- export function disposeGeometry(worldId: number, resourceId: number): void {
703
- emitIntent(worldId, { type: 'dispose-geometry', resourceId });
704
- }
705
-
706
- /**
707
- * Sends a system notification.
708
- */
709
- export function sendNotification(
710
- worldId: number,
711
- props: {
712
- level: NotificationLevel;
713
- title: string;
714
- message: string;
715
- },
716
- ): void {
717
- emitIntent(worldId, {
718
- type: 'send-notification',
719
- level: props.level,
720
- title: props.title,
721
- message: props.message,
722
- });
723
- }
724
-
725
- /**
726
- * Configures shadow settings for the world.
727
- */
728
- export function configureShadows(worldId: number, config: ShadowConfig): void {
729
- emitIntent(worldId, {
730
- type: 'configure-shadows',
731
- config,
732
- });
733
- }
734
-
735
- /**
736
- * Configures environment settings for the world.
737
- */
738
- export function configureEnvironment(
739
- worldId: number,
740
- config: EnvironmentConfig,
741
- ): void {
742
- emitIntent(worldId, {
743
- type: 'configure-environment',
744
- config,
745
- });
746
- }
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
-
979
- /**
980
- * Draws a debug gizmo line for one frame.
981
- */
982
- export function drawGizmoLine(
983
- worldId: number,
984
- props: {
985
- start: [number, number, number];
986
- end: [number, number, number];
987
- color?: [number, number, number, number];
988
- },
989
- ): void {
990
- emitIntent(worldId, {
991
- type: 'gizmo-draw-line',
992
- start: props.start,
993
- end: props.end,
994
- color: props.color || [1, 1, 1, 1],
995
- });
996
- }
997
-
998
- /**
999
- * Draws a debug gizmo AABB for one frame.
1000
- */
1001
- export function drawGizmoAabb(
1002
- worldId: number,
1003
- props: {
1004
- min: [number, number, number];
1005
- max: [number, number, number];
1006
- color?: [number, number, number, number];
1007
- },
1008
- ): void {
1009
- emitIntent(worldId, {
1010
- type: 'gizmo-draw-aabb',
1011
- min: props.min,
1012
- max: props.max,
1013
- color: props.color || [1, 1, 1, 1],
1014
- });
1015
- }
1016
-
1017
- /**
1018
- * Creates a new entity in the specified world.
1019
- * Returns the entity ID immediately, but the entity is actually created in the next tick.
1020
- */
1021
- export function createEntity(worldId: number): number {
1022
- requireInitialized();
1023
- const entityId = engineState.nextEntityId++;
1024
-
1025
- emitIntent(worldId, {
1026
- type: 'create-entity',
1027
- worldId,
1028
- entityId,
1029
- });
1030
-
1031
- return entityId;
1032
- }
1033
-
1034
- /**
1035
- * Removes an entity and all its components in the next tick.
1036
- */
1037
- export function removeEntity(worldId: number, entityId: number): void {
1038
- emitIntent(worldId, {
1039
- type: 'remove-entity',
1040
- entityId,
1041
- });
1042
- }
1043
-
1044
- /**
1045
- * Attaches a camera component to an entity via Intent.
1046
- */
1047
- export function createCamera(
1048
- worldId: number,
1049
- entityId: number,
1050
- props: CameraProps = {},
1051
- ): void {
1052
- emitIntent(worldId, {
1053
- type: 'attach-camera',
1054
- entityId,
1055
- props,
1056
- });
1057
- }
1058
-
1059
- /**
1060
- * Attaches a light component to an entity via Intent.
1061
- */
1062
- export function createLight(
1063
- worldId: number,
1064
- entityId: number,
1065
- props: LightProps = {},
1066
- ): void {
1067
- emitIntent(worldId, {
1068
- type: 'attach-light',
1069
- entityId,
1070
- props,
1071
- });
1072
- }
1073
-
1074
- /**
1075
- * Attaches a model component to an entity via Intent.
1076
- */
1077
- export function createModel(
1078
- worldId: number,
1079
- entityId: number,
1080
- props: ModelProps,
1081
- ): void {
1082
- emitIntent(worldId, {
1083
- type: 'attach-model',
1084
- entityId,
1085
- props,
1086
- });
1087
- }
1088
-
1089
- /**
1090
- * Updates an entity's transform via Intent.
1091
- */
1092
- export function updateTransform(
1093
- worldId: number,
1094
- entityId: number,
1095
- props: TransformProps,
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
-
1135
- emitIntent(worldId, {
1136
- type: 'update-transform',
1137
- entityId,
1138
- props,
1139
- });
1140
- }
1141
-
1142
- /**
1143
- * Attaches a tag component to an entity via Intent.
1144
- */
1145
- export function createTag(
1146
- worldId: number,
1147
- entityId: number,
1148
- props: TagProps,
1149
- ): void {
1150
- emitIntent(worldId, {
1151
- type: 'attach-tag',
1152
- entityId,
1153
- props,
1154
- });
1155
- }
1156
-
1157
- /**
1158
- * Sets the parent of an entity via Intent.
1159
- */
1160
- export function setParent(
1161
- worldId: number,
1162
- entityId: number,
1163
- parentId: number | null,
1164
- ): void {
1165
- if (parentId !== null && parentId === entityId) {
1166
- throw new EngineError(
1167
- 'InvalidParent',
1168
- `Entity ${entityId} cannot be parent of itself.`,
1169
- );
1170
- }
1171
- emitIntent(worldId, {
1172
- type: 'set-parent',
1173
- entityId,
1174
- parentId,
1175
- });
1176
- }
1177
-
1178
- /**
1179
- * Resources (Material, Geometry, Texture)
1180
- */
1181
-
1182
- /**
1183
- * Creates a material resource and returns its ID.
1184
- */
1185
- export function createMaterial(worldId: number, props: MaterialProps): number {
1186
- requireInitialized();
1187
- getWorldOrThrow(worldId);
1188
- const resourceId = allocateGlobalId();
1189
-
1190
- emitIntent(worldId, {
1191
- type: 'create-material',
1192
- resourceId,
1193
- props,
1194
- });
1195
-
1196
- return resourceId;
1197
- }
1198
-
1199
- /**
1200
- * Creates a geometry resource and returns its ID.
1201
- */
1202
- export function createGeometry(worldId: number, props: GeometryProps): number {
1203
- requireInitialized();
1204
- getWorldOrThrow(worldId);
1205
- const resourceId = allocateGlobalId();
1206
-
1207
- emitIntent(worldId, {
1208
- type: 'create-geometry',
1209
- resourceId,
1210
- props,
1211
- });
1212
-
1213
- return resourceId;
1214
- }
1215
-
1216
- /**
1217
- * Creates a texture resource and returns its ID.
1218
- */
1219
- export function createTexture(worldId: number, props: TextureProps): number {
1220
- requireInitialized();
1221
- getWorldOrThrow(worldId);
1222
- const resourceId = allocateGlobalId();
1223
-
1224
- emitIntent(worldId, {
1225
- type: 'create-texture',
1226
- resourceId,
1227
- props,
1228
- });
1229
-
1230
- return resourceId;
1231
- }
1232
-
1233
- /**
1234
- * Input & Window State Queries
1235
- */
1236
-
1237
- const WORLD_ENTITY_ID = 0;
1238
-
1239
- /**
1240
- * Gets the InputState component for a world.
1241
- * Returns undefined if the system hasn't run yet.
1242
- */
1243
- function getInputState(worldId: number): InputStateComponent | undefined {
1244
- requireInitialized();
1245
- const world = getWorldOrThrow(worldId);
1246
- const worldStore = world.components.get(WORLD_ENTITY_ID);
1247
- return worldStore?.get('InputState') as InputStateComponent | undefined;
1248
- }
1249
-
1250
- /**
1251
- * Gets the WindowState component for a world.
1252
- * Returns undefined if the system hasn't run yet.
1253
- */
1254
- function getWindowState(worldId: number): WindowStateComponent | undefined {
1255
- requireInitialized();
1256
- const world = getWorldOrThrow(worldId);
1257
- const worldStore = world.components.get(WORLD_ENTITY_ID);
1258
- return worldStore?.get('WindowState') as WindowStateComponent | undefined;
1259
- }
1260
-
1261
- /**
1262
- * Checks if a key is currently pressed.
1263
- */
1264
- export function isKeyPressed(worldId: number, keyCode: number): boolean {
1265
- const state = getInputState(worldId);
1266
- return state?.keysPressed.has(keyCode) ?? false;
1267
- }
1268
-
1269
- /**
1270
- * Checks if a key was just pressed this frame.
1271
- */
1272
- export function isKeyJustPressed(worldId: number, keyCode: number): boolean {
1273
- const state = getInputState(worldId);
1274
- return state?.keysJustPressed.has(keyCode) ?? false;
1275
- }
1276
-
1277
- /**
1278
- * Checks if a key was just released this frame.
1279
- */
1280
- export function isKeyJustReleased(worldId: number, keyCode: number): boolean {
1281
- const state = getInputState(worldId);
1282
- return state?.keysJustReleased.has(keyCode) ?? false;
1283
- }
1284
-
1285
- /**
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.
1313
- */
1314
- export function getPointerTargetPosition(
1315
- worldId: number,
1316
- ): [number, number] | null {
1317
- const state = getInputState(worldId);
1318
- return state?.pointerPositionTarget ?? null;
1319
- }
1320
-
1321
- /**
1322
- * Gets the real drawn target area associated with the latest pointer event.
1323
- */
1324
- export function getPointerTargetSize(
1325
- worldId: number,
1326
- ): [number, number] | null {
1327
- const state = getInputState(worldId);
1328
- return state?.pointerTargetSize ?? null;
1329
- }
1330
-
1331
- /**
1332
- * Gets the pointer movement delta relative to the current routed target.
1333
- */
1334
- export function getPointerTargetDelta(worldId: number): [number, number] | null {
1335
- const state = getInputState(worldId);
1336
- return state?.pointerTargetDelta ?? null;
1337
- }
1338
-
1339
- /**
1340
- * Gets the routed target under pointer, when available.
1341
- */
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(
1367
- worldId: number,
1368
- button: number,
1369
- ): boolean {
1370
- const state = getInputState(worldId);
1371
- return state?.pointerJustPressed.has(button) ?? false;
1372
- }
1373
-
1374
- /**
1375
- * Gets the scroll delta for this frame.
1376
- */
1377
- export function getScrollDelta(worldId: number): [number, number] {
1378
- const state = getInputState(worldId);
1379
- return state?.scrollDelta ?? [0, 0];
1380
- }
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
-
1414
- /**
1415
- * Gets the current window size.
1416
- */
1417
- export function getWindowSize(worldId: number): [number, number] {
1418
- const state = getWindowState(worldId);
1419
- return state?.size ?? [800, 600];
1420
- }
1421
-
1422
- /**
1423
- * Gets the current window position.
1424
- */
1425
- export function getWindowPosition(worldId: number): [number, number] {
1426
- const state = getWindowState(worldId);
1427
- return state?.position ?? [0, 0];
1428
- }
1429
-
1430
- /**
1431
- * Checks if the window is focused.
1432
- */
1433
- export function isWindowFocused(worldId: number): boolean {
1434
- const state = getWindowState(worldId);
1435
- return state?.focused ?? false;
1436
- }
1437
-
1438
- /**
1439
- * Checks if a close was requested this frame.
1440
- */
1441
- export function isWindowCloseRequested(worldId: number): boolean {
1442
- const state = getWindowState(worldId);
1443
- return state?.closeRequested ?? false;
1444
- }
1445
-
1446
- /**
1447
- * Checks if the window was resized this frame.
1448
- */
1449
- export function wasWindowResized(worldId: number): boolean {
1450
- const state = getWindowState(worldId);
1451
- return state?.resizedThisFrame ?? false;
1452
- }
1453
-
1454
- /**
1455
- * Gets the window scale factor (DPI scaling).
1456
- */
1457
- export function getWindowScaleFactor(worldId: number): number {
1458
- const state = getWindowState(worldId);
1459
- return state?.scaleFactor ?? 1.0;
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
- }
1
+ export * from './entities/intents';
2
+ export * from './entities/world-state';
3
+ export * from './entities/targets';
4
+ export * from './entities/audio';
5
+ export * from './entities/resources';
6
+ export * from './entities/ui';
7
+ export * from './entities/scene';
8
+ export * from './entities/state-queries';