@downdraft/engine 0.2.0 → 0.3.1
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/app/src/renderer/bootstrap.ts +0 -6
- package/app/src/renderer/feature-log.ts +1 -9
- package/app/src/renderer/mcp-harness.ts +8 -11
- package/app/src/renderer/standard-automation-tools.ts +8 -145
- package/core/src/index.ts +2 -2
- package/core/src/module/host.ts +3 -6
- package/core/src/module/index.ts +1 -1
- package/core/src/module/module.ts +4 -47
- package/core/src/physics/interface.ts +5 -0
- package/core/src/platform/render-surface.ts +13 -0
- package/core/src/platform/rpc.ts +4 -1
- package/core/src/render/canvas-resize-watcher.ts +1 -10
- package/core/src/render/game-renderer.ts +164 -79
- package/core/src/render/index.ts +0 -2
- package/core/src/render/input-manager.ts +7 -7
- package/core/src/render/renderer-module-host.ts +2 -4
- package/core/src/sab/grid-buffer.ts +55 -2
- package/core/src/sab/record-buffer.ts +48 -5
- package/core/src/sab/sim-channel.ts +181 -17
- package/core/src/sab/slot-buffer.ts +55 -1
- package/core/src/sab/types.ts +11 -0
- package/core/src/telemetry/gpu-resource-tracker.ts +24 -0
- package/core/src/ui-font-scale.ts +5 -38
- package/core/src/util/index.ts +1 -0
- package/core/src/util/session-timer.ts +23 -0
- package/core/src/worker/base-worker-host.ts +2 -1
- package/core/src/worker/buffer-sync.ts +20 -13
- package/core/src/worker/rpc.ts +119 -5
- package/core/src/worker/sim-worker-base.ts +5 -2
- package/core/src/worker/task-pool.ts +4 -0
- package/dist/.dd-manifest.json +893 -908
- package/dist/app/src/renderer/bootstrap.js +0 -4
- package/dist/app/src/renderer/feature-log.js +1 -10
- package/dist/app/src/renderer/mcp-harness.js +8 -11
- package/dist/app/src/renderer/standard-automation-tools.js +6 -138
- package/dist/core/src/index.js +2 -2
- package/dist/core/src/module/host.js +3 -6
- package/dist/core/src/platform/rpc.js +2 -1
- package/dist/core/src/render/canvas-resize-watcher.js +1 -10
- package/dist/core/src/render/game-renderer.js +166 -74
- package/dist/core/src/render/index.js +0 -1
- package/dist/core/src/render/input-manager.js +7 -7
- package/dist/core/src/render/renderer-module-host.js +2 -4
- package/dist/core/src/sab/grid-buffer.js +53 -1
- package/dist/core/src/sab/record-buffer.js +49 -5
- package/dist/core/src/sab/sim-channel.js +173 -17
- package/dist/core/src/sab/slot-buffer.js +54 -1
- package/dist/core/src/telemetry/gpu-resource-tracker.js +26 -0
- package/dist/core/src/ui-font-scale.js +5 -38
- package/dist/core/src/util/index.js +1 -0
- package/dist/core/src/util/session-timer.js +22 -0
- package/dist/core/src/worker/base-worker-host.js +2 -1
- package/dist/core/src/worker/buffer-sync.js +16 -9
- package/dist/core/src/worker/rpc.js +105 -8
- package/dist/core/src/worker/sim-worker-base.js +5 -2
- package/dist/core/src/worker/task-pool.js +5 -0
- package/dist/libraries/blitz-ui/src/native-module.js +8 -1
- package/dist/libraries/devtools/src/backend.js +35 -9
- package/dist/libraries/devtools/src/collectors.js +73 -22
- package/dist/libraries/devtools/src/native-providers.js +0 -2
- package/dist/libraries/entities/src/model-renderer.js +87 -27
- package/dist/libraries/entities/src/shaders/model.wgsl +13 -4
- package/dist/libraries/networking/src/index.js +1 -0
- package/dist/libraries/networking/src/ws-server.js +387 -0
- package/dist/libraries/physics-rapier/src/api.js +15 -0
- package/dist/libraries/physics-rapier/src/backend.js +15 -0
- package/dist/libraries/physics-rapier/src/rapier-backend.js +5 -0
- package/dist/libraries/postfx/src/post-process-stack.js +1 -3
- package/dist/libraries/sand/src/materials.js +2 -32
- package/dist/libraries/sand/src/sand-world.js +0 -3
- package/dist/modules/devtools/src/api.js +8 -23
- package/dist/modules/devtools/src/blitz/fonts.js +75 -0
- package/dist/modules/devtools/src/blitz/host.js +124 -30
- package/dist/modules/devtools/src/blitz/panels/console.js +19 -7
- package/dist/modules/devtools/src/blitz/panels/elements.js +217 -28
- package/dist/modules/devtools/src/blitz/panels/gpu.js +5 -2
- package/dist/modules/devtools/src/blitz/panels/performance.js +26 -12
- package/dist/modules/devtools/src/blitz/panels/snapshot.js +31 -8
- package/dist/modules/devtools/src/blitz/panels/types.js +2 -1
- package/dist/modules/devtools/src/blitz/theme.js +26 -4
- package/dist/modules/devtools/src/data-bridge.js +0 -11
- package/dist/modules/devtools/src/index.js +0 -6
- package/dist/modules/devtools/src/init.js +8 -42
- package/dist/modules/devtools/src/scene-inspector.js +1 -4
- package/dist/modules/devtools/src/worker-sync.js +3 -11
- package/dist/modules/html-ui/src/doc-backend.js +52 -3
- package/dist/modules/html-ui/src/host.js +6 -3
- package/dist/test/src/vision-test.js +1 -3
- package/libraries/blitz-ui/native/Cargo.toml +6 -0
- package/libraries/blitz-ui/native-osr/Cargo.toml +3 -1
- package/libraries/blitz-ui/src/native-module.ts +8 -1
- package/libraries/devtools/src/backend.ts +38 -12
- package/libraries/devtools/src/collectors.ts +75 -22
- package/libraries/devtools/src/native-providers.ts +0 -2
- package/libraries/entities/src/model-renderer.ts +88 -27
- package/libraries/entities/src/shaders/model.wgsl +13 -4
- package/libraries/networking/src/index.ts +4 -0
- package/libraries/networking/src/ws-server.ts +397 -0
- package/libraries/physics-rapier/src/api.ts +15 -0
- package/libraries/physics-rapier/src/backend.ts +15 -0
- package/libraries/physics-rapier/src/rapier-backend.ts +6 -0
- package/libraries/postfx/src/post-process-stack.ts +1 -2
- package/libraries/sand/src/materials.ts +2 -32
- package/libraries/sand/src/sand-world.ts +0 -4
- package/modules/devtools/README.md +1 -3
- package/modules/devtools/src/api.ts +10 -34
- package/modules/devtools/src/blitz/fonts.ts +78 -0
- package/modules/devtools/src/blitz/host.ts +107 -31
- package/modules/devtools/src/blitz/panels/console.ts +20 -8
- package/modules/devtools/src/blitz/panels/elements.ts +212 -31
- package/modules/devtools/src/blitz/panels/gpu.ts +4 -2
- package/modules/devtools/src/blitz/panels/performance.ts +24 -12
- package/modules/devtools/src/blitz/panels/snapshot.ts +30 -8
- package/modules/devtools/src/blitz/panels/types.ts +2 -1
- package/modules/devtools/src/blitz/theme.ts +26 -4
- package/modules/devtools/src/data-bridge.ts +0 -16
- package/modules/devtools/src/index.ts +1 -11
- package/modules/devtools/src/init.ts +8 -51
- package/modules/devtools/src/scene-inspector.ts +2 -6
- package/modules/devtools/src/types.ts +5 -90
- package/modules/devtools/src/worker-sync.ts +3 -13
- package/modules/html-ui/src/doc-backend.ts +67 -4
- package/modules/html-ui/src/host.ts +11 -3
- package/modules/html-ui/src/protocol.ts +1 -1
- package/package.json +4 -5
- package/test/src/vision-test.ts +2 -4
- package/core/src/render/sab-canvas-overlay.ts +0 -122
- package/dist/core/src/render/sab-canvas-overlay.js +0 -83
- package/dist/modules/devtools/src/debug-overlay.js +0 -181
- package/dist/modules/devtools/src/doctor-panel.js +0 -163
- package/dist/modules/devtools/src/label-overlay.js +0 -129
- package/dist/modules/devtools/src/material-stats-panel.js +0 -171
- package/dist/modules/devtools/src/sim-stats-panel.js +0 -250
- package/modules/devtools/src/debug-overlay.ts +0 -238
- package/modules/devtools/src/doctor-panel.ts +0 -182
- package/modules/devtools/src/label-overlay.ts +0 -154
- package/modules/devtools/src/material-stats-panel.ts +0 -188
- package/modules/devtools/src/sim-stats-panel.ts +0 -272
|
@@ -5,10 +5,10 @@
|
|
|
5
5
|
// 1. Creating an IDevToolsDataRenderer adapter
|
|
6
6
|
// 2. Instantiating DevToolsDataBridge (or BaseSceneInspector)
|
|
7
7
|
// 3. Calling init()
|
|
8
|
-
// 4. Wiring sim stats provider,
|
|
8
|
+
// 4. Wiring sim stats provider, debug overlay provider, etc.
|
|
9
9
|
//
|
|
10
|
-
// Also merges globally-registered
|
|
11
|
-
// singleton (registered by plugins via ctx.devtools.
|
|
10
|
+
// Also merges globally-registered feeds/commands from the `devtools`
|
|
11
|
+
// singleton (registered by plugins via ctx.devtools.registerDataFeed() etc.)
|
|
12
12
|
// and from worker manifests (via syncWorkerManifests).
|
|
13
13
|
//
|
|
14
14
|
// Returns the bridge instance for games that need post-init calls
|
|
@@ -22,7 +22,7 @@ import { BaseSceneInspector } from "./scene-inspector.js";
|
|
|
22
22
|
import { syncWorkerManifests } from "./worker-sync.js";
|
|
23
23
|
/**
|
|
24
24
|
* Initialize DevTools with one call. Creates the bridge, wires all providers,
|
|
25
|
-
|
|
25
|
+
* merges globally-registered feeds/commands, syncs worker manifests,
|
|
26
26
|
* and exposes the API on window.__sceneInspector.
|
|
27
27
|
*
|
|
28
28
|
* Returns the bridge instance.
|
|
@@ -34,19 +34,7 @@ export async function initDevTools(renderer, options = {}) {
|
|
|
34
34
|
const useSceneInspector = options.sceneInspector
|
|
35
35
|
|| !!options.bridgeClass
|
|
36
36
|
|| isSceneRenderer(renderer);
|
|
37
|
-
//
|
|
38
|
-
// (these merge with plugin-registered ones)
|
|
39
|
-
if (options.panels) {
|
|
40
|
-
options.panels.forEach((panel) => {
|
|
41
|
-
devtools.registerPanel(panel);
|
|
42
|
-
});
|
|
43
|
-
}
|
|
44
|
-
if (options.overlayToggles) {
|
|
45
|
-
options.overlayToggles.forEach((toggle) => {
|
|
46
|
-
devtools.registerOverlayToggle(toggle);
|
|
47
|
-
});
|
|
48
|
-
}
|
|
49
|
-
// 4. Sync worker manifests (fetches panels/feeds/commands from sim workers)
|
|
37
|
+
// 4. Sync worker manifests (fetches feeds/commands from sim workers)
|
|
50
38
|
if (options.workerHosts && options.workerHosts.length > 0) {
|
|
51
39
|
await syncWorkerManifests(options.workerHosts);
|
|
52
40
|
}
|
|
@@ -85,10 +73,8 @@ export async function initDevTools(renderer, options = {}) {
|
|
|
85
73
|
if (options.performanceMetricsProvider) {
|
|
86
74
|
wirePerformanceMetricsProvider(bridge, options.performanceMetricsProvider);
|
|
87
75
|
}
|
|
88
|
-
// 7. Merge globally-registered
|
|
89
|
-
//
|
|
90
|
-
// game-declared ones; we augment the __sceneInspector API with
|
|
91
|
-
// plugin-registered data feeds and commands.
|
|
76
|
+
// 7. Merge globally-registered feeds/commands/SAB stats into the bridge —
|
|
77
|
+
// augments the __sceneInspector API with plugin-registered methods.
|
|
92
78
|
mergeGlobalRegistrations(bridge);
|
|
93
79
|
// 8. Initialize the profiling system (if enabled)
|
|
94
80
|
if (options.profiling) {
|
|
@@ -134,35 +120,15 @@ function wirePerformanceMetricsProvider(bridge, provider) {
|
|
|
134
120
|
bridge.getPerformanceMetricsProvider = () => provider;
|
|
135
121
|
}
|
|
136
122
|
/**
|
|
137
|
-
* Merge globally-registered
|
|
123
|
+
* Merge globally-registered data feeds and commands from the `devtools`
|
|
138
124
|
* singleton into the bridge's __sceneInspector API.
|
|
139
125
|
*
|
|
140
|
-
* - Panels: merged into getPanelExtensions() (game panels + plugin panels)
|
|
141
126
|
* - Data feeds: added as methods on __sceneInspector (called synchronously)
|
|
142
127
|
* - Commands: added as methods on __sceneInspector (forwarded to worker or called directly)
|
|
143
128
|
* - SAB stats: added as methods on __sceneInspector (read from SAB)
|
|
144
129
|
*/
|
|
145
130
|
function mergeGlobalRegistrations(bridge) {
|
|
146
131
|
const manifest = devtools.getManifest();
|
|
147
|
-
// Augment getPanelExtensions to merge global registry panels
|
|
148
|
-
const originalGetPanels = bridge.getPanelExtensions?.bind(bridge);
|
|
149
|
-
bridge.getPanelExtensions = () => {
|
|
150
|
-
const gamePanels = originalGetPanels?.() ?? [];
|
|
151
|
-
// Merge: game panels take precedence on id collisions
|
|
152
|
-
const merged = new Map();
|
|
153
|
-
manifest.panels.forEach((p) => { merged.set(p.id, p); ; });
|
|
154
|
-
gamePanels.forEach((p) => { merged.set(p.id, p); ; });
|
|
155
|
-
return Array.from(merged.values()).sort((a, b) => (a.order ?? 100) - (b.order ?? 100));
|
|
156
|
-
};
|
|
157
|
-
// Augment getOverlayToggles to merge global registry toggles
|
|
158
|
-
const originalGetToggles = bridge.getOverlayToggles?.bind(bridge);
|
|
159
|
-
bridge.getOverlayToggles = () => {
|
|
160
|
-
const gameToggles = originalGetToggles?.() ?? [];
|
|
161
|
-
const merged = new Map();
|
|
162
|
-
manifest.toggles.forEach((t) => { merged.set(t.id, t); ; });
|
|
163
|
-
gameToggles.forEach((t) => { merged.set(t.id, t); ; });
|
|
164
|
-
return Array.from(merged.values());
|
|
165
|
-
};
|
|
166
132
|
// Add data feed methods to the __sceneInspector API
|
|
167
133
|
// These are called synchronously by the panel via callInspector()
|
|
168
134
|
const api = window.__sceneInspector;
|
|
@@ -27,7 +27,6 @@ export class BaseSceneInspector extends DevToolsDataBridge {
|
|
|
27
27
|
getDebugOverlayProvider() {
|
|
28
28
|
return null;
|
|
29
29
|
}
|
|
30
|
-
// getPanelExtensions() and getOverlayToggles() inherited from DevToolsDataBridge.
|
|
31
30
|
// --- Init ---
|
|
32
31
|
init(renderer) {
|
|
33
32
|
super.init(renderer);
|
|
@@ -510,9 +509,7 @@ export class BaseSceneInspector extends DevToolsDataBridge {
|
|
|
510
509
|
const cosY = Math.cos(angleY), sinY = Math.sin(angleY);
|
|
511
510
|
const cosX = Math.cos(angleX), sinX = Math.sin(angleX);
|
|
512
511
|
const THUMB_SIZE = 192;
|
|
513
|
-
const canvas =
|
|
514
|
-
canvas.width = THUMB_SIZE;
|
|
515
|
-
canvas.height = THUMB_SIZE;
|
|
512
|
+
const canvas = new OffscreenCanvas(THUMB_SIZE, THUMB_SIZE);
|
|
516
513
|
const ctx = canvas.getContext("2d");
|
|
517
514
|
if (!ctx)
|
|
518
515
|
return null;
|
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
// ============================================================================
|
|
2
2
|
// Renderer-side worker manifest sync — fetches the devtools manifest from
|
|
3
|
-
// one or more sim workers via worker RPC, then wires the worker-registered
|
|
4
|
-
// data feeds
|
|
3
|
+
// one or more sim workers via worker RPC, then wires the worker-registered
|
|
4
|
+
// data feeds and commands into the renderer-side __sceneInspector API.
|
|
5
5
|
//
|
|
6
6
|
// Data feeds from workers are read from the devtools SAB (zero-copy).
|
|
7
7
|
// Commands are forwarded via worker RPC.
|
|
8
|
-
// Panel declarations are merged into the global registry.
|
|
9
8
|
// ============================================================================
|
|
10
9
|
import { createLogger } from "@downdraft/engine/util/logger";
|
|
11
|
-
import { _devtoolsImpl
|
|
10
|
+
import { _devtoolsImpl } from "./api.js";
|
|
12
11
|
const log = createLogger("info");
|
|
13
12
|
/**
|
|
14
13
|
* Sync manifests from one or more workers into the renderer-side devtools
|
|
@@ -54,13 +53,6 @@ async function syncOneWorker(entry) {
|
|
|
54
53
|
// Worker may not have a devtools SAB (older workers)
|
|
55
54
|
}
|
|
56
55
|
}
|
|
57
|
-
// Register panels (namespaced by prefix to avoid collisions)
|
|
58
|
-
manifest.panels.forEach((panel) => {
|
|
59
|
-
const namespaced = prefix
|
|
60
|
-
? { ...panel, id: `${prefix}:${panel.id}` }
|
|
61
|
-
: panel;
|
|
62
|
-
devtools.registerPanel(namespaced);
|
|
63
|
-
});
|
|
64
56
|
// Register data feed readers — these read from the SAB synchronously
|
|
65
57
|
// We register them as main-realm data feeds that read from the worker SAB
|
|
66
58
|
if (sab) {
|
|
@@ -147,9 +147,32 @@ export function createDocCore(emit, gpuHooks) {
|
|
|
147
147
|
s.statsLast = now;
|
|
148
148
|
}
|
|
149
149
|
}
|
|
150
|
+
// Smooth-wheel ease: fraction of the remainder applied per elapsed ms.
|
|
151
|
+
// τ≈45ms → ~30% per 16ms tick, ~95% drained in ~135ms — snappy but
|
|
152
|
+
// animated. A 1/MAX_TICKS floor bounds any backlog at ~320ms of drain —
|
|
153
|
+
// a fast wheel can't make scroll trail input indefinitely. Dispatching
|
|
154
|
+
// real wheel events keeps Blitz's hit-testing, scroll chaining, scrollbar
|
|
155
|
+
// reveal, and scroll DOM events intact.
|
|
156
|
+
const WHEEL_TAU_MS = 45;
|
|
157
|
+
const WHEEL_MAX_TICKS = 20;
|
|
158
|
+
function stepWheel(s, now) {
|
|
159
|
+
const dt = Math.min(50, Math.max(1, now - s.wheelT));
|
|
160
|
+
s.wheelT = now;
|
|
161
|
+
const f = Math.max(1 - Math.exp(-dt / WHEEL_TAU_MS), 1 / WHEEL_MAX_TICKS);
|
|
162
|
+
// Snap the tail: a step below ~¾px/axis applies the whole residue instead
|
|
163
|
+
// of asymptoting through invisible sub-pixel repaints.
|
|
164
|
+
const dx = Math.abs(s.wheelRx) * f < 0.75 ? s.wheelRx : s.wheelRx * f;
|
|
165
|
+
const dy = Math.abs(s.wheelRy) * f < 0.75 ? s.wheelRy : s.wheelRy * f;
|
|
166
|
+
s.wheelRx -= dx;
|
|
167
|
+
s.wheelRy -= dy;
|
|
168
|
+
const k = s.scale;
|
|
169
|
+
s.doc.wheel(s.wheelX * k, s.wheelY * k, dx * k, dy * k, s.wheelMods);
|
|
170
|
+
}
|
|
150
171
|
function pump() {
|
|
151
172
|
const now = performance.now();
|
|
152
173
|
for (const [id, s] of docs.entries()) {
|
|
174
|
+
if (s.smoothWheel && (s.wheelRx !== 0 || s.wheelRy !== 0))
|
|
175
|
+
stepWheel(s, now);
|
|
153
176
|
const events = s.doc.pollEvents();
|
|
154
177
|
if (events.length)
|
|
155
178
|
emit({ type: "events", id, events });
|
|
@@ -233,6 +256,8 @@ export function createDocCore(emit, gpuHooks) {
|
|
|
233
256
|
doc, scale: m.scale, cssW: m.cssW, cssH: m.cssH,
|
|
234
257
|
maxFps: m.maxFps ?? 0, lastFrame: -1e9,
|
|
235
258
|
sab: null, sabU32: null, sabDisabled: false, gpuTex: null,
|
|
259
|
+
smoothWheel: m.smoothWheel === true,
|
|
260
|
+
wheelRx: 0, wheelRy: 0, wheelX: 0, wheelY: 0, wheelT: 0,
|
|
236
261
|
stats: freshStats(), statsLast: 0,
|
|
237
262
|
};
|
|
238
263
|
docs.set(m.id, s);
|
|
@@ -245,11 +270,18 @@ export function createDocCore(emit, gpuHooks) {
|
|
|
245
270
|
s.maxFps = m.maxFps;
|
|
246
271
|
break;
|
|
247
272
|
}
|
|
248
|
-
case "setHtml":
|
|
273
|
+
case "setHtml": {
|
|
249
274
|
if (/float\s*:/.test(m.html))
|
|
250
275
|
emit({ type: "error", id: m.id, message: "CSS float hangs Blitz layout — use flex instead" });
|
|
251
|
-
docs.get(m.id)
|
|
276
|
+
const s = docs.get(m.id);
|
|
277
|
+
if (!s)
|
|
278
|
+
break;
|
|
279
|
+
// Drop any in-flight smooth-wheel drain — its remainder belongs to
|
|
280
|
+
// the document being replaced.
|
|
281
|
+
s.wheelRx = s.wheelRy = 0;
|
|
282
|
+
s.doc.setHtml(m.html);
|
|
252
283
|
break;
|
|
284
|
+
}
|
|
253
285
|
case "resize": {
|
|
254
286
|
const s = docs.get(m.id);
|
|
255
287
|
if (s) {
|
|
@@ -374,13 +406,30 @@ export function createDocCore(emit, gpuHooks) {
|
|
|
374
406
|
s.doc.pointerMove(msg.x * k, msg.y * k, msg.mods);
|
|
375
407
|
break;
|
|
376
408
|
case "down":
|
|
409
|
+
// A press interrupts any in-flight wheel scroll (mirrors
|
|
410
|
+
// Blitz's interrupt_animation semantics for user input).
|
|
411
|
+
s.wheelRx = 0;
|
|
412
|
+
s.wheelRy = 0;
|
|
377
413
|
s.doc.pointerDown(msg.x * k, msg.y * k, msg.button, msg.mods);
|
|
378
414
|
break;
|
|
379
415
|
case "up":
|
|
380
416
|
s.doc.pointerUp(msg.x * k, msg.y * k, msg.button, msg.mods);
|
|
381
417
|
break;
|
|
382
418
|
case "wheel":
|
|
383
|
-
s.
|
|
419
|
+
if (s.smoothWheel) {
|
|
420
|
+
// A fresh gesture back-dates wheelT so the first pump tick
|
|
421
|
+
// applies a full ~16ms step immediately — no dead frame.
|
|
422
|
+
if (s.wheelRx === 0 && s.wheelRy === 0)
|
|
423
|
+
s.wheelT = performance.now() - 16;
|
|
424
|
+
s.wheelRx += msg.deltaX;
|
|
425
|
+
s.wheelRy += msg.deltaY;
|
|
426
|
+
s.wheelX = msg.x;
|
|
427
|
+
s.wheelY = msg.y;
|
|
428
|
+
s.wheelMods = msg.mods;
|
|
429
|
+
}
|
|
430
|
+
else {
|
|
431
|
+
s.doc.wheel(msg.x * k, msg.y * k, msg.deltaX * k, msg.deltaY * k, msg.mods);
|
|
432
|
+
}
|
|
384
433
|
break;
|
|
385
434
|
case "key":
|
|
386
435
|
s.doc.key(msg.down, msg.key, msg.code, msg.text, msg.mods);
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
// ============================================================================
|
|
8
8
|
import { getHostCapabilities, getNativeHost } from "@downdraft/engine/platform/runtime";
|
|
9
9
|
import { createLogger } from "@downdraft/engine/util/logger";
|
|
10
|
+
import { untrackedTimeout } from "@downdraft/engine/util/session-timer";
|
|
10
11
|
import { PanelBlitPass } from "./composite.js";
|
|
11
12
|
import { createLocalBackend, createWorkerBackend } from "./doc-backend.js";
|
|
12
13
|
const log = createLogger("info");
|
|
@@ -136,7 +137,7 @@ export class HtmlUiHost {
|
|
|
136
137
|
this.order.push(id);
|
|
137
138
|
// Doc viewport scale = raster px per CSS px = scale * zoom; layout size
|
|
138
139
|
// is rect/zoom CSS px so the raster still covers rect*scale texture px.
|
|
139
|
-
this.send({ type: "create", id, cssW: spec.rect.w / zoom, cssH: spec.docH ?? spec.rect.h / zoom, scale: scale * zoom, html: spec.html, maxFps: spec.maxFps });
|
|
140
|
+
this.send({ type: "create", id, cssW: spec.rect.w / zoom, cssH: spec.docH ?? spec.rect.h / zoom, scale: scale * zoom, html: spec.html, maxFps: spec.maxFps, smoothWheel: spec.smoothWheel });
|
|
140
141
|
// GPU-direct: create the panel texture eagerly so the worker's texBind
|
|
141
142
|
// lands before its first upload — the host knows raster dims at mount.
|
|
142
143
|
if (this.gpuReady)
|
|
@@ -301,6 +302,8 @@ export class HtmlUiHost {
|
|
|
301
302
|
/** Doc-backend capabilities (populated by the `ready` handshake —
|
|
302
303
|
* `incrementalDom` is false until then, and forever on old cdylibs). */
|
|
303
304
|
get docCaps() { return this.caps; }
|
|
305
|
+
/** Which doc backend is live — "worker" (raster off-thread) or "local". */
|
|
306
|
+
get backendKind() { return this.backend.kind; }
|
|
304
307
|
/** Compositor surface for GameRenderer's end-of-frame UI pass. */
|
|
305
308
|
get compositor() {
|
|
306
309
|
return {
|
|
@@ -349,7 +352,7 @@ export class HtmlUiHost {
|
|
|
349
352
|
this.terminating = true;
|
|
350
353
|
this.send({ type: "gpuDetach" });
|
|
351
354
|
const backend = this.backend;
|
|
352
|
-
|
|
355
|
+
untrackedTimeout(() => {
|
|
353
356
|
if (!this.gpuDetachAcked)
|
|
354
357
|
backend.dispose();
|
|
355
358
|
this.destroyRetired();
|
|
@@ -775,7 +778,7 @@ export class HtmlUiHost {
|
|
|
775
778
|
|| (p.postedBindPtr !== null && BigInt(ptr) === p.postedBindPtr));
|
|
776
779
|
if (borrowed) {
|
|
777
780
|
const key = BigInt(ptr);
|
|
778
|
-
const timer =
|
|
781
|
+
const timer = untrackedTimeout(() => {
|
|
779
782
|
if (this.retiredTex.delete(key))
|
|
780
783
|
t.destroy();
|
|
781
784
|
}, 1000);
|
|
@@ -155,9 +155,7 @@ export class VisionTest {
|
|
|
155
155
|
}
|
|
156
156
|
async screenshotToDataURL(texture, width, height) {
|
|
157
157
|
const data = await this.captureTexture(texture, width, height);
|
|
158
|
-
const canvas =
|
|
159
|
-
canvas.width = width;
|
|
160
|
-
canvas.height = height;
|
|
158
|
+
const canvas = new OffscreenCanvas(width, height);
|
|
161
159
|
const ctx = canvas.getContext("2d");
|
|
162
160
|
const imageData = ctx.createImageData(width, height);
|
|
163
161
|
imageData.data.set(data);
|
|
@@ -31,3 +31,9 @@ web-time = "1"
|
|
|
31
31
|
# Workaround copied from blitz examples: disable unicode URL support
|
|
32
32
|
# (see https://github.com/hsivonen/idna_adapter)
|
|
33
33
|
idna_adapter = "=1.0.0"
|
|
34
|
+
|
|
35
|
+
# Enable the vendored glifo's FreeType mask path (see [patch.crates-io] in the
|
|
36
|
+
# workspace root). Feature unification turns it on inside vello_cpu's copy.
|
|
37
|
+
# Target-gated: freetype-sys compiles bundled C, which wasm32 can't build.
|
|
38
|
+
[target.'cfg(not(target_family = "wasm"))'.dependencies]
|
|
39
|
+
glifo = { version = "0.2", features = ["freetype"] }
|
|
@@ -13,7 +13,9 @@ crate-type = ["cdylib"]
|
|
|
13
13
|
blitz-html = { git = "https://github.com/DioxusLabs/blitz", rev = "efef6519fe6520caf66dc82df557580eecbf652b" }
|
|
14
14
|
blitz-dom = { git = "https://github.com/DioxusLabs/blitz", rev = "efef6519fe6520caf66dc82df557580eecbf652b", default-features = false, features = ["woff"] }
|
|
15
15
|
blitz-traits = { git = "https://github.com/DioxusLabs/blitz", rev = "efef6519fe6520caf66dc82df557580eecbf652b" }
|
|
16
|
-
|
|
16
|
+
# scrollbars: painted overlay thumbs on scroll containers + thumb drag/hover
|
|
17
|
+
# hit-testing (pulls blitz-dom/scrollbars transitively).
|
|
18
|
+
blitz-paint = { git = "https://github.com/DioxusLabs/blitz", rev = "efef6519fe6520caf66dc82df557580eecbf652b", features = ["custom-widget", "scrollbars"] }
|
|
17
19
|
# Enable the vendored glifo's FreeType mask path (see [patch.crates-io] in the
|
|
18
20
|
# workspace root). Feature unification turns it on inside vello_cpu's copy.
|
|
19
21
|
glifo = { version = "0.2", features = ["freetype"] }
|
|
@@ -169,7 +169,14 @@ export function createBlitzUiNativeModule<S>(options: BlitzUiNativeOptions<S>):
|
|
|
169
169
|
const device = ctx.getDevice();
|
|
170
170
|
const view = ensureTexture(device);
|
|
171
171
|
const context = ctx.getSurface().getContext("webgpu");
|
|
172
|
-
|
|
172
|
+
// Join the in-flight frame, never start one: on a frame the
|
|
173
|
+
// game skipped (dirty-tracking), acquiring the surface here
|
|
174
|
+
// would present the UI blit over a blank swapchain image.
|
|
175
|
+
const peek = (context as { peekCurrentTexture?: () => GPUTexture | null } | null)
|
|
176
|
+
?.peekCurrentTexture;
|
|
177
|
+
const targetTex = typeof peek === "function"
|
|
178
|
+
? peek.call(context)
|
|
179
|
+
: context?.getCurrentTexture() ?? null;
|
|
173
180
|
const target = targetTex?.createView();
|
|
174
181
|
if (!view || !target || !targetTex) return;
|
|
175
182
|
|
|
@@ -77,6 +77,12 @@ export class DevtoolsBackend {
|
|
|
77
77
|
private providers = new Map<number, PanelProvider>();
|
|
78
78
|
private commandHandlers = new Map<number, PanelCommandHandler>();
|
|
79
79
|
private globalCommandHandler: PanelCommandHandler | null = null;
|
|
80
|
+
/** Dynamically-allocated slots for provider names outside the fixed PANEL
|
|
81
|
+
* table — panelSlot() alone collapsed every unknown name onto slot 0
|
|
82
|
+
* ("console"), so a second game provider silently replaced the first. */
|
|
83
|
+
private dynSlots = new Map<string, number>();
|
|
84
|
+
private dynSlotNames = new Map<number, string>();
|
|
85
|
+
private nextDynSlot = 100;
|
|
80
86
|
private lastProviderPush = new Map<number, number>();
|
|
81
87
|
providerIntervalMs = 1500;
|
|
82
88
|
/** When non-null, the cadence loop only auto-refreshes this provider
|
|
@@ -237,37 +243,57 @@ export class DevtoolsBackend {
|
|
|
237
243
|
|
|
238
244
|
// ── Providers / commands ──
|
|
239
245
|
|
|
240
|
-
|
|
241
|
-
|
|
246
|
+
/** Resolve a panel id to its slot. Names outside the fixed PANEL table
|
|
247
|
+
* get a stable dynamically-allocated slot so bespoke game providers can
|
|
248
|
+
* coexist instead of colliding on slot 0. */
|
|
249
|
+
private slotFor(panel: PanelName | number | string): number {
|
|
250
|
+
if (typeof panel === "number") return panel;
|
|
251
|
+
const fixed = PANEL[panel as PanelName];
|
|
252
|
+
if (fixed !== undefined) return fixed;
|
|
253
|
+
let s = this.dynSlots.get(panel);
|
|
254
|
+
if (s === undefined) {
|
|
255
|
+
s = this.nextDynSlot++;
|
|
256
|
+
this.dynSlots.set(panel, s);
|
|
257
|
+
this.dynSlotNames.set(s, panel);
|
|
258
|
+
}
|
|
259
|
+
return s;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
private nameFor(slot: number): string {
|
|
263
|
+
return this.dynSlotNames.get(slot) ?? slotName(slot);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
registerProvider(panel: PanelName | number | string, collect: PanelProvider): void {
|
|
267
|
+
this.providers.set(this.slotFor(panel), collect);
|
|
242
268
|
}
|
|
243
269
|
|
|
244
|
-
registerCommandHandler(panel: PanelName | number |
|
|
270
|
+
registerCommandHandler(panel: PanelName | number | string, handler: PanelCommandHandler): void {
|
|
245
271
|
if (panel === "*") {
|
|
246
272
|
this.globalCommandHandler = handler;
|
|
247
273
|
} else {
|
|
248
|
-
this.commandHandlers.set(
|
|
274
|
+
this.commandHandlers.set(this.slotFor(panel), handler);
|
|
249
275
|
}
|
|
250
276
|
}
|
|
251
277
|
|
|
252
278
|
/** Registered provider slots — the dynamic tab list for frontends. */
|
|
253
279
|
providerSlots(): { slot: number; name: string }[] {
|
|
254
|
-
return [...this.providers.keys()].map((slot) => ({ slot, name:
|
|
280
|
+
return [...this.providers.keys()].map((slot) => ({ slot, name: this.nameFor(slot) }));
|
|
255
281
|
}
|
|
256
282
|
|
|
257
283
|
/** Collect + emit one provider's snapshot immediately. */
|
|
258
|
-
refreshPanel(panel: PanelName | number): void {
|
|
284
|
+
refreshPanel(panel: PanelName | number | string): void {
|
|
259
285
|
const trace: number[] = [performance.now()];
|
|
260
|
-
const slot =
|
|
286
|
+
const slot = this.slotFor(panel);
|
|
261
287
|
trace.push(performance.now());
|
|
262
288
|
const collect = this.providers.get(slot);
|
|
263
289
|
if (!collect) return;
|
|
264
290
|
const push = (snap: PanelSnapshot | null | undefined) => {
|
|
265
|
-
if (snap) this.emit("snapshot", { panel:
|
|
291
|
+
if (snap) this.emit("snapshot", { panel: this.nameFor(slot), slot, snap });
|
|
266
292
|
this.lastProviderPush.set(slot, performance.now());
|
|
267
293
|
};
|
|
268
294
|
const fail = (err: unknown) => {
|
|
269
295
|
this.emit("snapshot", {
|
|
270
|
-
panel:
|
|
296
|
+
panel: this.nameFor(slot), slot,
|
|
271
297
|
snap: { status: "error", statusMsg: String(err), sections: [] },
|
|
272
298
|
});
|
|
273
299
|
this.lastProviderPush.set(slot, performance.now());
|
|
@@ -288,7 +314,7 @@ export class DevtoolsBackend {
|
|
|
288
314
|
const total = trace[4]! - trace[0]!;
|
|
289
315
|
if (total > 10) {
|
|
290
316
|
const seg = `pre=${(trace[2]! - trace[0]!).toFixed(1)} collect=${(trace[3]! - trace[2]!).toFixed(1)} emit=${(trace[4]! - trace[3]!).toFixed(1)}`;
|
|
291
|
-
this.diagWarn(`slow provider ${
|
|
317
|
+
this.diagWarn(`slow provider ${this.nameFor(slot)}: ${total.toFixed(1)}ms [${seg}]`);
|
|
292
318
|
}
|
|
293
319
|
}
|
|
294
320
|
|
|
@@ -431,7 +457,7 @@ export class DevtoolsBackend {
|
|
|
431
457
|
return collectThreads(this.ctx, [...this.threadEvals.keys()]);
|
|
432
458
|
case "snapshot": {
|
|
433
459
|
const panel = params.panel;
|
|
434
|
-
const slot = typeof panel === "number" ? panel :
|
|
460
|
+
const slot = typeof panel === "number" ? panel : this.slotFor(String(panel));
|
|
435
461
|
const collect = this.providers.get(slot);
|
|
436
462
|
if (!collect) return { status: "unsupported", statusMsg: `no provider for ${String(panel)}`, sections: [] };
|
|
437
463
|
try {
|
|
@@ -443,7 +469,7 @@ export class DevtoolsBackend {
|
|
|
443
469
|
}
|
|
444
470
|
case "command": {
|
|
445
471
|
const panel = params.panel;
|
|
446
|
-
const slot = typeof panel === "number" ? panel :
|
|
472
|
+
const slot = typeof panel === "number" ? panel : this.slotFor(String(panel ?? "console"));
|
|
447
473
|
this.dispatchCommand({ panel: slot, action: String(params.action ?? ""), payload: String(params.payload ?? "") });
|
|
448
474
|
return true;
|
|
449
475
|
}
|
|
@@ -18,6 +18,11 @@ export interface ThreadInfo { id: string; name: string; kind: number }
|
|
|
18
18
|
export interface TreeRow {
|
|
19
19
|
id: number; parentId: number; depth: number; childCount: number;
|
|
20
20
|
kind: number; label: string; detail: string;
|
|
21
|
+
/** Stable node identity for pick/inspect — scene-store id, stage node id,
|
|
22
|
+
* or ECS entity index ("ent-N") when the collector can provide one. The
|
|
23
|
+
* numeric `id` is ephemeral (re-assigned per collect); `nodeId` survives
|
|
24
|
+
* refreshes and is what `inspector.call` methods take. */
|
|
25
|
+
nodeId?: string;
|
|
21
26
|
}
|
|
22
27
|
export interface GpuInfoJson {
|
|
23
28
|
entries: { key: string; value: string; isHeader: boolean }[];
|
|
@@ -100,30 +105,77 @@ export async function collectThreads(
|
|
|
100
105
|
return threads;
|
|
101
106
|
}
|
|
102
107
|
|
|
103
|
-
|
|
108
|
+
function sceneInspectorApi(): Record<string, any> | null {
|
|
109
|
+
const g = globalThis as Record<string, any>;
|
|
110
|
+
return (g.window?.__sceneInspector ?? g.__sceneInspector ?? null) as Record<string, any> | null;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** Scene-graph tree (label + ctor + child counts). Prefers a live
|
|
114
|
+
* `gameScene.stage` walk; falls back to the SceneInspector's scene store
|
|
115
|
+
* (`__sceneInspector.getSceneTree`) so games without a DOM-like stage still
|
|
116
|
+
* get a populated tree — with stable nodeIds for pick/details. */
|
|
104
117
|
export function collectSceneTree(ctx: CollectorContext): TreeRow[] {
|
|
105
118
|
const stage = ctx.gameScene?.stage;
|
|
106
|
-
if (
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
119
|
+
if (stage) {
|
|
120
|
+
const nodes: TreeRow[] = [];
|
|
121
|
+
let idCounter = 1;
|
|
122
|
+
const collect = (node: any, parentId: number, depth: number): void => {
|
|
123
|
+
const id = idCounter++;
|
|
124
|
+
// Prefer `label` (some scene-graph hosts warn on `name` access); fall
|
|
125
|
+
// back to `name` for hosts that only set that.
|
|
126
|
+
const nodeLabel = typeof node.label === "string" ? node.label
|
|
127
|
+
: typeof node.name === "string" ? node.name : "";
|
|
128
|
+
const ctorName = node.constructor?.name ?? "Node";
|
|
129
|
+
nodes.push({
|
|
130
|
+
id, parentId, depth,
|
|
131
|
+
childCount: node.children?.length ?? 0,
|
|
132
|
+
kind: 0,
|
|
133
|
+
label: nodeLabel || ctorName,
|
|
134
|
+
detail: nodeLabel ? ctorName : "",
|
|
135
|
+
nodeId: typeof node.id === "string" ? node.id : undefined,
|
|
136
|
+
});
|
|
137
|
+
node.children?.forEach((child: any) => collect(child, id, depth + 1));
|
|
138
|
+
};
|
|
139
|
+
collect(stage, -1, 0);
|
|
140
|
+
return nodes;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Scene-store fallback — the same snapshot the inspector's getSceneTree
|
|
144
|
+
// exposes. Nodes are flat with id/parentId links; walk roots → children.
|
|
145
|
+
try {
|
|
146
|
+
const snap = sceneInspectorApi()?.getSceneTree?.();
|
|
147
|
+
const storeNodes = snap?.nodes;
|
|
148
|
+
if (!Array.isArray(storeNodes) || !storeNodes.length) return [];
|
|
149
|
+
const byId = new Map<string, any>(storeNodes.map((n: any) => [n.id, n]));
|
|
150
|
+
const roots = storeNodes.filter((n: any) => n.parentId === null || !byId.has(n.parentId));
|
|
151
|
+
const rows: TreeRow[] = [];
|
|
152
|
+
const visited = new Set<string>();
|
|
153
|
+
let idCounter = 1;
|
|
154
|
+
const walk = (n: any, parentId: number, depth: number): void => {
|
|
155
|
+
if (visited.has(n.id)) return; // corrupted parent/child links — don't loop
|
|
156
|
+
visited.add(n.id);
|
|
157
|
+
const id = idCounter++;
|
|
158
|
+
const p = n.position;
|
|
159
|
+
rows.push({
|
|
160
|
+
id, parentId, depth,
|
|
161
|
+
childCount: n.children?.length ?? 0,
|
|
162
|
+
kind: 0,
|
|
163
|
+
label: String(n.name ?? n.id),
|
|
164
|
+
detail: `${n.type ?? "node"}${Array.isArray(p) ? ` (${p.map((v: number) => (+v).toFixed(1)).join(", ")})` : ""}`,
|
|
165
|
+
nodeId: String(n.id),
|
|
166
|
+
});
|
|
167
|
+
for (const cid of n.children ?? []) {
|
|
168
|
+
const c = byId.get(cid);
|
|
169
|
+
if (c) walk(c, id, depth + 1);
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
roots.forEach((n: any) => walk(n, -1, 0));
|
|
173
|
+
// Orphans (every node has a parent but no root resolves) — still show them.
|
|
174
|
+
for (const n of storeNodes) if (!visited.has(n.id)) walk(n, -1, 0);
|
|
175
|
+
return rows;
|
|
176
|
+
} catch {
|
|
177
|
+
return [];
|
|
178
|
+
}
|
|
127
179
|
}
|
|
128
180
|
|
|
129
181
|
/** DOM tree: scene-graph stage or ECS entities depending on mode. */
|
|
@@ -148,6 +200,7 @@ export function collectDomTree(ctx: CollectorContext, mode: "scene" | "ecs"): Tr
|
|
|
148
200
|
id, parentId: -1, depth: 0, childCount: 0, kind: 1,
|
|
149
201
|
label: `Entity ${ent.idx}`,
|
|
150
202
|
detail: `type=${entityType} pos=(${x.toFixed(1)},${y.toFixed(1)},${z.toFixed(1)})`,
|
|
203
|
+
nodeId: `ent-${ent.idx}`,
|
|
151
204
|
});
|
|
152
205
|
});
|
|
153
206
|
} catch { /* sim reader hiccup */ }
|
|
@@ -525,8 +525,6 @@ async function collectWorkers(ctx: EngineProviderContext): Promise<PanelSnapshot
|
|
|
525
525
|
kind: "kv",
|
|
526
526
|
name: `Worker manifest (v${m.manifestVersion ?? "?"})`,
|
|
527
527
|
rows: [
|
|
528
|
-
kv("Panels", (m.panels ?? []).map((p: any) => p.id ?? p.name ?? "?").join(", ") || "(none)"),
|
|
529
|
-
kv("Toggles", (m.toggles ?? []).map((t: any) => t.id ?? "?").join(", ") || "(none)"),
|
|
530
528
|
kv("Data feeds", (m.dataFeeds ?? []).map((f: any) => `${f.name}@${f.writeRateHz}hz`).join(", ") || "(none)"),
|
|
531
529
|
kv("Commands", (m.commands ?? []).join(", ") || "(none)"),
|
|
532
530
|
],
|