@vulfram/engine 0.5.8-alpha → 0.17.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/README.md +106 -0
- package/package.json +55 -4
- package/src/core.ts +14 -0
- package/src/ecs.ts +1 -0
- package/src/engine/api.ts +234 -23
- package/src/engine/bridge/dispatch.ts +265 -40
- package/src/engine/bridge/guards.ts +4 -1
- package/src/engine/bridge/protocol.ts +72 -54
- package/src/engine/ecs/index.ts +187 -42
- package/src/engine/state.ts +133 -2
- package/src/engine/systems/command-intent.ts +153 -3
- package/src/engine/systems/constraint-solve.ts +167 -0
- package/src/engine/systems/core-command-builder.ts +9 -265
- package/src/engine/systems/diagnostics.ts +20 -19
- package/src/engine/systems/index.ts +3 -1
- package/src/engine/systems/input-mirror.ts +101 -3
- package/src/engine/systems/resource-upload.ts +96 -44
- package/src/engine/systems/response-decode.ts +69 -15
- package/src/engine/systems/scene-sync.ts +306 -0
- package/src/engine/systems/ui-bridge.ts +360 -0
- package/src/engine/systems/utils.ts +43 -1
- package/src/engine/systems/world-lifecycle.ts +72 -103
- package/src/engine/window/manager.ts +168 -0
- package/src/engine/world/entities.ts +931 -33
- package/src/engine/world/mount.ts +174 -0
- package/src/engine/world/types.ts +71 -0
- package/src/engine/world/world-ui.ts +266 -0
- package/src/engine/world/world3d.ts +280 -0
- package/src/index.ts +30 -1
- package/src/mount.ts +2 -0
- package/src/types/cmds/audio.ts +189 -0
- package/src/types/cmds/camera.ts +18 -13
- package/src/types/cmds/environment.ts +47 -4
- package/src/types/cmds/geometry.ts +18 -16
- package/src/types/cmds/index.ts +203 -132
- package/src/types/cmds/light.ts +17 -13
- package/src/types/cmds/material.ts +14 -13
- package/src/types/cmds/model.ts +40 -16
- package/src/types/cmds/realm.ts +25 -0
- package/src/types/cmds/render-graph.ts +49 -0
- package/src/types/cmds/resources.ts +4 -0
- package/src/types/cmds/shadow.ts +7 -7
- package/src/types/cmds/system.ts +29 -0
- package/src/types/cmds/target.ts +82 -0
- package/src/types/cmds/texture.ts +19 -5
- 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/pointer.ts +42 -13
- package/src/types/events/system.ts +150 -7
- package/src/types/events/ui.ts +21 -0
- package/src/types/index.ts +1 -0
- package/src/types/json.ts +15 -0
- package/src/types/kinds.ts +3 -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-wasm
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Use the transport that fits your environment (`transport-wasm`, `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-wasm';
|
|
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,68 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vulfram/engine",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.1-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
|
+
"./package.json": "./package.json"
|
|
51
|
+
},
|
|
52
|
+
"files": [
|
|
53
|
+
"src",
|
|
54
|
+
"package.json"
|
|
55
|
+
],
|
|
56
|
+
"publishConfig": {
|
|
57
|
+
"access": "public"
|
|
58
|
+
},
|
|
8
59
|
"dependencies": {
|
|
9
|
-
"@vulfram/transport-types": "^0.2.
|
|
60
|
+
"@vulfram/transport-types": "^0.2.5",
|
|
10
61
|
"gl-matrix": "^3.4.4",
|
|
11
|
-
"glob": "^13.0.
|
|
62
|
+
"glob": "^13.0.6",
|
|
12
63
|
"msgpackr": "^1.11.8"
|
|
13
64
|
},
|
|
14
65
|
"devDependencies": {
|
|
15
|
-
"@types/bun": "^1.3.
|
|
66
|
+
"@types/bun": "^1.3.10"
|
|
16
67
|
}
|
|
17
68
|
}
|
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';
|
|
@@ -15,8 +16,57 @@ import { EngineError } from './errors';
|
|
|
15
16
|
import { engineState, REQUIRED_SYSTEMS, type WorldState } from './state';
|
|
16
17
|
import * as CoreSystems from './systems';
|
|
17
18
|
import type { UploadType } from '../types/kinds';
|
|
19
|
+
import type { RealmKind } from '../types/cmds/realm';
|
|
20
|
+
import { asWorldId, type WorldId } from './world/types';
|
|
18
21
|
|
|
19
|
-
export * from './
|
|
22
|
+
export * from './window/manager';
|
|
23
|
+
|
|
24
|
+
function recalculateWorldWindowBindings(world: WorldState): void {
|
|
25
|
+
world.boundWindowIds.clear();
|
|
26
|
+
for (const windowId of world.targetWindowBindings.values()) {
|
|
27
|
+
world.boundWindowIds.add(windowId);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (world.boundWindowIds.size === 0) {
|
|
31
|
+
world.primaryWindowId = undefined;
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
let primary = Number.POSITIVE_INFINITY;
|
|
36
|
+
for (const windowId of world.boundWindowIds) {
|
|
37
|
+
if (windowId < primary) {
|
|
38
|
+
primary = windowId;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
world.primaryWindowId = primary;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function findLowestConfirmedWindowId(): number | undefined {
|
|
45
|
+
if (engineState.confirmedWindowIds.size === 0) {
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
let minWindowId = Number.POSITIVE_INFINITY;
|
|
49
|
+
for (const windowId of engineState.confirmedWindowIds) {
|
|
50
|
+
if (windowId < minWindowId) {
|
|
51
|
+
minWindowId = windowId;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return Number.isFinite(minWindowId) ? minWindowId : undefined;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Shared realm creation options used by `createWorld3D` and `createWorldUI`.
|
|
59
|
+
*/
|
|
60
|
+
export type CreateWorldOptions = {
|
|
61
|
+
/** Optional output surface id when provided by host integration. */
|
|
62
|
+
outputSurfaceId?: number;
|
|
63
|
+
/** Core scheduling hint for realm priority. */
|
|
64
|
+
importance?: number;
|
|
65
|
+
/** Core-side cache policy value. */
|
|
66
|
+
cachePolicy?: number;
|
|
67
|
+
/** Bit flags forwarded to realm creation in core. */
|
|
68
|
+
flags?: number;
|
|
69
|
+
};
|
|
20
70
|
|
|
21
71
|
/**
|
|
22
72
|
* Initializes the engine runtime and registers core systems.
|
|
@@ -35,9 +85,24 @@ export function initEngine(config: {
|
|
|
35
85
|
// Reset Engine State for fresh start (important if re-initializing after dispose)
|
|
36
86
|
engineState.worlds.clear();
|
|
37
87
|
engineState.commandBatch = [];
|
|
88
|
+
engineState.usedWindowIds.clear();
|
|
89
|
+
engineState.confirmedWindowIds.clear();
|
|
90
|
+
engineState.pendingWindowCreateByCommandId.clear();
|
|
91
|
+
engineState.pendingWindowCloseByCommandId.clear();
|
|
92
|
+
engineState.globalPendingCommands = [];
|
|
93
|
+
engineState.globalPendingCommandsHead = 0;
|
|
38
94
|
engineState.commandTracker.clear();
|
|
95
|
+
engineState.globalCommandTracker.clear();
|
|
96
|
+
engineState.globalInboundResponses = [];
|
|
97
|
+
engineState.nextWorldId = 1;
|
|
98
|
+
engineState.nextWindowId = 1;
|
|
39
99
|
engineState.nextEntityId = 1;
|
|
40
100
|
engineState.nextCommandId = 1;
|
|
101
|
+
engineState.nextGlobalId = 100;
|
|
102
|
+
engineState.routingIndex.byWindowId.clear();
|
|
103
|
+
engineState.routingIndex.byRealmId.clear();
|
|
104
|
+
engineState.routingIndex.byTargetId.clear();
|
|
105
|
+
engineState.routingIndex.dirty = true;
|
|
41
106
|
engineState.registry.systems.input = [];
|
|
42
107
|
engineState.registry.systems.update = [];
|
|
43
108
|
engineState.registry.systems.preRender = [];
|
|
@@ -60,9 +125,11 @@ export function initEngine(config: {
|
|
|
60
125
|
// Register Core Systems
|
|
61
126
|
registerSystem('input', CoreSystems.InputMirrorSystem);
|
|
62
127
|
registerSystem('update', CoreSystems.CommandIntentSystem);
|
|
128
|
+
registerSystem('update', CoreSystems.UiBridgeSystem);
|
|
63
129
|
registerSystem('update', CoreSystems.WorldLifecycleSystem);
|
|
64
130
|
registerSystem('update', CoreSystems.ResourceUploadSystem);
|
|
65
|
-
registerSystem('preRender', CoreSystems.
|
|
131
|
+
registerSystem('preRender', CoreSystems.ConstraintSolveSystem);
|
|
132
|
+
registerSystem('preRender', CoreSystems.SceneSyncSystem);
|
|
66
133
|
registerSystem('postRender', CoreSystems.ResponseDecodeSystem);
|
|
67
134
|
registerSystem('postRender', CoreSystems.DiagnosticsSystem);
|
|
68
135
|
}
|
|
@@ -80,7 +147,19 @@ export function disposeEngine(): void {
|
|
|
80
147
|
engineState.transport = null;
|
|
81
148
|
engineState.worlds.clear();
|
|
82
149
|
engineState.commandBatch = [];
|
|
150
|
+
engineState.usedWindowIds.clear();
|
|
151
|
+
engineState.confirmedWindowIds.clear();
|
|
152
|
+
engineState.pendingWindowCreateByCommandId.clear();
|
|
153
|
+
engineState.pendingWindowCloseByCommandId.clear();
|
|
154
|
+
engineState.globalPendingCommands = [];
|
|
155
|
+
engineState.globalPendingCommandsHead = 0;
|
|
83
156
|
engineState.commandTracker.clear();
|
|
157
|
+
engineState.globalCommandTracker.clear();
|
|
158
|
+
engineState.globalInboundResponses = [];
|
|
159
|
+
engineState.routingIndex.byWindowId.clear();
|
|
160
|
+
engineState.routingIndex.byRealmId.clear();
|
|
161
|
+
engineState.routingIndex.byTargetId.clear();
|
|
162
|
+
engineState.routingIndex.dirty = true;
|
|
84
163
|
engineState.status = 'disposed';
|
|
85
164
|
}
|
|
86
165
|
|
|
@@ -93,7 +172,13 @@ export function registerComponent(name: string, schema: ComponentSchema): void {
|
|
|
93
172
|
}
|
|
94
173
|
|
|
95
174
|
/**
|
|
96
|
-
* Registers a system
|
|
175
|
+
* Registers a custom system into the engine pipeline.
|
|
176
|
+
*
|
|
177
|
+
* Stage semantics:
|
|
178
|
+
* - `input`: consume mirrored inbound events/state for the current frame.
|
|
179
|
+
* - `update`: mutate ECS state and emit intents.
|
|
180
|
+
* - `preRender`: resolve constraints and emit core commands.
|
|
181
|
+
* - `postRender`: consume command responses and diagnostics.
|
|
97
182
|
*/
|
|
98
183
|
export function registerSystem(step: SystemStep, system: System): void {
|
|
99
184
|
requireInitialized();
|
|
@@ -119,12 +204,6 @@ function uploadTypeToId(type: UploadType): number {
|
|
|
119
204
|
}
|
|
120
205
|
}
|
|
121
206
|
|
|
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
207
|
/**
|
|
129
208
|
* Uploads a raw buffer to the core for later use (textures, geometry, etc.).
|
|
130
209
|
*/
|
|
@@ -148,17 +227,41 @@ export function uploadBuffer(
|
|
|
148
227
|
}
|
|
149
228
|
}
|
|
150
229
|
|
|
151
|
-
|
|
152
|
-
* Creates a world bound to a window ID. Returns the world ID.
|
|
153
|
-
*/
|
|
154
|
-
export function createWorld(windowId: number): number {
|
|
230
|
+
function createRealmWorld(kind: RealmKind, config: CreateWorldOptions = {}): WorldId {
|
|
155
231
|
requireInitialized();
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
232
|
+
|
|
233
|
+
const worldId = engineState.nextWorldId++;
|
|
234
|
+
// Core compatibility: when at least one window exists, pick the lowest ID as host window
|
|
235
|
+
// if the caller did not provide an explicit surface. This keeps world API window-agnostic
|
|
236
|
+
// while satisfying render graph bootstrap paths that still rely on host-window mapping.
|
|
237
|
+
const inferredHostWindowId = findLowestConfirmedWindowId();
|
|
159
238
|
|
|
160
239
|
const world: WorldState = {
|
|
161
|
-
|
|
240
|
+
worldId,
|
|
241
|
+
realmKind: kind,
|
|
242
|
+
primaryWindowId: undefined,
|
|
243
|
+
boundWindowIds: new Set(),
|
|
244
|
+
targetLayerBindings: new Map(),
|
|
245
|
+
targetWindowBindings: new Map(),
|
|
246
|
+
coreRealmId: undefined,
|
|
247
|
+
realmCreateArgs: {
|
|
248
|
+
kind,
|
|
249
|
+
hostWindowId: inferredHostWindowId,
|
|
250
|
+
outputSurfaceId: config.outputSurfaceId,
|
|
251
|
+
importance: config.importance,
|
|
252
|
+
cachePolicy: config.cachePolicy,
|
|
253
|
+
flags: config.flags,
|
|
254
|
+
},
|
|
255
|
+
coreSurfaceId: undefined,
|
|
256
|
+
corePresentId: undefined,
|
|
257
|
+
resolvedEntityTransforms: new Map(),
|
|
258
|
+
constraintDirtyEntities: new Set(),
|
|
259
|
+
constraintScratchResolved: new Map(),
|
|
260
|
+
constraintScratchVisiting: new Set(),
|
|
261
|
+
constraintChangedEntities: new Set(),
|
|
262
|
+
constraintChildrenByParent: new Map(),
|
|
263
|
+
constraintParentByChild: new Map(),
|
|
264
|
+
sceneSyncMatrixScratch: new Map(),
|
|
162
265
|
entities: new Set(),
|
|
163
266
|
components: new Map(),
|
|
164
267
|
nextCoreId: 100,
|
|
@@ -166,18 +269,72 @@ export function createWorld(windowId: number): number {
|
|
|
166
269
|
pendingIntents: [],
|
|
167
270
|
internalEvents: [],
|
|
168
271
|
pendingCommands: [],
|
|
272
|
+
pendingCommandsHead: 0,
|
|
169
273
|
inboundEvents: [],
|
|
170
274
|
inboundResponses: [],
|
|
275
|
+
realmCreateRetryCount: 0,
|
|
276
|
+
nextRealmCreateRetryAtMs: 0,
|
|
171
277
|
};
|
|
172
278
|
|
|
173
|
-
engineState.worlds.set(
|
|
174
|
-
|
|
279
|
+
engineState.worlds.set(worldId, world);
|
|
280
|
+
markRoutingIndexDirty();
|
|
281
|
+
world.pendingCommands.push({
|
|
282
|
+
id: engineState.nextCommandId++,
|
|
283
|
+
type: 'cmd-realm-create',
|
|
284
|
+
content: world.realmCreateArgs,
|
|
285
|
+
});
|
|
286
|
+
return asWorldId(worldId);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Creates a `three-d` realm world and queues `cmd-realm-create`.
|
|
291
|
+
*
|
|
292
|
+
* This function only allocates local runtime state and enqueues the create command.
|
|
293
|
+
* The core realm is resolved asynchronously after at least one `tick`.
|
|
294
|
+
*
|
|
295
|
+
* Preconditions:
|
|
296
|
+
* - `initEngine` must have been called.
|
|
297
|
+
*
|
|
298
|
+
* Side effects:
|
|
299
|
+
* - Allocates a new world ID.
|
|
300
|
+
* - Registers the world in engine state.
|
|
301
|
+
* - Enqueues `cmd-realm-create` for the new world.
|
|
302
|
+
*
|
|
303
|
+
* @param config Optional create options.
|
|
304
|
+
* @returns Numeric world ID associated with a core `three-d` realm.
|
|
305
|
+
*/
|
|
306
|
+
export function createWorld3D(config?: CreateWorldOptions): WorldId {
|
|
307
|
+
return createRealmWorld('three-d', config);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Creates a `two-d` realm world for UI-centric pipelines and queues `cmd-realm-create`.
|
|
312
|
+
*
|
|
313
|
+
* This world type is intended for the dedicated WorldUI functional APIs.
|
|
314
|
+
* The core realm is resolved asynchronously after at least one `tick`.
|
|
315
|
+
*
|
|
316
|
+
* Preconditions:
|
|
317
|
+
* - `initEngine` must have been called.
|
|
318
|
+
*
|
|
319
|
+
* Side effects:
|
|
320
|
+
* - Allocates a new world ID.
|
|
321
|
+
* - Registers the world in engine state.
|
|
322
|
+
* - Enqueues `cmd-realm-create` for the new world.
|
|
323
|
+
*
|
|
324
|
+
* @param config Optional create options.
|
|
325
|
+
* @returns Numeric world ID associated with a core `two-d` realm.
|
|
326
|
+
*/
|
|
327
|
+
export function createWorldUI(config?: CreateWorldOptions): WorldId {
|
|
328
|
+
return createRealmWorld('two-d', config);
|
|
175
329
|
}
|
|
176
330
|
|
|
177
331
|
/**
|
|
178
|
-
*
|
|
179
|
-
* Orchestrates event routing, world updates, command collection, and core synchronization.
|
|
332
|
+
* Creates a default `three-d` world.
|
|
180
333
|
*/
|
|
334
|
+
export function createWorld(config?: CreateWorldOptions): WorldId {
|
|
335
|
+
return createWorld3D(config);
|
|
336
|
+
}
|
|
337
|
+
|
|
181
338
|
/**
|
|
182
339
|
* Advances the engine by one frame.
|
|
183
340
|
* Call once per frame with monotonic time and delta in milliseconds.
|
|
@@ -185,6 +342,8 @@ export function createWorld(windowId: number): number {
|
|
|
185
342
|
export function tick(timeMs: number, deltaMs: number): void {
|
|
186
343
|
requireInitialized();
|
|
187
344
|
const transport = engineState.transport!;
|
|
345
|
+
const coreTimeMs = Math.max(0, Math.floor(timeMs));
|
|
346
|
+
const coreDeltaMs = Math.max(0, Math.floor(deltaMs));
|
|
188
347
|
|
|
189
348
|
// 1. Engine Phase: Receive from Core (Input Pipeline)
|
|
190
349
|
// We process events and responses received since last frame
|
|
@@ -199,9 +358,10 @@ export function tick(timeMs: number, deltaMs: number): void {
|
|
|
199
358
|
const responses = deserializeResponses(responsesResult.buffer);
|
|
200
359
|
routeResponses(responses);
|
|
201
360
|
}
|
|
361
|
+
processGlobalResponses();
|
|
202
362
|
|
|
203
363
|
// 2. Engine Phase: Clock & Context Preparation
|
|
204
|
-
const cappedDelta = Math.min(
|
|
364
|
+
const cappedDelta = Math.min(coreDeltaMs, 100);
|
|
205
365
|
engineState.clock.lastTime = timeMs;
|
|
206
366
|
engineState.clock.lastDelta = cappedDelta;
|
|
207
367
|
engineState.clock.frameCount++;
|
|
@@ -238,6 +398,14 @@ export function tick(timeMs: number, deltaMs: number): void {
|
|
|
238
398
|
);
|
|
239
399
|
for (const cmd of engineState.commandBatch) {
|
|
240
400
|
engineState.commandTracker.delete(cmd.id);
|
|
401
|
+
engineState.globalCommandTracker.delete(cmd.id);
|
|
402
|
+
const pendingCreateWindowId =
|
|
403
|
+
engineState.pendingWindowCreateByCommandId.get(cmd.id);
|
|
404
|
+
if (pendingCreateWindowId !== undefined) {
|
|
405
|
+
engineState.usedWindowIds.delete(pendingCreateWindowId);
|
|
406
|
+
engineState.pendingWindowCreateByCommandId.delete(cmd.id);
|
|
407
|
+
}
|
|
408
|
+
engineState.pendingWindowCloseByCommandId.delete(cmd.id);
|
|
241
409
|
}
|
|
242
410
|
if (engineState.flags.debugEnabled) {
|
|
243
411
|
console.group('[Vulfram Debug] Failed Batch');
|
|
@@ -254,7 +422,50 @@ export function tick(timeMs: number, deltaMs: number): void {
|
|
|
254
422
|
}
|
|
255
423
|
|
|
256
424
|
// 5. Core Phase: Execute Tick
|
|
257
|
-
transport.vulframTick(
|
|
425
|
+
transport.vulframTick(coreTimeMs, coreDeltaMs);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
function processGlobalResponses(): void {
|
|
429
|
+
for (let i = 0; i < engineState.globalInboundResponses.length; i++) {
|
|
430
|
+
const res = engineState.globalInboundResponses[i]!;
|
|
431
|
+
const content = res.content as { success?: boolean; message?: string };
|
|
432
|
+
if (res.type === 'window-create') {
|
|
433
|
+
const pendingWindowId = engineState.pendingWindowCreateByCommandId.get(res.id);
|
|
434
|
+
if (pendingWindowId !== undefined) {
|
|
435
|
+
if (content.success) {
|
|
436
|
+
engineState.confirmedWindowIds.add(pendingWindowId);
|
|
437
|
+
} else {
|
|
438
|
+
engineState.usedWindowIds.delete(pendingWindowId);
|
|
439
|
+
}
|
|
440
|
+
engineState.pendingWindowCreateByCommandId.delete(res.id);
|
|
441
|
+
}
|
|
442
|
+
} else if (res.type === 'window-close') {
|
|
443
|
+
const pendingWindowId = engineState.pendingWindowCloseByCommandId.get(res.id);
|
|
444
|
+
if (pendingWindowId !== undefined) {
|
|
445
|
+
if (content.success) {
|
|
446
|
+
engineState.usedWindowIds.delete(pendingWindowId);
|
|
447
|
+
engineState.confirmedWindowIds.delete(pendingWindowId);
|
|
448
|
+
for (const world of engineState.worlds.values()) {
|
|
449
|
+
for (const [targetId, windowId] of world.targetWindowBindings) {
|
|
450
|
+
if (windowId === pendingWindowId) {
|
|
451
|
+
world.targetWindowBindings.delete(targetId);
|
|
452
|
+
world.targetLayerBindings.delete(targetId);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
recalculateWorldWindowBindings(world);
|
|
456
|
+
}
|
|
457
|
+
markRoutingIndexDirty();
|
|
458
|
+
}
|
|
459
|
+
engineState.pendingWindowCloseByCommandId.delete(res.id);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
if (content && typeof content.success === 'boolean' && !content.success) {
|
|
463
|
+
console.error(
|
|
464
|
+
`[Global] Command ${res.type} (ID: ${res.id}) failed: ${content.message}`,
|
|
465
|
+
);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
engineState.globalInboundResponses.length = 0;
|
|
258
469
|
}
|
|
259
470
|
|
|
260
471
|
/**
|