@vulfram/engine 0.14.8-alpha → 0.19.2-alpha
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +106 -0
- package/package.json +60 -4
- package/src/core.ts +14 -0
- package/src/ecs.ts +1 -0
- package/src/engine/api.ts +222 -24
- package/src/engine/bridge/dispatch.ts +260 -40
- package/src/engine/bridge/guards.ts +4 -1
- package/src/engine/bridge/protocol.ts +69 -52
- package/src/engine/ecs/components.ts +340 -0
- package/src/engine/ecs/index.ts +3 -518
- package/src/engine/ecs/intents.ts +184 -0
- package/src/engine/ecs/systems.ts +26 -0
- package/src/engine/intents/store.ts +72 -0
- package/src/engine/state.ts +136 -5
- package/src/engine/systems/command-intent.ts +159 -14
- package/src/engine/systems/constraint-solve.ts +167 -0
- package/src/engine/systems/core-command-builder.ts +9 -268
- package/src/engine/systems/diagnostics.ts +20 -29
- package/src/engine/systems/index.ts +3 -1
- package/src/engine/systems/input-mirror.ts +257 -21
- package/src/engine/systems/resource-upload.ts +108 -58
- package/src/engine/systems/response-decode.ts +86 -15
- package/src/engine/systems/scene-sync.ts +305 -0
- package/src/engine/systems/ui-bridge.ts +381 -0
- package/src/engine/systems/utils.ts +86 -1
- package/src/engine/systems/world-lifecycle.ts +43 -114
- package/src/engine/window/manager.ts +168 -0
- package/src/engine/world/entities.ts +998 -91
- package/src/engine/world/mount.ts +195 -0
- package/src/engine/world/types.ts +71 -0
- package/src/engine/world/world-ui.ts +313 -0
- package/src/engine/world/world3d.ts +529 -0
- package/src/helpers/collision.ts +487 -0
- package/src/helpers/index.ts +2 -0
- package/src/helpers/raycast.ts +442 -0
- package/src/index.ts +30 -1
- package/src/mount.ts +2 -0
- package/src/types/cmds/audio.ts +73 -48
- package/src/types/cmds/camera.ts +12 -8
- package/src/types/cmds/environment.ts +9 -3
- package/src/types/cmds/geometry.ts +15 -16
- package/src/types/cmds/index.ts +234 -162
- package/src/types/cmds/input.ts +39 -0
- package/src/types/cmds/light.ts +12 -11
- package/src/types/cmds/material.ts +19 -21
- package/src/types/cmds/model.ts +17 -15
- package/src/types/cmds/realm.ts +23 -0
- package/src/types/cmds/system.ts +29 -0
- package/src/types/cmds/target.ts +96 -0
- package/src/types/cmds/texture.ts +13 -3
- package/src/types/cmds/ui.ts +220 -0
- package/src/types/cmds/window.ts +41 -204
- package/src/types/events/index.ts +4 -1
- package/src/types/events/keyboard.ts +2 -2
- package/src/types/events/pointer.ts +85 -13
- package/src/types/events/system.ts +188 -30
- package/src/types/events/ui.ts +21 -0
- package/src/types/index.ts +1 -0
- package/src/types/json.ts +15 -0
- package/src/window.ts +8 -0
- package/src/world-ui.ts +2 -0
- package/src/world3d.ts +10 -0
- package/tsconfig.json +0 -29
|
@@ -1,114 +1,31 @@
|
|
|
1
1
|
import type { EnvironmentConfig } from '../../types/cmds/environment';
|
|
2
2
|
import type { ShadowConfig } from '../../types/cmds/shadow';
|
|
3
|
-
import type { WindowState } from '../../types/kinds';
|
|
4
3
|
import { enqueueCommand } from '../bridge/dispatch';
|
|
5
4
|
import type { System } from '../ecs';
|
|
6
|
-
import {
|
|
5
|
+
import { toVec3, toVec4 } from './utils';
|
|
7
6
|
|
|
7
|
+
const WORLD_LIFECYCLE_INTENT_TYPES = [
|
|
8
|
+
'configure-environment',
|
|
9
|
+
'configure-shadows',
|
|
10
|
+
'send-notification',
|
|
11
|
+
] as const;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Applies world-scoped lifecycle/configuration intents.
|
|
15
|
+
*
|
|
16
|
+
* Covered intents:
|
|
17
|
+
* - environment configuration
|
|
18
|
+
* - shadow configuration
|
|
19
|
+
* - notification dispatch
|
|
20
|
+
*/
|
|
8
21
|
export const WorldLifecycleSystem: System = (world, context) => {
|
|
9
|
-
const
|
|
22
|
+
const intents = world.intentStore.takeMany(WORLD_LIFECYCLE_INTENT_TYPES);
|
|
10
23
|
|
|
11
|
-
for (let i = 0; i <
|
|
12
|
-
const intent =
|
|
24
|
+
for (let i = 0; i < intents.length; i++) {
|
|
25
|
+
const intent = intents[i];
|
|
13
26
|
if (!intent) continue;
|
|
14
27
|
|
|
15
|
-
if (intent.type === '
|
|
16
|
-
const cmd = {
|
|
17
|
-
windowId: context.worldId,
|
|
18
|
-
title: intent.props.title,
|
|
19
|
-
size: toVec2(intent.props.size),
|
|
20
|
-
position: toVec2(intent.props.position),
|
|
21
|
-
borderless: intent.props.borderless ?? false,
|
|
22
|
-
resizable: intent.props.resizable ?? true,
|
|
23
|
-
transparent: intent.props.transparent ?? false,
|
|
24
|
-
initialState: intent.props.initialState ?? ('windowed' as WindowState),
|
|
25
|
-
} as const;
|
|
26
|
-
const payload: typeof cmd & { canvasId?: string } = { ...cmd };
|
|
27
|
-
if (intent.props.canvasId !== undefined) {
|
|
28
|
-
payload.canvasId = intent.props.canvasId;
|
|
29
|
-
}
|
|
30
|
-
enqueueCommand(context.worldId, 'cmd-window-create', payload);
|
|
31
|
-
intentsToRemove.push(i);
|
|
32
|
-
} else if (intent.type === 'close-window') {
|
|
33
|
-
enqueueCommand(context.worldId, 'cmd-window-close', {
|
|
34
|
-
windowId: context.worldId,
|
|
35
|
-
});
|
|
36
|
-
intentsToRemove.push(i);
|
|
37
|
-
} else if (intent.type === 'update-window') {
|
|
38
|
-
const p = intent.props;
|
|
39
|
-
if (p.title !== undefined) {
|
|
40
|
-
enqueueCommand(context.worldId, 'cmd-window-set-title', {
|
|
41
|
-
windowId: context.worldId,
|
|
42
|
-
title: p.title,
|
|
43
|
-
});
|
|
44
|
-
}
|
|
45
|
-
if (p.position !== undefined) {
|
|
46
|
-
enqueueCommand(context.worldId, 'cmd-window-set-position', {
|
|
47
|
-
windowId: context.worldId,
|
|
48
|
-
position: p.position,
|
|
49
|
-
});
|
|
50
|
-
}
|
|
51
|
-
if (p.size !== undefined) {
|
|
52
|
-
enqueueCommand(context.worldId, 'cmd-window-set-size', {
|
|
53
|
-
windowId: context.worldId,
|
|
54
|
-
size: p.size,
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
if (p.state !== undefined) {
|
|
58
|
-
enqueueCommand(context.worldId, 'cmd-window-set-state', {
|
|
59
|
-
windowId: context.worldId,
|
|
60
|
-
state: p.state,
|
|
61
|
-
});
|
|
62
|
-
}
|
|
63
|
-
if (p.resizable !== undefined) {
|
|
64
|
-
enqueueCommand(context.worldId, 'cmd-window-set-resizable', {
|
|
65
|
-
windowId: context.worldId,
|
|
66
|
-
resizable: p.resizable,
|
|
67
|
-
});
|
|
68
|
-
}
|
|
69
|
-
if (p.decorations !== undefined) {
|
|
70
|
-
enqueueCommand(context.worldId, 'cmd-window-set-decorations', {
|
|
71
|
-
windowId: context.worldId,
|
|
72
|
-
decorations: p.decorations,
|
|
73
|
-
});
|
|
74
|
-
}
|
|
75
|
-
if (p.cursorVisible !== undefined) {
|
|
76
|
-
enqueueCommand(context.worldId, 'cmd-window-set-cursor-visible', {
|
|
77
|
-
windowId: context.worldId,
|
|
78
|
-
visible: p.cursorVisible,
|
|
79
|
-
});
|
|
80
|
-
}
|
|
81
|
-
if (p.cursorGrab !== undefined) {
|
|
82
|
-
enqueueCommand(context.worldId, 'cmd-window-set-cursor-grab', {
|
|
83
|
-
windowId: context.worldId,
|
|
84
|
-
mode: p.cursorGrab,
|
|
85
|
-
});
|
|
86
|
-
}
|
|
87
|
-
if (p.icon !== undefined) {
|
|
88
|
-
enqueueCommand(context.worldId, 'cmd-window-set-icon', {
|
|
89
|
-
windowId: context.worldId,
|
|
90
|
-
bufferId: p.icon,
|
|
91
|
-
});
|
|
92
|
-
}
|
|
93
|
-
if (p.cursorIcon !== undefined) {
|
|
94
|
-
enqueueCommand(context.worldId, 'cmd-window-set-cursor-icon', {
|
|
95
|
-
windowId: context.worldId,
|
|
96
|
-
icon: p.cursorIcon,
|
|
97
|
-
});
|
|
98
|
-
}
|
|
99
|
-
intentsToRemove.push(i);
|
|
100
|
-
} else if (intent.type === 'request-attention') {
|
|
101
|
-
enqueueCommand(context.worldId, 'cmd-window-request-attention', {
|
|
102
|
-
windowId: context.worldId,
|
|
103
|
-
attentionType: intent.attentionType,
|
|
104
|
-
});
|
|
105
|
-
intentsToRemove.push(i);
|
|
106
|
-
} else if (intent.type === 'focus-window') {
|
|
107
|
-
enqueueCommand(context.worldId, 'cmd-window-focus', {
|
|
108
|
-
windowId: context.worldId,
|
|
109
|
-
});
|
|
110
|
-
intentsToRemove.push(i);
|
|
111
|
-
} else if (intent.type === 'configure-environment') {
|
|
28
|
+
if (intent.type === 'configure-environment') {
|
|
112
29
|
const config = intent.config as EnvironmentConfig;
|
|
113
30
|
const payload: EnvironmentConfig = {
|
|
114
31
|
msaa: {
|
|
@@ -124,6 +41,7 @@ export const WorldLifecycleSystem: System = (world, context) => {
|
|
|
124
41
|
skyColor: toVec3(config.skybox.skyColor),
|
|
125
42
|
cubemapTextureId: config.skybox.cubemapTextureId ?? null,
|
|
126
43
|
},
|
|
44
|
+
clearColor: toVec4(config.clearColor ?? [0, 0, 0, 0]),
|
|
127
45
|
post: {
|
|
128
46
|
filterEnabled: config.post.filterEnabled,
|
|
129
47
|
filterExposure: config.post.filterExposure,
|
|
@@ -157,12 +75,31 @@ export const WorldLifecycleSystem: System = (world, context) => {
|
|
|
157
75
|
bloomScatter: config.post.bloomScatter,
|
|
158
76
|
},
|
|
159
77
|
};
|
|
160
|
-
enqueueCommand(context.worldId, 'cmd-environment-
|
|
161
|
-
|
|
78
|
+
enqueueCommand(context.worldId, 'cmd-environment-upsert', {
|
|
79
|
+
environmentId: context.worldId,
|
|
162
80
|
config: payload,
|
|
163
81
|
});
|
|
164
|
-
|
|
82
|
+
// Keep current realm->target bindings synchronized with this environment.
|
|
83
|
+
for (const binding of world.targetLayerBindings.values()) {
|
|
84
|
+
binding.environmentId = context.worldId;
|
|
85
|
+
if (world.coreRealmId === undefined) continue;
|
|
86
|
+
enqueueCommand(context.worldId, 'cmd-target-layer-upsert', {
|
|
87
|
+
realmId: world.coreRealmId,
|
|
88
|
+
targetId: binding.targetId,
|
|
89
|
+
layout: binding.layout,
|
|
90
|
+
cameraId: binding.cameraId,
|
|
91
|
+
environmentId: context.worldId,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
165
94
|
} else if (intent.type === 'configure-shadows') {
|
|
95
|
+
let windowId = world.primaryWindowId;
|
|
96
|
+
if (windowId === undefined) {
|
|
97
|
+
for (const boundWindowId of world.targetWindowBindings.values()) {
|
|
98
|
+
windowId = boundWindowId;
|
|
99
|
+
break;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
if (windowId === undefined) continue;
|
|
166
103
|
const c = intent.config || {};
|
|
167
104
|
const config: ShadowConfig = {
|
|
168
105
|
tileResolution: c.tileResolution ?? 1024,
|
|
@@ -174,10 +111,9 @@ export const WorldLifecycleSystem: System = (world, context) => {
|
|
|
174
111
|
normalBias: c.normalBias ?? 0.01,
|
|
175
112
|
};
|
|
176
113
|
enqueueCommand(context.worldId, 'cmd-shadow-configure', {
|
|
177
|
-
windowId
|
|
114
|
+
windowId,
|
|
178
115
|
config,
|
|
179
116
|
});
|
|
180
|
-
intentsToRemove.push(i);
|
|
181
117
|
} else if (intent.type === 'send-notification') {
|
|
182
118
|
enqueueCommand(context.worldId, 'cmd-notification-send', {
|
|
183
119
|
id: `notif-${Date.now()}`,
|
|
@@ -186,13 +122,6 @@ export const WorldLifecycleSystem: System = (world, context) => {
|
|
|
186
122
|
body: intent.message,
|
|
187
123
|
timeout: 5000,
|
|
188
124
|
});
|
|
189
|
-
intentsToRemove.push(i);
|
|
190
125
|
}
|
|
191
126
|
}
|
|
192
|
-
|
|
193
|
-
for (let i = intentsToRemove.length - 1; i >= 0; i--) {
|
|
194
|
-
const idx = intentsToRemove[i];
|
|
195
|
-
if (idx !== undefined) world.pendingIntents.splice(idx, 1);
|
|
196
|
-
}
|
|
197
|
-
|
|
198
127
|
};
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import type { CmdWindowCloseArgs, CmdWindowCreateArgs } from '../../types/cmds/window';
|
|
2
|
+
import type {
|
|
3
|
+
CursorGrabMode,
|
|
4
|
+
CursorIcon,
|
|
5
|
+
UserAttentionType,
|
|
6
|
+
WindowState,
|
|
7
|
+
} from '../../types/kinds';
|
|
8
|
+
import { enqueueGlobalCommand } from '../bridge/dispatch';
|
|
9
|
+
import { requireInitialized } from '../bridge/guards';
|
|
10
|
+
import { engineState } from '../state';
|
|
11
|
+
import type { CommandId, WindowId } from '../world/types';
|
|
12
|
+
import { asCommandId, asWindowId } from '../world/types';
|
|
13
|
+
|
|
14
|
+
export interface WindowProps {
|
|
15
|
+
title?: string;
|
|
16
|
+
position?: [number, number];
|
|
17
|
+
size?: [number, number];
|
|
18
|
+
state?: WindowState;
|
|
19
|
+
resizable?: boolean;
|
|
20
|
+
decorations?: boolean;
|
|
21
|
+
cursorVisible?: boolean;
|
|
22
|
+
cursorGrab?: CursorGrabMode;
|
|
23
|
+
icon?: number;
|
|
24
|
+
cursorIcon?: CursorIcon;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface CreateWindowProps {
|
|
28
|
+
title: string;
|
|
29
|
+
size: [number, number];
|
|
30
|
+
position: [number, number];
|
|
31
|
+
canvasId?: string;
|
|
32
|
+
borderless?: boolean;
|
|
33
|
+
resizable?: boolean;
|
|
34
|
+
transparent?: boolean;
|
|
35
|
+
initialState?: WindowState;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function allocateWindowId(): WindowId {
|
|
39
|
+
while (engineState.usedWindowIds.has(engineState.nextWindowId)) {
|
|
40
|
+
engineState.nextWindowId++;
|
|
41
|
+
}
|
|
42
|
+
const id = engineState.nextWindowId++;
|
|
43
|
+
engineState.usedWindowIds.add(id);
|
|
44
|
+
return asWindowId(id);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Creates a window and returns a typed window id plus the command id.
|
|
49
|
+
*
|
|
50
|
+
* IDs are auto-generated by default to avoid collisions. You can pass `windowId`
|
|
51
|
+
* only when integrating with an existing host-level ID contract.
|
|
52
|
+
*/
|
|
53
|
+
export function createWindow(
|
|
54
|
+
props: CreateWindowProps,
|
|
55
|
+
windowId?: WindowId,
|
|
56
|
+
): { windowId: WindowId; commandId: CommandId } {
|
|
57
|
+
requireInitialized();
|
|
58
|
+
const resolvedWindowId = windowId ?? allocateWindowId();
|
|
59
|
+
if (windowId !== undefined) {
|
|
60
|
+
engineState.usedWindowIds.add(windowId as number);
|
|
61
|
+
}
|
|
62
|
+
const payload: CmdWindowCreateArgs = {
|
|
63
|
+
windowId: resolvedWindowId as number,
|
|
64
|
+
title: props.title,
|
|
65
|
+
size: props.size,
|
|
66
|
+
position: props.position,
|
|
67
|
+
canvasId: props.canvasId,
|
|
68
|
+
borderless: props.borderless ?? false,
|
|
69
|
+
resizable: props.resizable ?? true,
|
|
70
|
+
transparent: props.transparent ?? false,
|
|
71
|
+
initialState: props.initialState ?? 'windowed',
|
|
72
|
+
};
|
|
73
|
+
const commandId = enqueueGlobalCommand('cmd-window-create', payload);
|
|
74
|
+
engineState.pendingWindowCreateByCommandId.set(
|
|
75
|
+
commandId,
|
|
76
|
+
resolvedWindowId as number,
|
|
77
|
+
);
|
|
78
|
+
return { windowId: resolvedWindowId, commandId: asCommandId(commandId) };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function closeWindow(windowId: WindowId): CommandId {
|
|
82
|
+
requireInitialized();
|
|
83
|
+
const payload: CmdWindowCloseArgs = { windowId: windowId as number };
|
|
84
|
+
const commandId = enqueueGlobalCommand('cmd-window-close', payload);
|
|
85
|
+
engineState.pendingWindowCloseByCommandId.set(commandId, windowId as number);
|
|
86
|
+
return asCommandId(commandId);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Applies partial window updates.
|
|
91
|
+
*
|
|
92
|
+
* Depending on the provided fields, this may enqueue up to three commands
|
|
93
|
+
* (`measurement`, `state`, `cursor`) and returns all resulting command ids.
|
|
94
|
+
*/
|
|
95
|
+
export function updateWindow(windowId: WindowId, props: WindowProps): CommandId[] {
|
|
96
|
+
requireInitialized();
|
|
97
|
+
const commandIds: CommandId[] = [];
|
|
98
|
+
|
|
99
|
+
if (props.position !== undefined || props.size !== undefined) {
|
|
100
|
+
commandIds.push(asCommandId(
|
|
101
|
+
enqueueGlobalCommand('cmd-window-measurement', {
|
|
102
|
+
windowId: windowId as number,
|
|
103
|
+
position: props.position,
|
|
104
|
+
size: props.size,
|
|
105
|
+
}),
|
|
106
|
+
));
|
|
107
|
+
}
|
|
108
|
+
if (
|
|
109
|
+
props.title !== undefined ||
|
|
110
|
+
props.state !== undefined ||
|
|
111
|
+
props.resizable !== undefined ||
|
|
112
|
+
props.decorations !== undefined ||
|
|
113
|
+
props.icon !== undefined
|
|
114
|
+
) {
|
|
115
|
+
commandIds.push(asCommandId(
|
|
116
|
+
enqueueGlobalCommand('cmd-window-state', {
|
|
117
|
+
windowId: windowId as number,
|
|
118
|
+
title: props.title,
|
|
119
|
+
state: props.state,
|
|
120
|
+
resizable: props.resizable,
|
|
121
|
+
decorations: props.decorations,
|
|
122
|
+
iconBufferId: props.icon,
|
|
123
|
+
}),
|
|
124
|
+
));
|
|
125
|
+
}
|
|
126
|
+
if (
|
|
127
|
+
props.cursorVisible !== undefined ||
|
|
128
|
+
props.cursorGrab !== undefined ||
|
|
129
|
+
props.cursorIcon !== undefined
|
|
130
|
+
) {
|
|
131
|
+
commandIds.push(asCommandId(
|
|
132
|
+
enqueueGlobalCommand('cmd-window-cursor', {
|
|
133
|
+
windowId: windowId as number,
|
|
134
|
+
visible: props.cursorVisible,
|
|
135
|
+
mode: props.cursorGrab,
|
|
136
|
+
icon: props.cursorIcon,
|
|
137
|
+
}),
|
|
138
|
+
));
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return commandIds;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Requests user attention for a window.
|
|
146
|
+
*
|
|
147
|
+
* This does not guarantee foreground focus; behavior depends on host platform policy.
|
|
148
|
+
*/
|
|
149
|
+
export function requestAttention(
|
|
150
|
+
windowId: WindowId,
|
|
151
|
+
attentionType?: UserAttentionType,
|
|
152
|
+
): CommandId {
|
|
153
|
+
requireInitialized();
|
|
154
|
+
return asCommandId(enqueueGlobalCommand('cmd-window-state', {
|
|
155
|
+
windowId: windowId as number,
|
|
156
|
+
action: 'request-attention',
|
|
157
|
+
attentionType,
|
|
158
|
+
}));
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** Requests input focus for a window. */
|
|
162
|
+
export function focusWindow(windowId: WindowId): CommandId {
|
|
163
|
+
requireInitialized();
|
|
164
|
+
return asCommandId(enqueueGlobalCommand('cmd-window-state', {
|
|
165
|
+
windowId: windowId as number,
|
|
166
|
+
action: 'focus',
|
|
167
|
+
}));
|
|
168
|
+
}
|