@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
|
|
@@ -24,8 +24,6 @@ import type {
|
|
|
24
24
|
IAssetResolver,
|
|
25
25
|
IDebugModeProvider,
|
|
26
26
|
IDebugOverlayProvider,
|
|
27
|
-
IDevToolsOverlayToggle,
|
|
28
|
-
IDevToolsPanelExtension,
|
|
29
27
|
IDevToolsRenderer,
|
|
30
28
|
IPerformanceMetricsProvider,
|
|
31
29
|
ISimStatsProvider,
|
|
@@ -33,10 +31,6 @@ import type {
|
|
|
33
31
|
import { syncWorkerManifests, type WorkerSyncEntry } from "./worker-sync";
|
|
34
32
|
|
|
35
33
|
export interface InitDevToolsOptions {
|
|
36
|
-
// --- Panel extensions & toggles (game-declared) ---
|
|
37
|
-
panels?: IDevToolsPanelExtension[];
|
|
38
|
-
overlayToggles?: IDevToolsOverlayToggle[];
|
|
39
|
-
|
|
40
34
|
// --- Providers (game-specific) ---
|
|
41
35
|
simStatsProvider?: ISimStatsProvider & { start?: () => void; stop?: () => void };
|
|
42
36
|
debugOverlayProvider?: IDebugOverlayProvider;
|
|
@@ -67,7 +61,7 @@ export interface InitDevToolsOptions {
|
|
|
67
61
|
|
|
68
62
|
/**
|
|
69
63
|
* Initialize DevTools with one call. Creates the bridge, wires all providers,
|
|
70
|
-
|
|
64
|
+
* merges globally-registered feeds/commands, syncs worker manifests,
|
|
71
65
|
* and exposes the API on window.__sceneInspector.
|
|
72
66
|
*
|
|
73
67
|
* Returns the bridge instance.
|
|
@@ -81,20 +75,7 @@ export async function initDevTools(renderer: any, options: InitDevToolsOptions =
|
|
|
81
75
|
|| !!options.bridgeClass
|
|
82
76
|
|| isSceneRenderer(renderer);
|
|
83
77
|
|
|
84
|
-
//
|
|
85
|
-
// (these merge with plugin-registered ones)
|
|
86
|
-
if (options.panels) {
|
|
87
|
-
options.panels.forEach((panel) => {
|
|
88
|
-
devtools.registerPanel(panel);
|
|
89
|
-
});
|
|
90
|
-
}
|
|
91
|
-
if (options.overlayToggles) {
|
|
92
|
-
options.overlayToggles.forEach((toggle) => {
|
|
93
|
-
devtools.registerOverlayToggle(toggle);
|
|
94
|
-
});
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// 4. Sync worker manifests (fetches panels/feeds/commands from sim workers)
|
|
78
|
+
// 4. Sync worker manifests (fetches feeds/commands from sim workers)
|
|
98
79
|
if (options.workerHosts && options.workerHosts.length > 0) {
|
|
99
80
|
await syncWorkerManifests(options.workerHosts);
|
|
100
81
|
}
|
|
@@ -136,10 +117,8 @@ export async function initDevTools(renderer: any, options: InitDevToolsOptions =
|
|
|
136
117
|
wirePerformanceMetricsProvider(bridge, options.performanceMetricsProvider);
|
|
137
118
|
}
|
|
138
119
|
|
|
139
|
-
// 7. Merge globally-registered
|
|
140
|
-
//
|
|
141
|
-
// game-declared ones; we augment the __sceneInspector API with
|
|
142
|
-
// plugin-registered data feeds and commands.
|
|
120
|
+
// 7. Merge globally-registered feeds/commands/SAB stats into the bridge —
|
|
121
|
+
// augments the __sceneInspector API with plugin-registered methods.
|
|
143
122
|
mergeGlobalRegistrations(bridge);
|
|
144
123
|
|
|
145
124
|
// 8. Initialize the profiling system (if enabled)
|
|
@@ -193,10 +172,9 @@ function wirePerformanceMetricsProvider(bridge: DevToolsDataBridge, provider: an
|
|
|
193
172
|
}
|
|
194
173
|
|
|
195
174
|
/**
|
|
196
|
-
* Merge globally-registered
|
|
175
|
+
* Merge globally-registered data feeds and commands from the `devtools`
|
|
197
176
|
* singleton into the bridge's __sceneInspector API.
|
|
198
177
|
*
|
|
199
|
-
* - Panels: merged into getPanelExtensions() (game panels + plugin panels)
|
|
200
178
|
* - Data feeds: added as methods on __sceneInspector (called synchronously)
|
|
201
179
|
* - Commands: added as methods on __sceneInspector (forwarded to worker or called directly)
|
|
202
180
|
* - SAB stats: added as methods on __sceneInspector (read from SAB)
|
|
@@ -204,27 +182,6 @@ function wirePerformanceMetricsProvider(bridge: DevToolsDataBridge, provider: an
|
|
|
204
182
|
function mergeGlobalRegistrations(bridge: DevToolsDataBridge): void {
|
|
205
183
|
const manifest = devtools.getManifest();
|
|
206
184
|
|
|
207
|
-
// Augment getPanelExtensions to merge global registry panels
|
|
208
|
-
const originalGetPanels = (bridge as any).getPanelExtensions?.bind(bridge);
|
|
209
|
-
(bridge as any).getPanelExtensions = (): IDevToolsPanelExtension[] => {
|
|
210
|
-
const gamePanels = originalGetPanels?.() ?? [];
|
|
211
|
-
// Merge: game panels take precedence on id collisions
|
|
212
|
-
const merged = new Map<string, IDevToolsPanelExtension>();
|
|
213
|
-
manifest.panels.forEach((p) => { merged.set(p.id, p);; });
|
|
214
|
-
gamePanels.forEach((p: any) => { merged.set(p.id, p);; });
|
|
215
|
-
return Array.from(merged.values()).sort((a, b) => (a.order ?? 100) - (b.order ?? 100));
|
|
216
|
-
};
|
|
217
|
-
|
|
218
|
-
// Augment getOverlayToggles to merge global registry toggles
|
|
219
|
-
const originalGetToggles = (bridge as any).getOverlayToggles?.bind(bridge);
|
|
220
|
-
(bridge as any).getOverlayToggles = (): IDevToolsOverlayToggle[] => {
|
|
221
|
-
const gameToggles = originalGetToggles?.() ?? [];
|
|
222
|
-
const merged = new Map<string, IDevToolsOverlayToggle>();
|
|
223
|
-
manifest.toggles.forEach((t) => { merged.set(t.id, t);; });
|
|
224
|
-
gameToggles.forEach((t: any) => { merged.set(t.id, t);; });
|
|
225
|
-
return Array.from(merged.values());
|
|
226
|
-
};
|
|
227
|
-
|
|
228
185
|
// Add data feed methods to the __sceneInspector API
|
|
229
186
|
// These are called synchronously by the panel via callInspector()
|
|
230
187
|
const api = (window as any).__sceneInspector;
|
|
@@ -44,8 +44,6 @@ export abstract class BaseSceneInspector extends DevToolsDataBridge {
|
|
|
44
44
|
return null;
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
// getPanelExtensions() and getOverlayToggles() inherited from DevToolsDataBridge.
|
|
48
|
-
|
|
49
47
|
// --- Init ---
|
|
50
48
|
|
|
51
49
|
init(renderer: IDevToolsRenderer): void {
|
|
@@ -568,9 +566,7 @@ export abstract class BaseSceneInspector extends DevToolsDataBridge {
|
|
|
568
566
|
const cosX = Math.cos(angleX), sinX = Math.sin(angleX);
|
|
569
567
|
|
|
570
568
|
const THUMB_SIZE = 192;
|
|
571
|
-
const canvas =
|
|
572
|
-
canvas.width = THUMB_SIZE;
|
|
573
|
-
canvas.height = THUMB_SIZE;
|
|
569
|
+
const canvas = new OffscreenCanvas(THUMB_SIZE, THUMB_SIZE);
|
|
574
570
|
const ctx = canvas.getContext("2d");
|
|
575
571
|
if (!ctx) return null;
|
|
576
572
|
|
|
@@ -617,7 +613,7 @@ export abstract class BaseSceneInspector extends DevToolsDataBridge {
|
|
|
617
613
|
}
|
|
618
614
|
}
|
|
619
615
|
|
|
620
|
-
const dataUrl = canvas.toDataURL("image/png");
|
|
616
|
+
const dataUrl = (canvas as unknown as { toDataURL(type?: string): string }).toDataURL("image/png");
|
|
621
617
|
this.evictThumbnailCache();
|
|
622
618
|
this.thumbnailCache.set(path, dataUrl);
|
|
623
619
|
return dataUrl;
|
|
@@ -69,7 +69,9 @@ export interface IAssetResolver {
|
|
|
69
69
|
getTextureUrl(uri: string): string | null;
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
/** Optional debug
|
|
72
|
+
/** Optional debug draw toggles — games with GPU-side debug overlays
|
|
73
|
+
* (chunk grid, velocity arrows) implement this. Drives the game's own
|
|
74
|
+
* debug rendering, not a DOM overlay. */
|
|
73
75
|
export interface IDebugOverlayProvider {
|
|
74
76
|
setShowChunkGrid(show: boolean): void;
|
|
75
77
|
getShowChunkGrid(): boolean;
|
|
@@ -91,73 +93,7 @@ export interface IPerformanceMetricsProvider {
|
|
|
91
93
|
getPerformanceMetrics(): any;
|
|
92
94
|
}
|
|
93
95
|
|
|
94
|
-
|
|
95
|
-
export interface IGameDevToolsProvider {
|
|
96
|
-
getTabs(): GameDevToolsTab[];
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
export interface GameDevToolsTab {
|
|
100
|
-
id: string;
|
|
101
|
-
label: string;
|
|
102
|
-
onActivate(container: HTMLElement): void;
|
|
103
|
-
onDeactivate(): void;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* Game-specific DevTools panel extension.
|
|
108
|
-
* Games declare tabs with HTML content, CSS, and a JS script that runs
|
|
109
|
-
* in the DevTools panel context. The script receives DevToolsPanel helpers
|
|
110
|
-
* and returns lifecycle callbacks.
|
|
111
|
-
*/
|
|
112
|
-
export interface IDevToolsPanelExtension {
|
|
113
|
-
/** Unique id for this extension (e.g. "debug-info", "game-panel"). */
|
|
114
|
-
id: string;
|
|
115
|
-
/** Tab label shown in the tab bar. */
|
|
116
|
-
tabLabel: string;
|
|
117
|
-
/** Tab tooltip. */
|
|
118
|
-
tabTooltip?: string;
|
|
119
|
-
/** Order/priority for tab placement. Core tabs: 0-100, game tabs: 100+. Default: 100. */
|
|
120
|
-
order?: number;
|
|
121
|
-
/** __sceneInspector methods that must exist for this tab to be shown. */
|
|
122
|
-
requiredMethods?: string[];
|
|
123
|
-
/** HTML content for the panel body (inserted into a container div). */
|
|
124
|
-
html: string;
|
|
125
|
-
/** CSS to inject into the panel document. */
|
|
126
|
-
css?: string;
|
|
127
|
-
/**
|
|
128
|
-
* JS to eval in the panel context. Receives a DevToolsPanelHelpers object
|
|
129
|
-
* and returns { onActivate?, onDeactivate?, onRefresh?, onDestroy? }.
|
|
130
|
-
*/
|
|
131
|
-
script?: string;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
/**
|
|
135
|
-
* Game-specific overlay toggle for the overlays dropdown menu.
|
|
136
|
-
*/
|
|
137
|
-
export interface IDevToolsOverlayToggle {
|
|
138
|
-
/** Unique id (e.g. "chunk-grid", "vel-arrows"). */
|
|
139
|
-
id: string;
|
|
140
|
-
/** Label shown next to the checkbox. */
|
|
141
|
-
label: string;
|
|
142
|
-
/** __sceneInspector methods that must exist for this toggle to be shown. */
|
|
143
|
-
requiredMethods?: string[];
|
|
144
|
-
/**
|
|
145
|
-
* JS to eval in the panel context. Receives DevToolsPanelHelpers.
|
|
146
|
-
* Should set up a checkbox listener on #chk-{id} that calls callInspector.
|
|
147
|
-
*/
|
|
148
|
-
script?: string;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// --- Debug Overlay Data Providers ---
|
|
152
|
-
|
|
153
|
-
/** Provides entity data for the 2D debug overlay (chunk grid + velocity arrows). */
|
|
154
|
-
export interface IDebugOverlayData {
|
|
155
|
-
getEntityCount(): number;
|
|
156
|
-
getEntityPosition(i: number): { x: number; y: number; z: number } | null;
|
|
157
|
-
getEntityVelocity(i: number): { x: number; y: number; z: number } | null;
|
|
158
|
-
getPlayerPosition(): { x: number; z: number } | null;
|
|
159
|
-
getChunkGridConfig(): { chunkSize: number; chunksVisible: number };
|
|
160
|
-
}
|
|
96
|
+
// --- Debug Raycast Providers ---
|
|
161
97
|
|
|
162
98
|
/** Provides camera state for debug overlays. */
|
|
163
99
|
export interface IDebugCamera {
|
|
@@ -170,8 +106,6 @@ export interface IDebugCamera {
|
|
|
170
106
|
far: number;
|
|
171
107
|
}
|
|
172
108
|
|
|
173
|
-
// --- Debug Raycast Providers ---
|
|
174
|
-
|
|
175
109
|
/** Result of a raycast against entities. */
|
|
176
110
|
export interface IRaycastResult {
|
|
177
111
|
entityIndex: number;
|
|
@@ -196,23 +130,6 @@ export interface IRaycastProvider {
|
|
|
196
130
|
): IRaycastResult | null;
|
|
197
131
|
}
|
|
198
132
|
|
|
199
|
-
// --- Label Overlay Providers ---
|
|
200
|
-
|
|
201
|
-
/** A single label entry for the HTML label overlay. */
|
|
202
|
-
export interface ILabelEntry {
|
|
203
|
-
key: string;
|
|
204
|
-
text: string;
|
|
205
|
-
color: string;
|
|
206
|
-
x: number;
|
|
207
|
-
y: number;
|
|
208
|
-
z: number;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
/** Provides label data for the HTML label overlay. */
|
|
212
|
-
export interface ILabelProvider {
|
|
213
|
-
getLabels(): ILabelEntry[];
|
|
214
|
-
}
|
|
215
|
-
|
|
216
133
|
// --- Scene Sync Providers ---
|
|
217
134
|
|
|
218
135
|
/** Entity snapshot for scene store sync. */
|
|
@@ -251,7 +168,6 @@ export interface IGameDevToolsExtension {
|
|
|
251
168
|
debugOverlayProvider?: IDebugOverlayProvider;
|
|
252
169
|
debugModeProvider?: IDebugModeProvider;
|
|
253
170
|
performanceMetricsProvider?: IPerformanceMetricsProvider;
|
|
254
|
-
tabProvider?: IGameDevToolsProvider;
|
|
255
171
|
}
|
|
256
172
|
|
|
257
173
|
// --- Sim Stats & Controls ---
|
|
@@ -274,8 +190,7 @@ export interface ISimStats {
|
|
|
274
190
|
|
|
275
191
|
/**
|
|
276
192
|
* Provides sim stats and control methods for the DevTools "Sim" tab.
|
|
277
|
-
* Any sim-driven game (2D or 3D) can implement this
|
|
278
|
-
* `createSimStatsPanelExtension()` factory renders the tab.
|
|
193
|
+
* Any sim-driven game (2D or 3D) can implement this.
|
|
279
194
|
*/
|
|
280
195
|
export interface ISimStatsProvider {
|
|
281
196
|
getSimStats(): ISimStats;
|
|
@@ -1,16 +1,14 @@
|
|
|
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
|
|
|
11
10
|
import { createLogger } from "@downdraft/engine/util/logger";
|
|
12
|
-
import { _devtoolsImpl,
|
|
13
|
-
import type { IDevToolsPanelExtension } from "./types";
|
|
11
|
+
import { _devtoolsImpl, type DevToolsManifest } from "./api";
|
|
14
12
|
|
|
15
13
|
const log = createLogger("info");
|
|
16
14
|
|
|
@@ -79,14 +77,6 @@ async function syncOneWorker(entry: WorkerSyncEntry): Promise<void> {
|
|
|
79
77
|
}
|
|
80
78
|
}
|
|
81
79
|
|
|
82
|
-
// Register panels (namespaced by prefix to avoid collisions)
|
|
83
|
-
manifest.panels.forEach((panel) => {
|
|
84
|
-
const namespaced: IDevToolsPanelExtension = prefix
|
|
85
|
-
? { ...panel, id: `${prefix}:${panel.id}` }
|
|
86
|
-
: panel;
|
|
87
|
-
devtools.registerPanel(namespaced);
|
|
88
|
-
});
|
|
89
|
-
|
|
90
80
|
// Register data feed readers — these read from the SAB synchronously
|
|
91
81
|
// We register them as main-realm data feeds that read from the worker SAB
|
|
92
82
|
if (sab) {
|
|
@@ -76,6 +76,19 @@ interface DocState {
|
|
|
76
76
|
gpuTex: { ptr: number | bigint; tex: unknown; w: number; h: number } | null;
|
|
77
77
|
stats: { frames: number; resolveMs: number; paintMs: number; diffMs: number; bytes: number };
|
|
78
78
|
statsLast: number;
|
|
79
|
+
/** smoothWheel (PanelSpec): wheel deltas accumulate here and drain as
|
|
80
|
+
* eased per-tick steps in pump() instead of each event repainting an
|
|
81
|
+
* instant jump. Deltas/positions stay in doc CSS px (scale applied at
|
|
82
|
+
* dispatch); DOM sign convention (+y = scroll down). */
|
|
83
|
+
smoothWheel: boolean;
|
|
84
|
+
wheelRx: number;
|
|
85
|
+
wheelRy: number;
|
|
86
|
+
wheelX: number;
|
|
87
|
+
wheelY: number;
|
|
88
|
+
wheelMods?: string[];
|
|
89
|
+
/** performance.now() of the last step — the ease is dt-based so the curve
|
|
90
|
+
* holds regardless of pump cadence. */
|
|
91
|
+
wheelT: number;
|
|
79
92
|
}
|
|
80
93
|
|
|
81
94
|
// Bound-buffer header (u32 indices) — mirrors BoundBuf layout in lib.rs.
|
|
@@ -211,9 +224,32 @@ export function createDocCore(emit: (m: WorkerToUi) => void, gpuHooks?: DocGpuHo
|
|
|
211
224
|
}
|
|
212
225
|
}
|
|
213
226
|
|
|
227
|
+
// Smooth-wheel ease: fraction of the remainder applied per elapsed ms.
|
|
228
|
+
// τ≈45ms → ~30% per 16ms tick, ~95% drained in ~135ms — snappy but
|
|
229
|
+
// animated. A 1/MAX_TICKS floor bounds any backlog at ~320ms of drain —
|
|
230
|
+
// a fast wheel can't make scroll trail input indefinitely. Dispatching
|
|
231
|
+
// real wheel events keeps Blitz's hit-testing, scroll chaining, scrollbar
|
|
232
|
+
// reveal, and scroll DOM events intact.
|
|
233
|
+
const WHEEL_TAU_MS = 45;
|
|
234
|
+
const WHEEL_MAX_TICKS = 20;
|
|
235
|
+
function stepWheel(s: DocState, now: number): void {
|
|
236
|
+
const dt = Math.min(50, Math.max(1, now - s.wheelT));
|
|
237
|
+
s.wheelT = now;
|
|
238
|
+
const f = Math.max(1 - Math.exp(-dt / WHEEL_TAU_MS), 1 / WHEEL_MAX_TICKS);
|
|
239
|
+
// Snap the tail: a step below ~¾px/axis applies the whole residue instead
|
|
240
|
+
// of asymptoting through invisible sub-pixel repaints.
|
|
241
|
+
const dx = Math.abs(s.wheelRx) * f < 0.75 ? s.wheelRx : s.wheelRx * f;
|
|
242
|
+
const dy = Math.abs(s.wheelRy) * f < 0.75 ? s.wheelRy : s.wheelRy * f;
|
|
243
|
+
s.wheelRx -= dx;
|
|
244
|
+
s.wheelRy -= dy;
|
|
245
|
+
const k = s.scale;
|
|
246
|
+
s.doc.wheel(s.wheelX * k, s.wheelY * k, dx * k, dy * k, s.wheelMods);
|
|
247
|
+
}
|
|
248
|
+
|
|
214
249
|
function pump() {
|
|
215
250
|
const now = performance.now();
|
|
216
251
|
for (const [id, s] of docs.entries()) {
|
|
252
|
+
if (s.smoothWheel && (s.wheelRx !== 0 || s.wheelRy !== 0)) stepWheel(s, now);
|
|
217
253
|
const events = s.doc.pollEvents();
|
|
218
254
|
if (events.length) emit({ type: "events", id, events });
|
|
219
255
|
if (!s.doc.pending()) continue;
|
|
@@ -284,6 +320,8 @@ export function createDocCore(emit: (m: WorkerToUi) => void, gpuHooks?: DocGpuHo
|
|
|
284
320
|
doc, scale: m.scale, cssW: m.cssW, cssH: m.cssH,
|
|
285
321
|
maxFps: m.maxFps ?? 0, lastFrame: -1e9,
|
|
286
322
|
sab: null, sabU32: null, sabDisabled: false, gpuTex: null,
|
|
323
|
+
smoothWheel: m.smoothWheel === true,
|
|
324
|
+
wheelRx: 0, wheelRy: 0, wheelX: 0, wheelY: 0, wheelT: 0,
|
|
287
325
|
stats: freshStats(), statsLast: 0,
|
|
288
326
|
};
|
|
289
327
|
docs.set(m.id, s);
|
|
@@ -295,9 +333,16 @@ export function createDocCore(emit: (m: WorkerToUi) => void, gpuHooks?: DocGpuHo
|
|
|
295
333
|
if (s) s.maxFps = m.maxFps;
|
|
296
334
|
break;
|
|
297
335
|
}
|
|
298
|
-
case "setHtml":
|
|
336
|
+
case "setHtml": {
|
|
299
337
|
if (/float\s*:/.test(m.html)) emit({ type: "error", id: m.id, message: "CSS float hangs Blitz layout — use flex instead" });
|
|
300
|
-
docs.get(m.id)
|
|
338
|
+
const s = docs.get(m.id);
|
|
339
|
+
if (!s) break;
|
|
340
|
+
// Drop any in-flight smooth-wheel drain — its remainder belongs to
|
|
341
|
+
// the document being replaced.
|
|
342
|
+
s.wheelRx = s.wheelRy = 0;
|
|
343
|
+
s.doc.setHtml(m.html);
|
|
344
|
+
break;
|
|
345
|
+
}
|
|
301
346
|
case "resize": {
|
|
302
347
|
const s = docs.get(m.id);
|
|
303
348
|
if (s) {
|
|
@@ -403,9 +448,27 @@ export function createDocCore(emit: (m: WorkerToUi) => void, gpuHooks?: DocGpuHo
|
|
|
403
448
|
const k = s.scale;
|
|
404
449
|
switch (msg.kind) {
|
|
405
450
|
case "move": s.doc.pointerMove(msg.x * k, msg.y * k, msg.mods); break;
|
|
406
|
-
case "down":
|
|
451
|
+
case "down":
|
|
452
|
+
// A press interrupts any in-flight wheel scroll (mirrors
|
|
453
|
+
// Blitz's interrupt_animation semantics for user input).
|
|
454
|
+
s.wheelRx = 0; s.wheelRy = 0;
|
|
455
|
+
s.doc.pointerDown(msg.x * k, msg.y * k, msg.button, msg.mods);
|
|
456
|
+
break;
|
|
407
457
|
case "up": s.doc.pointerUp(msg.x * k, msg.y * k, msg.button, msg.mods); break;
|
|
408
|
-
case "wheel":
|
|
458
|
+
case "wheel":
|
|
459
|
+
if (s.smoothWheel) {
|
|
460
|
+
// A fresh gesture back-dates wheelT so the first pump tick
|
|
461
|
+
// applies a full ~16ms step immediately — no dead frame.
|
|
462
|
+
if (s.wheelRx === 0 && s.wheelRy === 0) s.wheelT = performance.now() - 16;
|
|
463
|
+
s.wheelRx += msg.deltaX;
|
|
464
|
+
s.wheelRy += msg.deltaY;
|
|
465
|
+
s.wheelX = msg.x;
|
|
466
|
+
s.wheelY = msg.y;
|
|
467
|
+
s.wheelMods = msg.mods;
|
|
468
|
+
} else {
|
|
469
|
+
s.doc.wheel(msg.x * k, msg.y * k, msg.deltaX * k, msg.deltaY * k, msg.mods);
|
|
470
|
+
}
|
|
471
|
+
break;
|
|
409
472
|
case "key": s.doc.key(msg.down, msg.key, msg.code, msg.text, msg.mods); break;
|
|
410
473
|
}
|
|
411
474
|
break;
|
|
@@ -10,6 +10,7 @@ import type { OsrDomEvent } from "@downdraft/engine/libraries/blitz-ui/native-os
|
|
|
10
10
|
import type { InputEventControl, RendererInputBus } from "@downdraft/engine/module/renderer-module";
|
|
11
11
|
import { getHostCapabilities, getNativeHost } from "@downdraft/engine/platform/runtime";
|
|
12
12
|
import { createLogger } from "@downdraft/engine/util/logger";
|
|
13
|
+
import { untrackedTimeout } from "@downdraft/engine/util/session-timer";
|
|
13
14
|
import { PanelBlitPass } from "./composite";
|
|
14
15
|
import { createLocalBackend, createWorkerBackend, type DocBackend } from "./doc-backend";
|
|
15
16
|
import type { DocInputMsg, DocMutation, NavNodeInfo, UiPointerMsg, WorkerToUi } from "./protocol";
|
|
@@ -40,6 +41,10 @@ export interface PanelSpec {
|
|
|
40
41
|
zoom?: number;
|
|
41
42
|
/** Raster-rate cap for animating docs (0 = uncapped). Static docs repaint on demand regardless. */
|
|
42
43
|
maxFps?: number;
|
|
44
|
+
/** Ease wheel deltas out at frame cadence instead of applying each detent
|
|
45
|
+
* as an instant jump — scrolling animates smoothly and repaints stay at
|
|
46
|
+
* one per pump tick regardless of input rate. Default false. */
|
|
47
|
+
smoothWheel?: boolean;
|
|
43
48
|
/** Initial markup (or `vdom` rendered beforehand by the caller). */
|
|
44
49
|
html: string;
|
|
45
50
|
/** When false the panel never receives pointer input and `panelAt` skips
|
|
@@ -306,7 +311,7 @@ export class HtmlUiHost {
|
|
|
306
311
|
this.order.push(id);
|
|
307
312
|
// Doc viewport scale = raster px per CSS px = scale * zoom; layout size
|
|
308
313
|
// is rect/zoom CSS px so the raster still covers rect*scale texture px.
|
|
309
|
-
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 });
|
|
314
|
+
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 });
|
|
310
315
|
// GPU-direct: create the panel texture eagerly so the worker's texBind
|
|
311
316
|
// lands before its first upload — the host knows raster dims at mount.
|
|
312
317
|
if (this.gpuReady) this.bindPanelTex(panel);
|
|
@@ -470,6 +475,9 @@ export class HtmlUiHost {
|
|
|
470
475
|
* `incrementalDom` is false until then, and forever on old cdylibs). */
|
|
471
476
|
get docCaps(): { incrementalDom: boolean } { return this.caps; }
|
|
472
477
|
|
|
478
|
+
/** Which doc backend is live — "worker" (raster off-thread) or "local". */
|
|
479
|
+
get backendKind(): "worker" | "local" { return this.backend.kind; }
|
|
480
|
+
|
|
473
481
|
/** Compositor surface for GameRenderer's end-of-frame UI pass. */
|
|
474
482
|
get compositor(): { hasContent(): boolean; render(pass: GPURenderPassEncoder, w: number, h: number): void } {
|
|
475
483
|
return {
|
|
@@ -511,7 +519,7 @@ export class HtmlUiHost {
|
|
|
511
519
|
this.terminating = true;
|
|
512
520
|
this.send({ type: "gpuDetach" });
|
|
513
521
|
const backend = this.backend;
|
|
514
|
-
|
|
522
|
+
untrackedTimeout(() => {
|
|
515
523
|
if (!this.gpuDetachAcked) backend.dispose();
|
|
516
524
|
this.destroyRetired();
|
|
517
525
|
}, 150);
|
|
@@ -912,7 +920,7 @@ export class HtmlUiHost {
|
|
|
912
920
|
|| (p.postedBindPtr !== null && BigInt(ptr) === p.postedBindPtr));
|
|
913
921
|
if (borrowed) {
|
|
914
922
|
const key = BigInt(ptr);
|
|
915
|
-
const timer =
|
|
923
|
+
const timer = untrackedTimeout(() => {
|
|
916
924
|
if (this.retiredTex.delete(key)) t.destroy();
|
|
917
925
|
}, 1000);
|
|
918
926
|
this.retiredTex.set(key, { tex: t, timer });
|
|
@@ -52,7 +52,7 @@ export interface NavNodeInfo {
|
|
|
52
52
|
|
|
53
53
|
/** Host → backend */
|
|
54
54
|
export type UiToWorker =
|
|
55
|
-
| { type: "create"; id: string; cssW: number; cssH: number; scale: number; html: string; maxFps?: number }
|
|
55
|
+
| { type: "create"; id: string; cssW: number; cssH: number; scale: number; html: string; maxFps?: number; smoothWheel?: boolean }
|
|
56
56
|
| { type: "fps"; id: string; maxFps: number }
|
|
57
57
|
| { type: "setHtml"; id: string; html: string }
|
|
58
58
|
| { type: "resize"; id: string; cssW: number; cssH: number; scale: number }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@downdraft/engine",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "https://github.com/knackstedt/downdraft.git",
|
|
@@ -844,13 +844,12 @@
|
|
|
844
844
|
},
|
|
845
845
|
"dependencies": {
|
|
846
846
|
"@bokuweb/zstd-wasm": "^0.0.27",
|
|
847
|
+
"@dimforge/rapier3d-compat": "^0.19.3",
|
|
847
848
|
"@gltf-transform/core": "^4.4.2",
|
|
848
849
|
"@gltf-transform/extensions": "^4.4.2",
|
|
849
850
|
"@gltf-transform/functions": "^4.4.2",
|
|
850
851
|
"@h00w/basis-universal-transcoder": "^2.1.0",
|
|
851
|
-
"@jitl/quickjs-singlefile-browser-release-sync": "^0.32.0",
|
|
852
852
|
"acorn": "^8.15.0",
|
|
853
|
-
"buffer": "^6.0.3",
|
|
854
853
|
"comment-json": "^5.0.0",
|
|
855
854
|
"dns-packet": "^5.6.1",
|
|
856
855
|
"draco3dgltf": "^1.5.7",
|
|
@@ -859,13 +858,13 @@
|
|
|
859
858
|
"ktx2-encoder": "^0.6.0",
|
|
860
859
|
"meshoptimizer": "^1.2.0",
|
|
861
860
|
"quickjs-emscripten": "^0.32.0",
|
|
861
|
+
"recast-navigation": "^0.43.1",
|
|
862
862
|
"wgpu-matrix": "^3.4.2",
|
|
863
863
|
"xxh3-ts": "^2.0.1",
|
|
864
|
-
"xxhash-wasm": "^0.4.2",
|
|
865
864
|
"zustand": "^5.0.2"
|
|
866
865
|
},
|
|
867
866
|
"peerDependencies": {
|
|
868
|
-
"@downdraft/platform-native": "^0.
|
|
867
|
+
"@downdraft/platform-native": "^0.3.1"
|
|
869
868
|
},
|
|
870
869
|
"peerDependenciesMeta": {
|
|
871
870
|
"@downdraft/platform-native": {
|
package/test/src/vision-test.ts
CHANGED
|
@@ -251,14 +251,12 @@ export class VisionTest {
|
|
|
251
251
|
height: number,
|
|
252
252
|
): Promise<string> {
|
|
253
253
|
const data = await this.captureTexture(texture, width, height);
|
|
254
|
-
const canvas =
|
|
255
|
-
canvas.width = width;
|
|
256
|
-
canvas.height = height;
|
|
254
|
+
const canvas = new OffscreenCanvas(width, height);
|
|
257
255
|
const ctx = canvas.getContext("2d")!;
|
|
258
256
|
const imageData = ctx.createImageData(width, height);
|
|
259
257
|
imageData.data.set(data);
|
|
260
258
|
ctx.putImageData(imageData, 0, 0);
|
|
261
|
-
return canvas.toDataURL("image/png");
|
|
259
|
+
return (canvas as unknown as { toDataURL(type?: string): string }).toDataURL("image/png");
|
|
262
260
|
}
|
|
263
261
|
|
|
264
262
|
destroy(): void {
|
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
// ============================================================================
|
|
2
|
-
// SabCanvasOverlay — DOM <canvas> overlay backed by a SharedArrayBuffer bitmap
|
|
3
|
-
//
|
|
4
|
-
// Generalized from overburden's MapCanvas. Provides the generic shell:
|
|
5
|
-
// - A visible <canvas> fixed-positioned over the game canvas
|
|
6
|
-
// (pointer-events: none, configurable z-index, hidden while opacity <= 0).
|
|
7
|
-
// - An offscreen "bitmap" canvas at a fixed data resolution that is repainted
|
|
8
|
-
// only when a SAB sequence counter changes (via getSeq + redrawBitmap).
|
|
9
|
-
// - Per-frame window resize tracking and opacity control.
|
|
10
|
-
//
|
|
11
|
-
// The game supplies the compositing + drawing logic via callbacks; the overlay
|
|
12
|
-
// owns all DOM plumbing.
|
|
13
|
-
// ============================================================================
|
|
14
|
-
|
|
15
|
-
export interface SabCanvasOverlayOptions {
|
|
16
|
-
/** DOM id for the visible canvas. Default: "sab-canvas-overlay" */
|
|
17
|
-
id?: string;
|
|
18
|
-
/** CSS z-index. Default: 55 (above the game surface, below DOM overlays) */
|
|
19
|
-
zIndex?: number;
|
|
20
|
-
/** Offscreen bitmap resolution. If omitted, no bitmap canvas is created. */
|
|
21
|
-
bitmapWidth?: number;
|
|
22
|
-
bitmapHeight?: number;
|
|
23
|
-
/** CSS image-rendering for the visible canvas. Default: "pixelated" */
|
|
24
|
-
imageRendering?: "pixelated" | "auto";
|
|
25
|
-
/**
|
|
26
|
-
* Monotonic sequence number for the backing data (e.g. a SAB seq counter).
|
|
27
|
-
* The bitmap is repainted only when this value changes. Omit to repaint
|
|
28
|
-
* every visible frame.
|
|
29
|
-
*/
|
|
30
|
-
getSeq?: () => number;
|
|
31
|
-
/**
|
|
32
|
-
* Repaint the offscreen bitmap canvas. Called only when getSeq() reports
|
|
33
|
-
* a new value (requires bitmapWidth/bitmapHeight).
|
|
34
|
-
*/
|
|
35
|
-
redrawBitmap?: (ctx: CanvasRenderingContext2D, bitmapW: number, bitmapH: number) => void;
|
|
36
|
-
/**
|
|
37
|
-
* Draw the visible canvas each visible frame, after the bitmap has been
|
|
38
|
-
* refreshed. `bitmap` is the offscreen bitmap canvas (or null when no
|
|
39
|
-
* bitmap was configured).
|
|
40
|
-
*/
|
|
41
|
-
draw: (ctx: CanvasRenderingContext2D, bitmap: HTMLCanvasElement | null, w: number, h: number) => void;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export class SabCanvasOverlay {
|
|
45
|
-
readonly canvas: HTMLCanvasElement;
|
|
46
|
-
protected ctx: CanvasRenderingContext2D;
|
|
47
|
-
/** Offscreen bitmap canvas (fixed data resolution). Null when unconfigured. */
|
|
48
|
-
readonly bitmapCanvas: HTMLCanvasElement | null = null;
|
|
49
|
-
protected bitmapCtx: CanvasRenderingContext2D | null = null;
|
|
50
|
-
private opts: SabCanvasOverlayOptions;
|
|
51
|
-
private lastSeq = -1;
|
|
52
|
-
|
|
53
|
-
constructor(opts: SabCanvasOverlayOptions) {
|
|
54
|
-
this.opts = opts;
|
|
55
|
-
|
|
56
|
-
this.canvas = document.createElement("canvas");
|
|
57
|
-
this.canvas.id = opts.id ?? "sab-canvas-overlay";
|
|
58
|
-
this.canvas.style.position = "fixed";
|
|
59
|
-
this.canvas.style.top = "0";
|
|
60
|
-
this.canvas.style.left = "0";
|
|
61
|
-
this.canvas.style.width = "100%";
|
|
62
|
-
this.canvas.style.height = "100%";
|
|
63
|
-
this.canvas.style.pointerEvents = "none";
|
|
64
|
-
this.canvas.style.zIndex = String(opts.zIndex ?? 55);
|
|
65
|
-
this.canvas.style.display = "none"; // hidden until opacity > 0
|
|
66
|
-
this.canvas.style.imageRendering = opts.imageRendering ?? "pixelated";
|
|
67
|
-
this.ctx = this.canvas.getContext("2d")!;
|
|
68
|
-
|
|
69
|
-
if (opts.bitmapWidth !== undefined && opts.bitmapHeight !== undefined) {
|
|
70
|
-
this.bitmapCanvas = document.createElement("canvas");
|
|
71
|
-
this.bitmapCanvas.width = opts.bitmapWidth;
|
|
72
|
-
this.bitmapCanvas.height = opts.bitmapHeight;
|
|
73
|
-
this.bitmapCtx = this.bitmapCanvas.getContext("2d")!;
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/** Attach the canvas to the DOM (call once during init). */
|
|
78
|
-
mount(parent: HTMLElement = document.body): void {
|
|
79
|
-
parent.appendChild(this.canvas);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Per-frame update. Call from the render loop.
|
|
84
|
-
* Hides the canvas while `opacity <= 0`; otherwise resizes the canvas to the
|
|
85
|
-
* window, repaints the bitmap when the seq counter advanced, and invokes
|
|
86
|
-
* the draw callback.
|
|
87
|
-
*/
|
|
88
|
-
update(opacity: number): void {
|
|
89
|
-
if (opacity <= 0) {
|
|
90
|
-
this.canvas.style.display = "none";
|
|
91
|
-
return;
|
|
92
|
-
}
|
|
93
|
-
this.canvas.style.display = "block";
|
|
94
|
-
this.canvas.style.opacity = String(opacity);
|
|
95
|
-
|
|
96
|
-
// Resize the visible canvas to match the window if needed.
|
|
97
|
-
const w = window.innerWidth;
|
|
98
|
-
const h = window.innerHeight;
|
|
99
|
-
if (this.canvas.width !== w || this.canvas.height !== h) {
|
|
100
|
-
this.canvas.width = w;
|
|
101
|
-
this.canvas.height = h;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// Repaint the bitmap if the backing data has a new seq value.
|
|
105
|
-
const seq = this.opts.getSeq?.();
|
|
106
|
-
if (this.bitmapCtx && (seq === undefined || seq !== this.lastSeq)) {
|
|
107
|
-
if (seq !== undefined) this.lastSeq = seq;
|
|
108
|
-
this.opts.redrawBitmap?.(this.bitmapCtx, this.bitmapCanvas!.width, this.bitmapCanvas!.height);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
this.opts.draw(this.ctx, this.bitmapCanvas, w, h);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
/** Mark the bitmap stale so it repaints on the next visible frame. */
|
|
115
|
-
invalidate(): void {
|
|
116
|
-
this.lastSeq = -1;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
dispose(): void {
|
|
120
|
-
this.canvas.remove();
|
|
121
|
-
}
|
|
122
|
-
}
|