@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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vulfram/engine",
3
- "version": "0.19.2-alpha",
3
+ "version": "0.20.1-alpha",
4
4
  "module": "src/index.ts",
5
5
  "main": "src/index.ts",
6
6
  "types": "src/index.ts",
@@ -62,9 +62,9 @@
62
62
  "access": "public"
63
63
  },
64
64
  "dependencies": {
65
- "@vulfram/transport-types": "^0.2.6",
65
+ "@vulfram/transport-types": "^0.3.0",
66
66
  "gl-matrix": "^3.4.4",
67
- "msgpackr": "^1.11.8"
67
+ "msgpackr": "^1.11.9"
68
68
  },
69
69
  "devDependencies": {
70
70
  "@types/bun": "^1.3.10",
@@ -5,9 +5,11 @@ import type { ForwardAtlasOptions } from '../../types/cmds/texture';
5
5
  import type { GamepadEvent, SystemEvent, UiEvent } from '../../types/events';
6
6
  import type {
7
7
  CameraKind,
8
+ CursorGrabMode,
8
9
  LightKind,
9
10
  MaterialKind,
10
11
  TextureCreateMode,
12
+ WindowState,
11
13
  } from '../../types/kinds';
12
14
  import type { JsonObject, JsonValue } from '../../types/json';
13
15
 
@@ -134,6 +136,12 @@ export interface WindowStateComponent {
134
136
  size: [number, number];
135
137
  position: [number, number];
136
138
  scaleFactor: number;
139
+ lifecycleState?: WindowState;
140
+ pointerCapture?: {
141
+ mode: CursorGrabMode;
142
+ active: boolean;
143
+ reason?: string;
144
+ };
137
145
  closeRequested: boolean;
138
146
  resizedThisFrame: boolean;
139
147
  movedThisFrame: boolean;
@@ -143,6 +143,8 @@ export const InputMirrorSystem: System = (world) => {
143
143
  size: [800, 600],
144
144
  position: [0, 0],
145
145
  scaleFactor: 1.0,
146
+ lifecycleState: undefined,
147
+ pointerCapture: undefined,
146
148
  closeRequested: false,
147
149
  resizedThisFrame: false,
148
150
  movedThisFrame: false,
@@ -331,6 +333,14 @@ export const InputMirrorSystem: System = (world) => {
331
333
  windowState.movedThisFrame = true;
332
334
  } else if (winEvent.event === 'on-scale-factor-change') {
333
335
  windowState.scaleFactor = winEvent.data.scaleFactor;
336
+ } else if (winEvent.event === 'on-state-change') {
337
+ windowState.lifecycleState = winEvent.data.state;
338
+ } else if (winEvent.event === 'on-pointer-capture-change') {
339
+ windowState.pointerCapture = {
340
+ mode: winEvent.data.capture.mode,
341
+ active: winEvent.data.capture.active,
342
+ reason: winEvent.data.capture.reason,
343
+ };
334
344
  }
335
345
  }
336
346
  // Gamepad events
@@ -0,0 +1,193 @@
1
+ import type {
2
+ CmdAudioListenerCreateArgs,
3
+ CmdAudioListenerDisposeArgs,
4
+ CmdAudioListenerUpsertArgs,
5
+ CmdAudioListenerUpdateArgs,
6
+ CmdAudioResourceUpsertArgs,
7
+ CmdAudioResourceDisposeArgs,
8
+ CmdAudioSourceTransportArgs,
9
+ CmdAudioSourceCreateArgs,
10
+ CmdAudioSourceDisposeArgs,
11
+ CmdAudioSourceUpsertArgs,
12
+ CmdAudioSourceUpdateArgs,
13
+ CmdAudioStateGetArgs,
14
+ } from '../../../types/cmds/audio';
15
+ import type { CmdPoseUpdateArgs } from '../../../types/cmds/model';
16
+ import type { CmdTextureBindTargetArgs } from '../../../types/cmds/texture';
17
+ import { enqueueCommand } from '../../bridge/dispatch';
18
+
19
+ /**
20
+ * Sends an audio listener update command.
21
+ */
22
+ export function audioListenerUpdate(
23
+ worldId: number,
24
+ args: CmdAudioListenerUpdateArgs,
25
+ ): number {
26
+ return enqueueCommand(worldId, 'cmd-audio-listener-upsert', args);
27
+ }
28
+
29
+ /**
30
+ * Binds a texture id to a texture target output.
31
+ */
32
+ export function bindTextureToTarget(
33
+ worldId: number,
34
+ args: CmdTextureBindTargetArgs,
35
+ ): number {
36
+ return enqueueCommand(worldId, 'cmd-texture-bind-target', args);
37
+ }
38
+
39
+ /**
40
+ * Binds the audio listener to a model.
41
+ */
42
+ export function audioListenerCreate(
43
+ worldId: number,
44
+ args: CmdAudioListenerCreateArgs,
45
+ ): number {
46
+ return enqueueCommand(worldId, 'cmd-audio-listener-upsert', args);
47
+ }
48
+
49
+ /**
50
+ * Upserts audio listener params or binding.
51
+ */
52
+ export function audioListenerUpsert(
53
+ worldId: number,
54
+ args: CmdAudioListenerUpsertArgs,
55
+ ): number {
56
+ return enqueueCommand(worldId, 'cmd-audio-listener-upsert', args);
57
+ }
58
+
59
+ /**
60
+ * Disposes the audio listener binding.
61
+ */
62
+ export function audioListenerDispose(
63
+ worldId: number,
64
+ args: CmdAudioListenerDisposeArgs,
65
+ ): number {
66
+ return enqueueCommand(worldId, 'cmd-audio-listener-dispose', args);
67
+ }
68
+
69
+ /**
70
+ * Creates an audio resource from an uploaded buffer.
71
+ */
72
+ export function audioResourceCreate(
73
+ worldId: number,
74
+ args: CmdAudioResourceUpsertArgs,
75
+ ): number {
76
+ return enqueueCommand(worldId, 'cmd-audio-resource-upsert', args);
77
+ }
78
+
79
+ /**
80
+ * Pushes a chunk into a streaming audio resource.
81
+ */
82
+ export function audioResourcePush(
83
+ worldId: number,
84
+ args: CmdAudioResourceUpsertArgs,
85
+ ): number {
86
+ return enqueueCommand(worldId, 'cmd-audio-resource-upsert', args);
87
+ }
88
+
89
+ /**
90
+ * Disposes an audio resource.
91
+ */
92
+ export function audioResourceDispose(
93
+ worldId: number,
94
+ args: CmdAudioResourceDisposeArgs,
95
+ ): number {
96
+ return enqueueCommand(worldId, 'cmd-audio-resource-dispose', args);
97
+ }
98
+
99
+ /**
100
+ * Creates an audio source bound to a model.
101
+ */
102
+ export function audioSourceCreate(
103
+ worldId: number,
104
+ args: CmdAudioSourceCreateArgs,
105
+ ): number {
106
+ return enqueueCommand(worldId, 'cmd-audio-source-upsert', args);
107
+ }
108
+
109
+ /**
110
+ * Updates an audio source.
111
+ */
112
+ export function audioSourceUpdate(
113
+ worldId: number,
114
+ args: CmdAudioSourceUpdateArgs,
115
+ ): number {
116
+ return enqueueCommand(worldId, 'cmd-audio-source-upsert', args);
117
+ }
118
+
119
+ /**
120
+ * Upserts audio source params or binding.
121
+ */
122
+ export function audioSourceUpsert(
123
+ worldId: number,
124
+ args: CmdAudioSourceUpsertArgs,
125
+ ): number {
126
+ return enqueueCommand(worldId, 'cmd-audio-source-upsert', args);
127
+ }
128
+
129
+ /**
130
+ * Starts playback for an audio source.
131
+ */
132
+ export function audioSourcePlay(
133
+ worldId: number,
134
+ args: Omit<CmdAudioSourceTransportArgs, 'action'>,
135
+ ): number {
136
+ return enqueueCommand(worldId, 'cmd-audio-source-transport', {
137
+ ...args,
138
+ action: 'play',
139
+ });
140
+ }
141
+
142
+ /**
143
+ * Pauses playback for an audio source.
144
+ */
145
+ export function audioSourcePause(
146
+ worldId: number,
147
+ args: Omit<CmdAudioSourceTransportArgs, 'action'>,
148
+ ): number {
149
+ return enqueueCommand(worldId, 'cmd-audio-source-transport', {
150
+ ...args,
151
+ action: 'pause',
152
+ });
153
+ }
154
+
155
+ /**
156
+ * Stops playback for an audio source.
157
+ */
158
+ export function audioSourceStop(
159
+ worldId: number,
160
+ args: Omit<CmdAudioSourceTransportArgs, 'action'>,
161
+ ): number {
162
+ return enqueueCommand(worldId, 'cmd-audio-source-transport', {
163
+ ...args,
164
+ action: 'stop',
165
+ });
166
+ }
167
+
168
+ /**
169
+ * Requests a snapshot of audio runtime state.
170
+ */
171
+ export function audioStateGet(
172
+ worldId: number,
173
+ args: CmdAudioStateGetArgs = {},
174
+ ): number {
175
+ return enqueueCommand(worldId, 'cmd-audio-state-get', args);
176
+ }
177
+
178
+ /**
179
+ * Disposes an audio source.
180
+ */
181
+ export function audioSourceDispose(
182
+ worldId: number,
183
+ args: CmdAudioSourceDisposeArgs,
184
+ ): number {
185
+ return enqueueCommand(worldId, 'cmd-audio-source-dispose', args);
186
+ }
187
+
188
+ /**
189
+ * Updates a model pose (skinning) using an uploaded matrices buffer.
190
+ */
191
+ export function poseUpdate(worldId: number, args: CmdPoseUpdateArgs): number {
192
+ return enqueueCommand(worldId, 'cmd-pose-update', args);
193
+ }
@@ -0,0 +1,30 @@
1
+ import { getWorldOrThrow } from '../../bridge/guards';
2
+ import { engineState } from '../../state';
3
+
4
+ export const WORLD_ENTITY_ID = 0;
5
+
6
+ export function allocateGlobalId(): number {
7
+ return engineState.nextGlobalId++;
8
+ }
9
+
10
+ export function recalculateWorldWindowBindings(
11
+ world: ReturnType<typeof getWorldOrThrow>,
12
+ ): void {
13
+ world.boundWindowIds.clear();
14
+ for (const windowId of world.targetWindowBindings.values()) {
15
+ world.boundWindowIds.add(windowId);
16
+ }
17
+
18
+ if (world.boundWindowIds.size === 0) {
19
+ world.primaryWindowId = undefined;
20
+ return;
21
+ }
22
+
23
+ let primary = Number.POSITIVE_INFINITY;
24
+ for (const windowId of world.boundWindowIds) {
25
+ if (windowId < primary) {
26
+ primary = windowId;
27
+ }
28
+ }
29
+ world.primaryWindowId = primary;
30
+ }
@@ -0,0 +1,12 @@
1
+ import { getWorldOrThrow, requireInitialized } from '../../bridge/guards';
2
+ import type { Intent } from '../../ecs';
3
+
4
+ /**
5
+ * Emits an intent to the specified world.
6
+ * Intents are processed by systems at the beginning of each tick.
7
+ */
8
+ export function emitIntent(worldId: number, intent: Intent): void {
9
+ requireInitialized();
10
+ const world = getWorldOrThrow(worldId);
11
+ world.intentStore.enqueue(intent);
12
+ }
@@ -0,0 +1,185 @@
1
+ import type { ShadowConfig } from '../../../types/cmds/shadow';
2
+ import type {
3
+ EnvironmentConfig,
4
+ CmdEnvironmentDisposeArgs,
5
+ } from '../../../types/cmds/environment';
6
+ import type {
7
+ CmdRealmRenderGraphBindArgs,
8
+ CmdRenderGraphDisposeArgs,
9
+ CmdRenderGraphListArgs,
10
+ CmdRenderGraphUpsertArgs,
11
+ } from '../../../types/cmds/render-graph';
12
+ import type { NotificationLevel } from '../../../types/kinds';
13
+ import { EngineError } from '../../errors';
14
+ import { enqueueCommand } from '../../bridge/dispatch';
15
+ import { getWorldOrThrow } from '../../bridge/guards';
16
+ import { emitIntent } from './intents';
17
+
18
+ /**
19
+ * Requests a list of resources from the engine for debugging.
20
+ */
21
+ export function requestResourceList(
22
+ worldId: number,
23
+ resourceType:
24
+ | 'model'
25
+ | 'material'
26
+ | 'texture'
27
+ | 'geometry'
28
+ | 'light'
29
+ | 'camera',
30
+ ): void {
31
+ emitIntent(worldId, {
32
+ type: 'request-resource-list',
33
+ resourceType,
34
+ });
35
+ }
36
+
37
+ function resolveWorldWindowId(worldId: number): number {
38
+ const world = getWorldOrThrow(worldId);
39
+ if (world.primaryWindowId === undefined) {
40
+ for (const windowId of world.targetWindowBindings.values()) {
41
+ return windowId;
42
+ }
43
+ throw new EngineError(
44
+ 'WindowNotFound',
45
+ `World ${worldId} has no window binding available for this command.`,
46
+ );
47
+ }
48
+ return world.primaryWindowId;
49
+ }
50
+
51
+ /** Requests model list for a world window context. */
52
+ export function listModels(worldId: number): number {
53
+ return enqueueCommand(worldId, 'cmd-model-list', {
54
+ windowId: resolveWorldWindowId(worldId),
55
+ });
56
+ }
57
+
58
+ /** Requests material list for a world window context. */
59
+ export function listMaterials(worldId: number): number {
60
+ return enqueueCommand(worldId, 'cmd-material-list', {
61
+ windowId: resolveWorldWindowId(worldId),
62
+ });
63
+ }
64
+
65
+ /** Requests texture list for a world window context. */
66
+ export function listTextures(worldId: number): number {
67
+ return enqueueCommand(worldId, 'cmd-texture-list', {
68
+ windowId: resolveWorldWindowId(worldId),
69
+ });
70
+ }
71
+
72
+ /** Requests geometry list for a world window context. */
73
+ export function listGeometries(worldId: number): number {
74
+ return enqueueCommand(worldId, 'cmd-geometry-list', {
75
+ windowId: resolveWorldWindowId(worldId),
76
+ });
77
+ }
78
+
79
+ /** Requests light list for a world window context. */
80
+ export function listLights(worldId: number): number {
81
+ return enqueueCommand(worldId, 'cmd-light-list', {
82
+ windowId: resolveWorldWindowId(worldId),
83
+ });
84
+ }
85
+
86
+ /** Requests camera list for a world window context. */
87
+ export function listCameras(worldId: number): number {
88
+ return enqueueCommand(worldId, 'cmd-camera-list', {
89
+ windowId: resolveWorldWindowId(worldId),
90
+ });
91
+ }
92
+
93
+ /** Creates or updates a render graph in the core catalog. */
94
+ export function upsertRenderGraph(
95
+ worldId: number,
96
+ args: CmdRenderGraphUpsertArgs,
97
+ ): number {
98
+ return enqueueCommand(worldId, 'cmd-render-graph-upsert', args);
99
+ }
100
+
101
+ /** Disposes a render graph from the core catalog. */
102
+ export function disposeRenderGraph(
103
+ worldId: number,
104
+ args: CmdRenderGraphDisposeArgs,
105
+ ): number {
106
+ return enqueueCommand(worldId, 'cmd-render-graph-dispose', args);
107
+ }
108
+
109
+ /** Requests the current render graph catalog. */
110
+ export function listRenderGraphs(
111
+ worldId: number,
112
+ args: CmdRenderGraphListArgs = {},
113
+ ): number {
114
+ return enqueueCommand(worldId, 'cmd-render-graph-list', args);
115
+ }
116
+
117
+ /** Binds a realm to a render graph id. */
118
+ export function bindRealmRenderGraph(
119
+ worldId: number,
120
+ args: CmdRealmRenderGraphBindArgs,
121
+ ): number {
122
+ return enqueueCommand(worldId, 'cmd-realm-render-graph-bind', args);
123
+ }
124
+
125
+ /** Disposes a material. */
126
+ export function disposeMaterial(worldId: number, resourceId: number): void {
127
+ emitIntent(worldId, { type: 'dispose-material', resourceId });
128
+ }
129
+
130
+ /** Disposes a texture. */
131
+ export function disposeTexture(worldId: number, resourceId: number): void {
132
+ emitIntent(worldId, { type: 'dispose-texture', resourceId });
133
+ }
134
+
135
+ /** Disposes a geometry. */
136
+ export function disposeGeometry(worldId: number, resourceId: number): void {
137
+ emitIntent(worldId, { type: 'dispose-geometry', resourceId });
138
+ }
139
+
140
+ /** Sends a system notification. */
141
+ export function sendNotification(
142
+ worldId: number,
143
+ props: {
144
+ level: NotificationLevel;
145
+ title: string;
146
+ message: string;
147
+ },
148
+ ): void {
149
+ emitIntent(worldId, {
150
+ type: 'send-notification',
151
+ level: props.level,
152
+ title: props.title,
153
+ message: props.message,
154
+ });
155
+ }
156
+
157
+ /** Configures shadow settings for the world. */
158
+ export function configureShadows(worldId: number, config: ShadowConfig): void {
159
+ emitIntent(worldId, {
160
+ type: 'configure-shadows',
161
+ config,
162
+ });
163
+ }
164
+
165
+ /** Configures environment settings for the world. */
166
+ export function configureEnvironment(
167
+ worldId: number,
168
+ config: EnvironmentConfig,
169
+ ): void {
170
+ emitIntent(worldId, {
171
+ type: 'configure-environment',
172
+ config,
173
+ });
174
+ }
175
+
176
+ /**
177
+ * Disposes an environment profile.
178
+ * If omitted, defaults to this world id (engine convention).
179
+ */
180
+ export function disposeEnvironment(
181
+ worldId: number,
182
+ args: CmdEnvironmentDisposeArgs = { environmentId: worldId },
183
+ ): number {
184
+ return enqueueCommand(worldId, 'cmd-environment-dispose', args);
185
+ }