@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
package/README.md
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# @vulfram/engine
|
|
2
|
+
|
|
3
|
+
Functional engine for the Vulfram core, focused on a simple API for creating worlds, entities, and mounting to targets.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
bun add @vulfram/engine @vulfram/transport-browser
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Use the transport that fits your environment (`transport-browser`, `transport-bun`, `transport-napi`).
|
|
12
|
+
|
|
13
|
+
## Simple example
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { initEngine, tick } from '@vulfram/engine/core';
|
|
17
|
+
import { createWindow } from '@vulfram/engine/window';
|
|
18
|
+
import { mountWorldToWindow } from '@vulfram/engine/mount';
|
|
19
|
+
import * as World3D from '@vulfram/engine/world3d';
|
|
20
|
+
import { transportWasm } from '@vulfram/transport-browser';
|
|
21
|
+
|
|
22
|
+
initEngine({ transport: transportWasm });
|
|
23
|
+
|
|
24
|
+
const world = World3D.create3DWorld();
|
|
25
|
+
const { windowId } = createWindow({
|
|
26
|
+
title: 'Vulfram Engine - Simple Demo',
|
|
27
|
+
size: [1280, 720],
|
|
28
|
+
position: [100, 100],
|
|
29
|
+
initialState: 'windowed',
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
mountWorldToWindow(world, windowId);
|
|
33
|
+
|
|
34
|
+
const camera = World3D.create3DEntity(world);
|
|
35
|
+
World3D.create3DCamera(world, camera, {
|
|
36
|
+
kind: 'perspective',
|
|
37
|
+
near: 0.1,
|
|
38
|
+
far: 100,
|
|
39
|
+
order: 0,
|
|
40
|
+
});
|
|
41
|
+
World3D.update3DTransform(world, camera, {
|
|
42
|
+
position: [0, 1.2, 4],
|
|
43
|
+
rotation: [0, 0, 0, 1],
|
|
44
|
+
scale: [1, 1, 1],
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const light = World3D.create3DEntity(world);
|
|
48
|
+
World3D.create3DLight(world, light, {
|
|
49
|
+
kind: 'directional',
|
|
50
|
+
color: [1, 1, 1],
|
|
51
|
+
intensity: 2,
|
|
52
|
+
});
|
|
53
|
+
World3D.update3DTransform(world, light, {
|
|
54
|
+
position: [3, 6, 2],
|
|
55
|
+
rotation: [0, 0, 0, 1],
|
|
56
|
+
scale: [1, 1, 1],
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const geom = World3D.create3DGeometry(world, { type: 'primitive', shape: 'cube' });
|
|
60
|
+
const mat = World3D.create3DMaterial(world, {
|
|
61
|
+
kind: 'standard',
|
|
62
|
+
options: {
|
|
63
|
+
type: 'standard',
|
|
64
|
+
content: {
|
|
65
|
+
baseColor: [0.9, 0.2, 0.2, 1],
|
|
66
|
+
surfaceType: 'opaque',
|
|
67
|
+
flags: 0,
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
const cube = World3D.create3DEntity(world);
|
|
73
|
+
World3D.create3DModel(world, cube, { geometryId: geom, materialId: mat });
|
|
74
|
+
World3D.update3DTransform(world, cube, {
|
|
75
|
+
position: [0, 0, 0],
|
|
76
|
+
rotation: [0, 0, 0, 1],
|
|
77
|
+
scale: [1, 1, 1],
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
let last = performance.now();
|
|
81
|
+
function frame(now: number) {
|
|
82
|
+
const dt = now - last;
|
|
83
|
+
last = now;
|
|
84
|
+
tick(now, dt);
|
|
85
|
+
requestAnimationFrame(frame);
|
|
86
|
+
}
|
|
87
|
+
requestAnimationFrame(frame);
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Public module structure
|
|
91
|
+
|
|
92
|
+
- `@vulfram/engine/core`: init, tick, dispose, system/component registration
|
|
93
|
+
- `@vulfram/engine/window`: window APIs
|
|
94
|
+
- `@vulfram/engine/world3d`: 3D world APIs
|
|
95
|
+
- `@vulfram/engine/world-ui`: UI world APIs
|
|
96
|
+
- `@vulfram/engine/mount`: world binding to targets/windows
|
|
97
|
+
- `@vulfram/engine/ecs`: ECS types
|
|
98
|
+
- `@vulfram/engine/types`: command/event types
|
|
99
|
+
|
|
100
|
+
## Documentation
|
|
101
|
+
|
|
102
|
+
Temporary documentation URL:
|
|
103
|
+
|
|
104
|
+
- https://vulppi.dev/vulfram/docs
|
|
105
|
+
|
|
106
|
+
When the final engine documentation URL is available, this README will be updated.
|
package/package.json
CHANGED
|
@@ -1,17 +1,73 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vulfram/engine",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.19.2-alpha",
|
|
4
4
|
"module": "src/index.ts",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"types": "src/index.ts",
|
|
7
7
|
"type": "module",
|
|
8
|
+
"sideEffects": false,
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"default": "./src/index.ts",
|
|
12
|
+
"import": "./src/index.ts",
|
|
13
|
+
"types": "./src/index.ts"
|
|
14
|
+
},
|
|
15
|
+
"./core": {
|
|
16
|
+
"default": "./src/core.ts",
|
|
17
|
+
"import": "./src/core.ts",
|
|
18
|
+
"types": "./src/core.ts"
|
|
19
|
+
},
|
|
20
|
+
"./window": {
|
|
21
|
+
"default": "./src/window.ts",
|
|
22
|
+
"import": "./src/window.ts",
|
|
23
|
+
"types": "./src/window.ts"
|
|
24
|
+
},
|
|
25
|
+
"./mount": {
|
|
26
|
+
"default": "./src/mount.ts",
|
|
27
|
+
"import": "./src/mount.ts",
|
|
28
|
+
"types": "./src/mount.ts"
|
|
29
|
+
},
|
|
30
|
+
"./world3d": {
|
|
31
|
+
"default": "./src/world3d.ts",
|
|
32
|
+
"import": "./src/world3d.ts",
|
|
33
|
+
"types": "./src/world3d.ts"
|
|
34
|
+
},
|
|
35
|
+
"./world-ui": {
|
|
36
|
+
"default": "./src/world-ui.ts",
|
|
37
|
+
"import": "./src/world-ui.ts",
|
|
38
|
+
"types": "./src/world-ui.ts"
|
|
39
|
+
},
|
|
40
|
+
"./ecs": {
|
|
41
|
+
"default": "./src/ecs.ts",
|
|
42
|
+
"import": "./src/ecs.ts",
|
|
43
|
+
"types": "./src/ecs.ts"
|
|
44
|
+
},
|
|
45
|
+
"./types": {
|
|
46
|
+
"default": "./src/types/index.ts",
|
|
47
|
+
"import": "./src/types/index.ts",
|
|
48
|
+
"types": "./src/types/index.ts"
|
|
49
|
+
},
|
|
50
|
+
"./helpers": {
|
|
51
|
+
"default": "./src/helpers/index.ts",
|
|
52
|
+
"import": "./src/helpers/index.ts",
|
|
53
|
+
"types": "./src/helpers/index.ts"
|
|
54
|
+
},
|
|
55
|
+
"./package.json": "./package.json"
|
|
56
|
+
},
|
|
57
|
+
"files": [
|
|
58
|
+
"src",
|
|
59
|
+
"package.json"
|
|
60
|
+
],
|
|
61
|
+
"publishConfig": {
|
|
62
|
+
"access": "public"
|
|
63
|
+
},
|
|
8
64
|
"dependencies": {
|
|
9
|
-
"@vulfram/transport-types": "^0.2.
|
|
65
|
+
"@vulfram/transport-types": "^0.2.6",
|
|
10
66
|
"gl-matrix": "^3.4.4",
|
|
11
|
-
"glob": "^13.0.0",
|
|
12
67
|
"msgpackr": "^1.11.8"
|
|
13
68
|
},
|
|
14
69
|
"devDependencies": {
|
|
15
|
-
"@types/bun": "^1.3.
|
|
70
|
+
"@types/bun": "^1.3.10",
|
|
71
|
+
"glob": "^13.0.6"
|
|
16
72
|
}
|
|
17
73
|
}
|
package/src/core.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export {
|
|
2
|
+
disposeEngine,
|
|
3
|
+
initEngine,
|
|
4
|
+
registerComponent,
|
|
5
|
+
registerSystem,
|
|
6
|
+
tick,
|
|
7
|
+
uploadBuffer,
|
|
8
|
+
} from './engine/api';
|
|
9
|
+
export { EngineError } from './engine/errors';
|
|
10
|
+
export type {
|
|
11
|
+
BufferResult,
|
|
12
|
+
EngineTransport,
|
|
13
|
+
EngineTransportFactory,
|
|
14
|
+
} from '@vulfram/transport-types';
|
package/src/ecs.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './engine/ecs';
|
package/src/engine/api.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { EngineTransportFactory } from '@vulfram/transport-types';
|
|
2
2
|
import {
|
|
3
3
|
collectCommands,
|
|
4
|
+
markRoutingIndexDirty,
|
|
4
5
|
routeEvents,
|
|
5
6
|
routeResponses,
|
|
6
7
|
} from './bridge/dispatch';
|
|
@@ -13,10 +14,45 @@ import {
|
|
|
13
14
|
import type { ComponentSchema, System, SystemContext, SystemStep } from './ecs';
|
|
14
15
|
import { EngineError } from './errors';
|
|
15
16
|
import { engineState, REQUIRED_SYSTEMS, type WorldState } from './state';
|
|
17
|
+
import { createIntentStore } from './intents/store';
|
|
16
18
|
import * as CoreSystems from './systems';
|
|
17
19
|
import type { UploadType } from '../types/kinds';
|
|
20
|
+
import type { RealmKind } from '../types/cmds/realm';
|
|
21
|
+
import { asWorldId, type WorldId } from './world/types';
|
|
18
22
|
|
|
19
|
-
export * from './
|
|
23
|
+
export * from './window/manager';
|
|
24
|
+
|
|
25
|
+
function recalculateWorldWindowBindings(world: WorldState): void {
|
|
26
|
+
world.boundWindowIds.clear();
|
|
27
|
+
for (const windowId of world.targetWindowBindings.values()) {
|
|
28
|
+
world.boundWindowIds.add(windowId);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (world.boundWindowIds.size === 0) {
|
|
32
|
+
world.primaryWindowId = undefined;
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
let primary = Number.POSITIVE_INFINITY;
|
|
37
|
+
for (const windowId of world.boundWindowIds) {
|
|
38
|
+
if (windowId < primary) {
|
|
39
|
+
primary = windowId;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
world.primaryWindowId = primary;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Shared realm creation options used by `createWorld3D` and `createWorldUI`.
|
|
47
|
+
*/
|
|
48
|
+
export type CreateWorldOptions = {
|
|
49
|
+
/** Core scheduling hint for realm priority. */
|
|
50
|
+
importance?: number;
|
|
51
|
+
/** Core-side cache policy value. */
|
|
52
|
+
cachePolicy?: number;
|
|
53
|
+
/** Bit flags forwarded to realm creation in core. */
|
|
54
|
+
flags?: number;
|
|
55
|
+
};
|
|
20
56
|
|
|
21
57
|
/**
|
|
22
58
|
* Initializes the engine runtime and registers core systems.
|
|
@@ -35,9 +71,24 @@ export function initEngine(config: {
|
|
|
35
71
|
// Reset Engine State for fresh start (important if re-initializing after dispose)
|
|
36
72
|
engineState.worlds.clear();
|
|
37
73
|
engineState.commandBatch = [];
|
|
74
|
+
engineState.usedWindowIds.clear();
|
|
75
|
+
engineState.confirmedWindowIds.clear();
|
|
76
|
+
engineState.pendingWindowCreateByCommandId.clear();
|
|
77
|
+
engineState.pendingWindowCloseByCommandId.clear();
|
|
78
|
+
engineState.globalPendingCommands = [];
|
|
79
|
+
engineState.globalPendingCommandsHead = 0;
|
|
38
80
|
engineState.commandTracker.clear();
|
|
81
|
+
engineState.globalCommandTracker.clear();
|
|
82
|
+
engineState.globalInboundResponses = [];
|
|
83
|
+
engineState.nextWorldId = 1;
|
|
84
|
+
engineState.nextWindowId = 1;
|
|
39
85
|
engineState.nextEntityId = 1;
|
|
40
86
|
engineState.nextCommandId = 1;
|
|
87
|
+
engineState.nextGlobalId = 100;
|
|
88
|
+
engineState.routingIndex.byWindowId.clear();
|
|
89
|
+
engineState.routingIndex.byRealmId.clear();
|
|
90
|
+
engineState.routingIndex.byTargetId.clear();
|
|
91
|
+
engineState.routingIndex.dirty = true;
|
|
41
92
|
engineState.registry.systems.input = [];
|
|
42
93
|
engineState.registry.systems.update = [];
|
|
43
94
|
engineState.registry.systems.preRender = [];
|
|
@@ -60,9 +111,11 @@ export function initEngine(config: {
|
|
|
60
111
|
// Register Core Systems
|
|
61
112
|
registerSystem('input', CoreSystems.InputMirrorSystem);
|
|
62
113
|
registerSystem('update', CoreSystems.CommandIntentSystem);
|
|
114
|
+
registerSystem('update', CoreSystems.UiBridgeSystem);
|
|
63
115
|
registerSystem('update', CoreSystems.WorldLifecycleSystem);
|
|
64
116
|
registerSystem('update', CoreSystems.ResourceUploadSystem);
|
|
65
|
-
registerSystem('preRender', CoreSystems.
|
|
117
|
+
registerSystem('preRender', CoreSystems.ConstraintSolveSystem);
|
|
118
|
+
registerSystem('preRender', CoreSystems.SceneSyncSystem);
|
|
66
119
|
registerSystem('postRender', CoreSystems.ResponseDecodeSystem);
|
|
67
120
|
registerSystem('postRender', CoreSystems.DiagnosticsSystem);
|
|
68
121
|
}
|
|
@@ -80,7 +133,19 @@ export function disposeEngine(): void {
|
|
|
80
133
|
engineState.transport = null;
|
|
81
134
|
engineState.worlds.clear();
|
|
82
135
|
engineState.commandBatch = [];
|
|
136
|
+
engineState.usedWindowIds.clear();
|
|
137
|
+
engineState.confirmedWindowIds.clear();
|
|
138
|
+
engineState.pendingWindowCreateByCommandId.clear();
|
|
139
|
+
engineState.pendingWindowCloseByCommandId.clear();
|
|
140
|
+
engineState.globalPendingCommands = [];
|
|
141
|
+
engineState.globalPendingCommandsHead = 0;
|
|
83
142
|
engineState.commandTracker.clear();
|
|
143
|
+
engineState.globalCommandTracker.clear();
|
|
144
|
+
engineState.globalInboundResponses = [];
|
|
145
|
+
engineState.routingIndex.byWindowId.clear();
|
|
146
|
+
engineState.routingIndex.byRealmId.clear();
|
|
147
|
+
engineState.routingIndex.byTargetId.clear();
|
|
148
|
+
engineState.routingIndex.dirty = true;
|
|
84
149
|
engineState.status = 'disposed';
|
|
85
150
|
}
|
|
86
151
|
|
|
@@ -93,7 +158,13 @@ export function registerComponent(name: string, schema: ComponentSchema): void {
|
|
|
93
158
|
}
|
|
94
159
|
|
|
95
160
|
/**
|
|
96
|
-
* Registers a system
|
|
161
|
+
* Registers a custom system into the engine pipeline.
|
|
162
|
+
*
|
|
163
|
+
* Stage semantics:
|
|
164
|
+
* - `input`: consume mirrored inbound events/state for the current frame.
|
|
165
|
+
* - `update`: mutate ECS state and emit intents.
|
|
166
|
+
* - `preRender`: resolve constraints and emit core commands.
|
|
167
|
+
* - `postRender`: consume command responses and diagnostics.
|
|
97
168
|
*/
|
|
98
169
|
export function registerSystem(step: SystemStep, system: System): void {
|
|
99
170
|
requireInitialized();
|
|
@@ -119,12 +190,6 @@ function uploadTypeToId(type: UploadType): number {
|
|
|
119
190
|
}
|
|
120
191
|
}
|
|
121
192
|
|
|
122
|
-
/**
|
|
123
|
-
* Uploads a raw buffer to the engine's GPU memory or internal storage.
|
|
124
|
-
* @param bufferId Unique ID for this buffer.
|
|
125
|
-
* @param type Type of data in the buffer (ImageData, VertexData, etc).
|
|
126
|
-
* @param data The raw binary data.
|
|
127
|
-
*/
|
|
128
193
|
/**
|
|
129
194
|
* Uploads a raw buffer to the core for later use (textures, geometry, etc.).
|
|
130
195
|
*/
|
|
@@ -148,36 +213,111 @@ export function uploadBuffer(
|
|
|
148
213
|
}
|
|
149
214
|
}
|
|
150
215
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
216
|
+
function createRealmWorld(
|
|
217
|
+
kind: RealmKind,
|
|
218
|
+
config: CreateWorldOptions = {},
|
|
219
|
+
): WorldId {
|
|
155
220
|
requireInitialized();
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
}
|
|
221
|
+
|
|
222
|
+
const worldId = engineState.nextWorldId++;
|
|
159
223
|
|
|
160
224
|
const world: WorldState = {
|
|
161
|
-
|
|
225
|
+
worldId,
|
|
226
|
+
realmKind: kind,
|
|
227
|
+
primaryWindowId: undefined,
|
|
228
|
+
boundWindowIds: new Set(),
|
|
229
|
+
targetLayerBindings: new Map(),
|
|
230
|
+
targetWindowBindings: new Map(),
|
|
231
|
+
coreRealmId: undefined,
|
|
232
|
+
realmCreateArgs: {
|
|
233
|
+
kind,
|
|
234
|
+
importance: config.importance,
|
|
235
|
+
cachePolicy: config.cachePolicy,
|
|
236
|
+
flags: config.flags,
|
|
237
|
+
},
|
|
238
|
+
coreSurfaceId: undefined,
|
|
239
|
+
corePresentId: undefined,
|
|
240
|
+
resolvedEntityTransforms: new Map(),
|
|
241
|
+
constraintDirtyEntities: new Set(),
|
|
242
|
+
constraintScratchResolved: new Map(),
|
|
243
|
+
constraintScratchVisiting: new Set(),
|
|
244
|
+
constraintChangedEntities: new Set(),
|
|
245
|
+
constraintChildrenByParent: new Map(),
|
|
246
|
+
constraintParentByChild: new Map(),
|
|
247
|
+
sceneSyncMatrixScratch: new Map(),
|
|
162
248
|
entities: new Set(),
|
|
163
249
|
components: new Map(),
|
|
164
250
|
nextCoreId: 100,
|
|
165
251
|
systems: [...REQUIRED_SYSTEMS],
|
|
166
|
-
|
|
252
|
+
intentStore: createIntentStore(),
|
|
167
253
|
internalEvents: [],
|
|
168
254
|
pendingCommands: [],
|
|
255
|
+
pendingCommandsHead: 0,
|
|
169
256
|
inboundEvents: [],
|
|
170
257
|
inboundResponses: [],
|
|
258
|
+
realmCreateRetryCount: 0,
|
|
259
|
+
nextRealmCreateRetryAtMs: 0,
|
|
171
260
|
};
|
|
172
261
|
|
|
173
|
-
engineState.worlds.set(
|
|
174
|
-
|
|
262
|
+
engineState.worlds.set(worldId, world);
|
|
263
|
+
markRoutingIndexDirty();
|
|
264
|
+
world.pendingCommands.push({
|
|
265
|
+
id: engineState.nextCommandId++,
|
|
266
|
+
type: 'cmd-realm-create',
|
|
267
|
+
content: world.realmCreateArgs,
|
|
268
|
+
});
|
|
269
|
+
return asWorldId(worldId);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Creates a `three-d` realm world and queues `cmd-realm-create`.
|
|
274
|
+
*
|
|
275
|
+
* This function only allocates local runtime state and enqueues the create command.
|
|
276
|
+
* The core realm is resolved asynchronously after at least one `tick`.
|
|
277
|
+
*
|
|
278
|
+
* Preconditions:
|
|
279
|
+
* - `initEngine` must have been called.
|
|
280
|
+
*
|
|
281
|
+
* Side effects:
|
|
282
|
+
* - Allocates a new world ID.
|
|
283
|
+
* - Registers the world in engine state.
|
|
284
|
+
* - Enqueues `cmd-realm-create` for the new world.
|
|
285
|
+
*
|
|
286
|
+
* @param config Optional create options.
|
|
287
|
+
* @returns Numeric world ID associated with a core `three-d` realm.
|
|
288
|
+
*/
|
|
289
|
+
export function createWorld3D(config?: CreateWorldOptions): WorldId {
|
|
290
|
+
return createRealmWorld('three-d', config);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Creates a `two-d` realm world for UI-centric pipelines and queues `cmd-realm-create`.
|
|
295
|
+
*
|
|
296
|
+
* This world type is intended for the dedicated WorldUI functional APIs.
|
|
297
|
+
* The core realm is resolved asynchronously after at least one `tick`.
|
|
298
|
+
*
|
|
299
|
+
* Preconditions:
|
|
300
|
+
* - `initEngine` must have been called.
|
|
301
|
+
*
|
|
302
|
+
* Side effects:
|
|
303
|
+
* - Allocates a new world ID.
|
|
304
|
+
* - Registers the world in engine state.
|
|
305
|
+
* - Enqueues `cmd-realm-create` for the new world.
|
|
306
|
+
*
|
|
307
|
+
* @param config Optional create options.
|
|
308
|
+
* @returns Numeric world ID associated with a core `two-d` realm.
|
|
309
|
+
*/
|
|
310
|
+
export function createWorldUI(config?: CreateWorldOptions): WorldId {
|
|
311
|
+
return createRealmWorld('two-d', config);
|
|
175
312
|
}
|
|
176
313
|
|
|
177
314
|
/**
|
|
178
|
-
*
|
|
179
|
-
* Orchestrates event routing, world updates, command collection, and core synchronization.
|
|
315
|
+
* Creates a default `three-d` world.
|
|
180
316
|
*/
|
|
317
|
+
export function createWorld(config?: CreateWorldOptions): WorldId {
|
|
318
|
+
return createWorld3D(config);
|
|
319
|
+
}
|
|
320
|
+
|
|
181
321
|
/**
|
|
182
322
|
* Advances the engine by one frame.
|
|
183
323
|
* Call once per frame with monotonic time and delta in milliseconds.
|
|
@@ -185,6 +325,8 @@ export function createWorld(windowId: number): number {
|
|
|
185
325
|
export function tick(timeMs: number, deltaMs: number): void {
|
|
186
326
|
requireInitialized();
|
|
187
327
|
const transport = engineState.transport!;
|
|
328
|
+
const coreTimeMs = Math.max(0, Math.floor(timeMs));
|
|
329
|
+
const coreDeltaMs = Math.max(0, Math.floor(deltaMs));
|
|
188
330
|
|
|
189
331
|
// 1. Engine Phase: Receive from Core (Input Pipeline)
|
|
190
332
|
// We process events and responses received since last frame
|
|
@@ -199,9 +341,10 @@ export function tick(timeMs: number, deltaMs: number): void {
|
|
|
199
341
|
const responses = deserializeResponses(responsesResult.buffer);
|
|
200
342
|
routeResponses(responses);
|
|
201
343
|
}
|
|
344
|
+
processGlobalResponses();
|
|
202
345
|
|
|
203
346
|
// 2. Engine Phase: Clock & Context Preparation
|
|
204
|
-
const cappedDelta = Math.min(
|
|
347
|
+
const cappedDelta = Math.min(coreDeltaMs, 100);
|
|
205
348
|
engineState.clock.lastTime = timeMs;
|
|
206
349
|
engineState.clock.lastDelta = cappedDelta;
|
|
207
350
|
engineState.clock.frameCount++;
|
|
@@ -238,6 +381,14 @@ export function tick(timeMs: number, deltaMs: number): void {
|
|
|
238
381
|
);
|
|
239
382
|
for (const cmd of engineState.commandBatch) {
|
|
240
383
|
engineState.commandTracker.delete(cmd.id);
|
|
384
|
+
engineState.globalCommandTracker.delete(cmd.id);
|
|
385
|
+
const pendingCreateWindowId =
|
|
386
|
+
engineState.pendingWindowCreateByCommandId.get(cmd.id);
|
|
387
|
+
if (pendingCreateWindowId !== undefined) {
|
|
388
|
+
engineState.usedWindowIds.delete(pendingCreateWindowId);
|
|
389
|
+
engineState.pendingWindowCreateByCommandId.delete(cmd.id);
|
|
390
|
+
}
|
|
391
|
+
engineState.pendingWindowCloseByCommandId.delete(cmd.id);
|
|
241
392
|
}
|
|
242
393
|
if (engineState.flags.debugEnabled) {
|
|
243
394
|
console.group('[Vulfram Debug] Failed Batch');
|
|
@@ -254,7 +405,54 @@ export function tick(timeMs: number, deltaMs: number): void {
|
|
|
254
405
|
}
|
|
255
406
|
|
|
256
407
|
// 5. Core Phase: Execute Tick
|
|
257
|
-
transport.vulframTick(
|
|
408
|
+
transport.vulframTick(coreTimeMs, coreDeltaMs);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
function processGlobalResponses(): void {
|
|
412
|
+
for (let i = 0; i < engineState.globalInboundResponses.length; i++) {
|
|
413
|
+
const res = engineState.globalInboundResponses[i]!;
|
|
414
|
+
const content = res.content as { success?: boolean; message?: string };
|
|
415
|
+
if (res.type === 'window-create') {
|
|
416
|
+
const pendingWindowId = engineState.pendingWindowCreateByCommandId.get(
|
|
417
|
+
res.id,
|
|
418
|
+
);
|
|
419
|
+
if (pendingWindowId !== undefined) {
|
|
420
|
+
if (content.success) {
|
|
421
|
+
engineState.confirmedWindowIds.add(pendingWindowId);
|
|
422
|
+
} else {
|
|
423
|
+
engineState.usedWindowIds.delete(pendingWindowId);
|
|
424
|
+
}
|
|
425
|
+
engineState.pendingWindowCreateByCommandId.delete(res.id);
|
|
426
|
+
}
|
|
427
|
+
} else if (res.type === 'window-close') {
|
|
428
|
+
const pendingWindowId = engineState.pendingWindowCloseByCommandId.get(
|
|
429
|
+
res.id,
|
|
430
|
+
);
|
|
431
|
+
if (pendingWindowId !== undefined) {
|
|
432
|
+
if (content.success) {
|
|
433
|
+
engineState.usedWindowIds.delete(pendingWindowId);
|
|
434
|
+
engineState.confirmedWindowIds.delete(pendingWindowId);
|
|
435
|
+
for (const world of engineState.worlds.values()) {
|
|
436
|
+
for (const [targetId, windowId] of world.targetWindowBindings) {
|
|
437
|
+
if (windowId === pendingWindowId) {
|
|
438
|
+
world.targetWindowBindings.delete(targetId);
|
|
439
|
+
world.targetLayerBindings.delete(targetId);
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
recalculateWorldWindowBindings(world);
|
|
443
|
+
}
|
|
444
|
+
markRoutingIndexDirty();
|
|
445
|
+
}
|
|
446
|
+
engineState.pendingWindowCloseByCommandId.delete(res.id);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
if (content && typeof content.success === 'boolean' && !content.success) {
|
|
450
|
+
console.error(
|
|
451
|
+
`[Global] Command ${res.type} (ID: ${res.id}) failed: ${content.message}`,
|
|
452
|
+
);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
engineState.globalInboundResponses.length = 0;
|
|
258
456
|
}
|
|
259
457
|
|
|
260
458
|
/**
|