@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
|
@@ -71,32 +71,18 @@ export enum Material {
|
|
|
71
71
|
// --- Ingredients ---
|
|
72
72
|
// Ice (frozen water — produced by cooling)
|
|
73
73
|
Ice = 64,
|
|
74
|
+
// (IDs 67–68, 71–73, 76–77, 79–81, 83–88 were inert alchemy materials —
|
|
75
|
+
// retired; keep the gaps so old saves don't remap onto new materials.)
|
|
74
76
|
// Liquids
|
|
75
77
|
Ether = 65, // ethereal solvent — light, volatile, glowing
|
|
76
78
|
Blood = 66, // viscous, necrotic-tinted
|
|
77
|
-
Syrup = 67, // thick, dense, sweet (binding agent)
|
|
78
|
-
NightshadeExtract = 68,// toxic liquid
|
|
79
79
|
TrollBlood = 69, // viscous, healing + necrotic
|
|
80
80
|
LiquidShadow = 70, // dense, light-absorbing
|
|
81
|
-
LoveEssence = 71, // psychic liquid
|
|
82
|
-
HateEssence = 72, // psychic liquid, corrosive
|
|
83
|
-
DreamMist = 73, // ethereal + psychic, gas-like liquid
|
|
84
81
|
VoidEssence = 74, // dense, necrotic, reality-bending
|
|
85
82
|
// Powders (solid, fall)
|
|
86
83
|
Sulfur = 75, // yellow powder, flammable, corrosive
|
|
87
|
-
GroundEyeOfNewt = 76, // psychic powder
|
|
88
|
-
GroundBatWing = 77, // ethereal powder
|
|
89
84
|
BoneDust = 78, // necrotic powder
|
|
90
|
-
IronFilings = 79, // kinetic + dense powder, magnetic
|
|
91
|
-
MoonstoneDust = 80, // luminous + ethereal powder
|
|
92
|
-
CrystalDust = 81, // luminous + kinetic powder
|
|
93
85
|
MushroomSpores = 82, // toxic + psychic powder, grows
|
|
94
|
-
DragonScale = 83, // thermal + kinetic, very dense
|
|
95
|
-
PhoenixFeather = 84, // thermal + healing, light
|
|
96
|
-
UnicornHorn = 85, // healing + luminous, rare
|
|
97
|
-
MandrakeRoot = 86, // toxic + psychic, solid root
|
|
98
|
-
SpiderSilk = 87, // kinetic + ethereal, very light
|
|
99
|
-
GraveDust = 88, // necrotic + dense
|
|
100
86
|
StarShard = 89, // luminous + ethereal + kinetic, glowing crystal
|
|
101
87
|
TimeSand = 90, // kinetic + ethereal, reality-bending
|
|
102
88
|
// Gases
|
|
@@ -283,30 +269,14 @@ export const MATERIALS: Record<number, MaterialDef> = {
|
|
|
283
269
|
// --- Liquids ---
|
|
284
270
|
[Material.Ether]: def(65, "Ether", [0.55, 0.45, 0.85, 0.8], { gravity: 1.5, gravityDir: 1, density: 0.7, liquid: true, flammable: true, burnTime: 40, albedo: 0.2, reflectivity: 0.4, brightness: 1.1 }),
|
|
285
271
|
[Material.Blood]: def(66, "Blood", [0.7, 0.05, 0.05, 0.95], { gravity: 2, gravityDir: 1, density: 1.06, liquid: true, albedo: 0.3, reflectivity: 0.2, brightness: 0.8 }),
|
|
286
|
-
[Material.Syrup]: def(67, "Syrup", [0.6, 0.4, 0.15, 0.95], { gravity: 1, gravityDir: 1, density: 1.4, liquid: true, albedo: 0.3, reflectivity: 0.3, brightness: 0.9 }),
|
|
287
|
-
[Material.NightshadeExtract]: def(68, "Nightshade Extract", [0.3, 0.05, 0.35, 0.95], { gravity: 2, gravityDir: 1, density: 1.1, liquid: true, albedo: 0.2, reflectivity: 0.1, brightness: 0.7 }),
|
|
288
272
|
[Material.TrollBlood]: def(69, "Troll Blood", [0.4, 0.2, 0.15, 0.95], { gravity: 2, gravityDir: 1, density: 1.2, liquid: true, albedo: 0.25, reflectivity: 0.15, brightness: 0.8 }),
|
|
289
273
|
[Material.LiquidShadow]: def(70, "Liquid Shadow", [0.05, 0.05, 0.12, 0.98], { gravity: 3, gravityDir: 1, density: 2.0, liquid: true, albedo: 0.0, reflectivity: 0.0, brightness: 0.3 }),
|
|
290
|
-
[Material.LoveEssence]: def(71, "Love Essence", [0.95, 0.4, 0.6, 0.9], { gravity: 1.5, gravityDir: 1, density: 0.9, liquid: true, albedo: 0.3, reflectivity: 0.4, brightness: 1.0 }),
|
|
291
|
-
[Material.HateEssence]: def(72, "Hate Essence", [0.2, 0.1, 0.15, 0.9], { gravity: 2, gravityDir: 1, density: 1.3, liquid: true, albedo: 0.2, reflectivity: 0.2, brightness: 0.7 }),
|
|
292
|
-
[Material.DreamMist]: def(73, "Dream Mist", [0.6, 0.7, 0.95, 0.6], { gravity: 1, gravityDir: -1, density: 0.3, liquid: true, albedo: 0.2, reflectivity: 0.5, brightness: 1.2 }),
|
|
293
274
|
[Material.VoidEssence]: def(74, "Void Essence", [0.1, 0.0, 0.2, 0.95], { gravity: 4, gravityDir: 1, density: 3.0, liquid: true, albedo: 0.0, reflectivity: 0.1, brightness: 0.5 }),
|
|
294
275
|
|
|
295
276
|
// --- Alchemy game: powders (solid, fall) ---
|
|
296
277
|
[Material.Sulfur]: def(75, "Sulfur", [0.9, 0.85, 0.2, 1.0], { gravity: 1, gravityDir: 1, density: 2.0, solid: true, flammable: true, burnTime: 60, albedo: 0.5, reflectivity: 0.1, brightness: 0.9 }),
|
|
297
|
-
[Material.GroundEyeOfNewt]: def(76, "Ground Eye of Newt", [0.5, 0.35, 0.2, 1.0], { gravity: 1, gravityDir: 1, density: 1.2, solid: true, albedo: 0.4, brightness: 0.8 }),
|
|
298
|
-
[Material.GroundBatWing]: def(77, "Ground Bat Wing", [0.3, 0.25, 0.35, 1.0], { gravity: 0.8, gravityDir: 1, density: 0.6, solid: true, albedo: 0.3, brightness: 0.7 }),
|
|
299
278
|
[Material.BoneDust]: def(78, "Bone Dust", [0.85, 0.82, 0.75, 1.0], { gravity: 1, gravityDir: 1, density: 1.5, solid: true, albedo: 0.5, brightness: 0.8 }),
|
|
300
|
-
[Material.IronFilings]: def(79, "Iron Filings", [0.55, 0.55, 0.6, 1.0], { gravity: 1.5, gravityDir: 1, density: 7.8, solid: true, magnetic: true, albedo: 0.5, reflectivity: 0.3 }),
|
|
301
|
-
[Material.MoonstoneDust]: def(80, "Moonstone Dust", [0.8, 0.85, 0.95, 1.0], { gravity: 1, gravityDir: 1, density: 1.0, solid: true, albedo: 0.4, reflectivity: 0.5, brightness: 1.3 }),
|
|
302
|
-
[Material.CrystalDust]: def(81, "Crystal Dust", [0.6, 0.9, 0.95, 1.0], { gravity: 1, gravityDir: 1, density: 2.5, solid: true, albedo: 0.3, reflectivity: 0.6, brightness: 1.2 }),
|
|
303
279
|
[Material.MushroomSpores]: def(82, "Mushroom Spores", [0.6, 0.5, 0.4, 0.9], { gravity: 0.5, gravityDir: 1, density: 0.4, solid: true, albedo: 0.3, brightness: 0.8 }),
|
|
304
|
-
[Material.DragonScale]: def(83, "Dragon Scale", [0.5, 0.15, 0.1, 1.0], { gravity: 1.5, gravityDir: 1, density: 5.0, solid: true, albedo: 0.4, reflectivity: 0.3, brightness: 1.0 }),
|
|
305
|
-
[Material.PhoenixFeather]: def(84, "Phoenix Feather", [0.95, 0.5, 0.15, 0.9], { gravity: 0.3, gravityDir: 1, density: 0.3, solid: true, flammable: true, burnTime: 200, albedo: 0.4, brightness: 1.4 }),
|
|
306
|
-
[Material.UnicornHorn]: def(85, "Unicorn Horn", [0.95, 0.9, 1.0, 1.0], { gravity: 1, gravityDir: 1, density: 1.8, solid: true, albedo: 0.5, reflectivity: 0.5, brightness: 1.5 }),
|
|
307
|
-
[Material.MandrakeRoot]: def(86, "Mandrake Root", [0.5, 0.4, 0.25, 1.0], { gravity: 1, gravityDir: 1, density: 1.3, solid: true, albedo: 0.4, brightness: 0.7 }),
|
|
308
|
-
[Material.SpiderSilk]: def(87, "Spider Silk", [0.9, 0.9, 0.85, 0.8], { gravity: 0.2, gravityDir: 1, density: 0.2, solid: true, albedo: 0.4, reflectivity: 0.2, brightness: 0.9 }),
|
|
309
|
-
[Material.GraveDust]: def(88, "Grave Dust", [0.3, 0.28, 0.25, 1.0], { gravity: 1, gravityDir: 1, density: 1.8, solid: true, albedo: 0.3, brightness: 0.6 }),
|
|
310
280
|
[Material.StarShard]: def(89, "Star Shard", [0.7, 0.8, 1.0, 1.0], { gravity: 1, gravityDir: 1, density: 2.0, solid: true, albedo: 0.3, reflectivity: 0.7, brightness: 1.6 }),
|
|
311
281
|
[Material.TimeSand]: def(90, "Time Sand", [0.4, 0.3, 0.6, 0.9], { gravity: 1, gravityDir: 1, density: 1.5, solid: true, albedo: 0.3, reflectivity: 0.4, brightness: 1.2 }),
|
|
312
282
|
|
|
@@ -2130,10 +2130,6 @@ export class SandWorld {
|
|
|
2130
2130
|
}
|
|
2131
2131
|
}
|
|
2132
2132
|
|
|
2133
|
-
// --- PhoenixFeather near fire/lava: glows brighter, resists burning ---
|
|
2134
|
-
// (flammable but long burnTime already set; here we just let it shimmer)
|
|
2135
|
-
// --- DragonScale: very dense, sinks through liquids (handled by density) ---
|
|
2136
|
-
|
|
2137
2133
|
// --- LiquidShadow + light source (fire/plasma) → dissipates to smoke ---
|
|
2138
2134
|
if (mat === Material.LiquidShadow) {
|
|
2139
2135
|
let lightNi = -1;
|
|
@@ -14,12 +14,10 @@ bun add @downdraft/engine/modules/devtools
|
|
|
14
14
|
- `BaseSceneInspector`
|
|
15
15
|
- `DEVTOOLS_REALM`
|
|
16
16
|
- `DEVTOOLS_SAB_MAX_FEEDS`
|
|
17
|
-
- `DebugOverlay`
|
|
18
17
|
- `DebugRaycast`
|
|
19
18
|
- `DevToolsDataBridge`
|
|
20
19
|
- `GridRenderer`
|
|
21
20
|
- `HeightRulerRenderer`
|
|
22
|
-
-
|
|
23
|
-
- …and 29 more
|
|
21
|
+
- …and 24 more
|
|
24
22
|
|
|
25
23
|
See the [Downdraft engine repository](https://github.com/knackstedt/downdraft) for architecture docs and examples.
|
|
@@ -3,23 +3,21 @@
|
|
|
3
3
|
// engine systems, regardless of which thread they run on.
|
|
4
4
|
//
|
|
5
5
|
// Realm detection determines transport:
|
|
6
|
-
// - Main realm:
|
|
6
|
+
// - Main realm: feeds/commands registered directly on
|
|
7
7
|
// window.__sceneInspector via DevToolsDataBridge.
|
|
8
|
-
// - Worker realm:
|
|
9
|
-
//
|
|
10
|
-
// synchronous reads). Commands are forwarded via worker RPC.
|
|
11
|
-
// declarations are synced to the renderer via a one-time manifest RPC.
|
|
8
|
+
// - Worker realm: feeds/commands registered in a worker-side registry.
|
|
9
|
+
// Data feeds are written to a devtools SAB region (zero-copy,
|
|
10
|
+
// synchronous reads). Commands are forwarded via worker RPC.
|
|
12
11
|
//
|
|
13
12
|
// Plugins import `devtools` from @downdraft/engine/modules/devtools and call
|
|
14
|
-
//
|
|
15
|
-
//
|
|
13
|
+
// registerDataFeed/registerCommand/registerSABStat — the same API works in
|
|
14
|
+
// both realms.
|
|
16
15
|
// ============================================================================
|
|
17
16
|
|
|
18
17
|
import { resourceToken } from "@downdraft/engine/ecs/resource";
|
|
19
18
|
import { createLogger } from "@downdraft/engine/util/logger";
|
|
20
19
|
import type { DebugViewDescriptor } from "./debug-view-descriptors";
|
|
21
20
|
import { BUILTIN_VIEW_DESCRIPTORS } from "./debug-view-descriptors";
|
|
22
|
-
import type { IDevToolsOverlayToggle, IDevToolsPanelExtension } from "./types";
|
|
23
21
|
|
|
24
22
|
const log = createLogger("info");
|
|
25
23
|
|
|
@@ -56,7 +54,7 @@ export const DEVTOOLS_REALM = detectRealm();
|
|
|
56
54
|
// synchronously. No RPC polling needed.
|
|
57
55
|
//
|
|
58
56
|
// Layout:
|
|
59
|
-
// [0..3] manifestVersion: Uint32 (bumped when
|
|
57
|
+
// [0..3] manifestVersion: Uint32 (bumped when feeds/commands change)
|
|
60
58
|
// [4..7] feedCount: Uint32
|
|
61
59
|
// [8..] feed entries: each is [lengthBytes: Uint32][JSON bytes...]
|
|
62
60
|
// (lengthBytes = 0 means "not yet written" / "unavailable")
|
|
@@ -124,11 +122,7 @@ export interface DevToolsAPI {
|
|
|
124
122
|
/** Current realm ("main" or "worker"). */
|
|
125
123
|
readonly realm: "main" | "worker";
|
|
126
124
|
|
|
127
|
-
// ---
|
|
128
|
-
registerPanel(ext: IDevToolsPanelExtension): void;
|
|
129
|
-
registerOverlayToggle(toggle: IDevToolsOverlayToggle): void;
|
|
130
|
-
|
|
131
|
-
// --- Data feeds (read by panel, realm chooses transport) ---
|
|
125
|
+
// --- Data feeds (read by devtools frontends, realm chooses transport) ---
|
|
132
126
|
// In main realm: fn is called directly when the panel queries __sceneInspector.
|
|
133
127
|
// In worker realm: fn is called each tick (or at writeRate), result JSON-serialized
|
|
134
128
|
// to the devtools SAB. Renderer reads synchronously from SAB.
|
|
@@ -165,7 +159,7 @@ export interface DevToolsAPI {
|
|
|
165
159
|
getViews(): DebugViewDescriptor[];
|
|
166
160
|
|
|
167
161
|
// --- Manifest (renderer fetches from worker via RPC) ---
|
|
168
|
-
/** Get the full manifest:
|
|
162
|
+
/** Get the full manifest: data feed names, commands, SAB stats. */
|
|
169
163
|
getManifest(): DevToolsManifest;
|
|
170
164
|
|
|
171
165
|
// --- Worker tick hook ---
|
|
@@ -178,8 +172,6 @@ export interface DevToolsAPI {
|
|
|
178
172
|
}
|
|
179
173
|
|
|
180
174
|
export interface DevToolsManifest {
|
|
181
|
-
panels: IDevToolsPanelExtension[];
|
|
182
|
-
toggles: IDevToolsOverlayToggle[];
|
|
183
175
|
dataFeeds: { name: string; feedIndex: number; writeRateHz: number }[];
|
|
184
176
|
commands: string[];
|
|
185
177
|
sabStats: DevToolsSABStat[];
|
|
@@ -200,8 +192,6 @@ interface RegisteredDataFeed {
|
|
|
200
192
|
class DevToolsAPIImpl implements DevToolsAPI {
|
|
201
193
|
readonly realm: "main" | "worker";
|
|
202
194
|
|
|
203
|
-
private panels = new Map<string, IDevToolsPanelExtension>();
|
|
204
|
-
private toggles = new Map<string, IDevToolsOverlayToggle>();
|
|
205
195
|
private dataFeeds = new Map<string, RegisteredDataFeed>();
|
|
206
196
|
private dataFeedNames: string[] = []; // ordered list for feedIndex assignment
|
|
207
197
|
private commands = new Map<string, (...args: any[]) => any>();
|
|
@@ -223,16 +213,6 @@ class DevToolsAPIImpl implements DevToolsAPI {
|
|
|
223
213
|
this.realm = realm;
|
|
224
214
|
}
|
|
225
215
|
|
|
226
|
-
registerPanel(ext: IDevToolsPanelExtension): void {
|
|
227
|
-
this.panels.set(ext.id, ext);
|
|
228
|
-
this.bumpManifest();
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
registerOverlayToggle(toggle: IDevToolsOverlayToggle): void {
|
|
232
|
-
this.toggles.set(toggle.id, toggle);
|
|
233
|
-
this.bumpManifest();
|
|
234
|
-
}
|
|
235
|
-
|
|
236
216
|
registerDataFeed(name: string, fn: () => any, writeRateHz: number = 10): void {
|
|
237
217
|
if (this.dataFeeds.has(name)) {
|
|
238
218
|
// Update fn/rate, keep feedIndex and hasWritten
|
|
@@ -318,8 +298,6 @@ class DevToolsAPIImpl implements DevToolsAPI {
|
|
|
318
298
|
|
|
319
299
|
getManifest(): DevToolsManifest {
|
|
320
300
|
return {
|
|
321
|
-
panels: Array.from(this.panels.values()),
|
|
322
|
-
toggles: Array.from(this.toggles.values()),
|
|
323
301
|
dataFeeds: this.dataFeedNames.map((name, i) => ({
|
|
324
302
|
name,
|
|
325
303
|
feedIndex: i,
|
|
@@ -414,8 +392,6 @@ class DevToolsAPIImpl implements DevToolsAPI {
|
|
|
414
392
|
}
|
|
415
393
|
|
|
416
394
|
clear(): void {
|
|
417
|
-
this.panels.clear();
|
|
418
|
-
this.toggles.clear();
|
|
419
395
|
this.dataFeeds.clear();
|
|
420
396
|
this.dataFeedNames = [];
|
|
421
397
|
this.commands.clear();
|
|
@@ -443,7 +419,7 @@ const _devtools = new DevToolsAPIImpl(DEVTOOLS_REALM);
|
|
|
443
419
|
* the appropriate transport for data feeds (direct call vs SAB) and
|
|
444
420
|
* commands (direct call vs RPC forwarding).
|
|
445
421
|
*
|
|
446
|
-
* Plugins import this and call
|
|
422
|
+
* Plugins import this and call registerDataFeed/registerCommand.
|
|
447
423
|
* The same code works in both realms.
|
|
448
424
|
*/
|
|
449
425
|
export const devtools: DevToolsAPI = _devtools;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// fonts.ts — bind the host's real system fonts into the devtools dock doc.
|
|
3
|
+
//
|
|
4
|
+
// The OSR rasterizer ships a single embedded DejaVu Sans as the fallback for
|
|
5
|
+
// every generic family — legible, but `monospace` resolves to it too, so the
|
|
6
|
+
// console/REPL/tree read as proportional sans. Same fix as cruiser's
|
|
7
|
+
// libraries/app-shell/fonts.ts: resolve the user's actual sans + mono faces
|
|
8
|
+
// via fontconfig (fc-match -f %{file}), register them as `ui://` blobs on the
|
|
9
|
+
// HtmlUiHost before mount, and emit @font-face rules the dock stylesheet
|
|
10
|
+
// binds ahead of the generic fallbacks. Any failure (no fc-match, non-Linux
|
|
11
|
+
// host, unreadable file) leaves the embedded fallback in place.
|
|
12
|
+
// ============================================================================
|
|
13
|
+
|
|
14
|
+
import { execFileSync } from "node:child_process";
|
|
15
|
+
import { readFileSync } from "node:fs";
|
|
16
|
+
|
|
17
|
+
/** Resource sink — HtmlUiHost.registerResource, or anything shaped like it. */
|
|
18
|
+
type ResourceSink = { registerResource(url: string, bytes: Uint8Array | ArrayBuffer): void };
|
|
19
|
+
|
|
20
|
+
interface Face { family: string; pattern: string; urlName: string; weight: number }
|
|
21
|
+
|
|
22
|
+
// Blitz-dom only fetches a @font-face source whose format is detectable —
|
|
23
|
+
// either an explicit format() hint or a file extension on the url — so the
|
|
24
|
+
// urls carry the resolved file's real extension.
|
|
25
|
+
const FACES: Face[] = [
|
|
26
|
+
{ family: "DevTools Sans", pattern: "sans-serif", urlName: "sans", weight: 400 },
|
|
27
|
+
{ family: "DevTools Sans", pattern: "sans-serif:weight=bold", urlName: "sans-bold", weight: 700 },
|
|
28
|
+
{ family: "DevTools Mono", pattern: "monospace", urlName: "mono", weight: 400 },
|
|
29
|
+
{ family: "DevTools Mono", pattern: "monospace:weight=bold", urlName: "mono-bold", weight: 700 },
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
/** `fc-match -f %{file}` for a fontconfig pattern; null when fc-match is
|
|
33
|
+
* absent or the match isn't a font file Blitz can load. */
|
|
34
|
+
function fcFontFile(pattern: string): string | null {
|
|
35
|
+
try {
|
|
36
|
+
const p = execFileSync("fc-match", ["-f", "%{file}", pattern], { timeout: 5000 }).toString().trim();
|
|
37
|
+
return /\.(ttf|otf|woff2?)$/i.test(p) ? p : null;
|
|
38
|
+
} catch {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function fontExt(path: string): string {
|
|
44
|
+
return path.match(/\.(ttf|otf|woff2?)$/i)?.[0] ?? ".ttf";
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Register system sans + mono faces as `ui://` resources and return the
|
|
49
|
+
* @font-face CSS to prepend to the dock stylesheet. Returns "" when nothing
|
|
50
|
+
* registered — the stylesheet's named families then fail to resolve and the
|
|
51
|
+
* generic fallbacks (embedded DejaVu) apply.
|
|
52
|
+
*
|
|
53
|
+
* Call before mount — the doc backend applies resources synchronously and in
|
|
54
|
+
* order, so the first doc resolve sees them.
|
|
55
|
+
*/
|
|
56
|
+
export function registerDevtoolsFonts(sink: ResourceSink): string {
|
|
57
|
+
try {
|
|
58
|
+
// Both weights of a family are required: a 700 rule pointing at the
|
|
59
|
+
// regular file keeps bold text in-family instead of falling back.
|
|
60
|
+
const files = new Map<string, string>();
|
|
61
|
+
for (const f of FACES) files.set(f.urlName, fcFontFile(f.pattern) ?? "");
|
|
62
|
+
if (!files.get("sans")) return "";
|
|
63
|
+
if (!files.get("mono")) files.set("mono", files.get("sans")!);
|
|
64
|
+
if (!files.get("sans-bold")) files.set("sans-bold", files.get("sans")!);
|
|
65
|
+
if (!files.get("mono-bold")) files.set("mono-bold", files.get("mono")!);
|
|
66
|
+
let css = "";
|
|
67
|
+
for (const f of FACES) {
|
|
68
|
+
const file = files.get(f.urlName)!;
|
|
69
|
+
const url = `ui://devtools-fonts/${f.urlName}${fontExt(file)}`;
|
|
70
|
+
sink.registerResource(url, readFileSync(file));
|
|
71
|
+
css += `@font-face { font-family:'${f.family}'; font-weight:${f.weight}; src:url('${url}'); }\n`;
|
|
72
|
+
}
|
|
73
|
+
return css;
|
|
74
|
+
} catch {
|
|
75
|
+
// fc-match absent / unreadable font file — the embedded fallback applies.
|
|
76
|
+
return "";
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -16,12 +16,13 @@ import { createLogger } from "@downdraft/engine";
|
|
|
16
16
|
import { CdpBridge, DevtoolsBackend } from "@downdraft/engine/libraries/devtools";
|
|
17
17
|
import { HtmlUiHost, type OsrDomEvent, type UiPanelHandle } from "@downdraft/engine/modules/html-ui";
|
|
18
18
|
|
|
19
|
+
import { registerDevtoolsFonts } from "./fonts";
|
|
19
20
|
import { ConsolePanel } from "./panels/console";
|
|
20
21
|
import { ElementsPanel } from "./panels/elements";
|
|
21
22
|
import { GpuPanel } from "./panels/gpu";
|
|
22
23
|
import { PerformancePanel } from "./panels/performance";
|
|
23
24
|
import { SnapshotPanel } from "./panels/snapshot";
|
|
24
|
-
import type
|
|
25
|
+
import { esc, type DtPanel, type DtPanelCtx } from "./panels/types";
|
|
25
26
|
import { DEVTOOLS_CSS } from "./theme";
|
|
26
27
|
|
|
27
28
|
const log = createLogger("info");
|
|
@@ -35,12 +36,17 @@ export interface DevtoolsUiSurface {
|
|
|
35
36
|
z?: number;
|
|
36
37
|
scale?: number;
|
|
37
38
|
maxFps?: number;
|
|
39
|
+
/** Ease wheel deltas at pump cadence (html-ui PanelSpec.smoothWheel). */
|
|
40
|
+
smoothWheel?: boolean;
|
|
38
41
|
interactive?: boolean;
|
|
39
42
|
onEvent?: (ev: OsrDomEvent) => void;
|
|
40
43
|
}): UiPanelHandle;
|
|
41
44
|
/** Doc-backend capabilities — panels use incremental DOM ops only when
|
|
42
45
|
* the underlying cdylib provides them. */
|
|
43
46
|
docCaps?(): { incrementalDom?: boolean };
|
|
47
|
+
/** Register a `ui://` resource (fonts) served to the dock doc — must run
|
|
48
|
+
* before mount. Absent on surfaces that don't own a resource table. */
|
|
49
|
+
registerResource?(url: string, bytes: Uint8Array | ArrayBuffer): void;
|
|
44
50
|
}
|
|
45
51
|
|
|
46
52
|
export interface BlitzDevtoolsOptions {
|
|
@@ -83,6 +89,9 @@ export class BlitzDevtoolsHost {
|
|
|
83
89
|
private panelCtx: DtPanelCtx;
|
|
84
90
|
private lastPanelRender = -1e9;
|
|
85
91
|
private lastSlowLog = 0;
|
|
92
|
+
/** @font-face css from registerDevtoolsFonts — "" means "embedded font
|
|
93
|
+
* fallback", null means "not yet attempted". */
|
|
94
|
+
private fontCss: string | null = null;
|
|
86
95
|
|
|
87
96
|
constructor(opts: BlitzDevtoolsOptions) {
|
|
88
97
|
this.opts = opts;
|
|
@@ -154,19 +163,41 @@ export class BlitzDevtoolsHost {
|
|
|
154
163
|
|
|
155
164
|
show(): void {
|
|
156
165
|
if (!this._ready || this.visible || this.disposed) return;
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
166
|
+
// Register real system fonts once per host — ui:// resources persist on
|
|
167
|
+
// the surface across dock remounts, and must land before the doc mounts
|
|
168
|
+
// so the first resolve sees them (embedded DejaVu fallback otherwise).
|
|
169
|
+
if (this.fontCss === null) {
|
|
170
|
+
const ui = this.opts.ui as DevtoolsUiSurface & { host?: HtmlUiHost };
|
|
171
|
+
const rr = ui.registerResource?.bind(ui)
|
|
172
|
+
?? (ui.host ? ui.host.registerResource.bind(ui.host) : undefined);
|
|
173
|
+
this.fontCss = rr ? registerDevtoolsFonts({ registerResource: rr }) : "";
|
|
174
|
+
}
|
|
175
|
+
try {
|
|
176
|
+
this.panel = this.opts.ui.mount(this.shellHtml(), {
|
|
177
|
+
id: "devtools",
|
|
178
|
+
rect: this.dockRect(),
|
|
179
|
+
// Above game overlays — menus/loading screens shouldn't bury the dock.
|
|
180
|
+
z: 1000,
|
|
181
|
+
// Native-res raster: the vendored glifo/FreeType path grid-fits hinted
|
|
182
|
+
// glyph stems to whole pixels — supersample factors >1 just blur that
|
|
183
|
+
// back out on downscale (and cost 2.25x the texture memory).
|
|
184
|
+
scale: 1,
|
|
185
|
+
// Ease wheel detents at pump cadence instead of jumping per event.
|
|
186
|
+
smoothWheel: true,
|
|
187
|
+
interactive: true,
|
|
188
|
+
// Devtools UI doesn't need vsync-rate raster — cap it so mutation
|
|
189
|
+
// bursts (console streams, snapshot refreshes) coalesce into ~66ms
|
|
190
|
+
// rasters instead of one raster per message.
|
|
191
|
+
maxFps: 15,
|
|
192
|
+
onEvent: (ev) => this.handleDocEvent(ev),
|
|
193
|
+
});
|
|
194
|
+
} catch (err) {
|
|
195
|
+
// Mount fails when the ui worker is dead — stay hidden so the next F12
|
|
196
|
+
// retries cleanly instead of wedging on a half-mounted doc.
|
|
197
|
+
this.panel = null;
|
|
198
|
+
log.error("blitz-devtools-host", `dock mount failed: ${err}`);
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
170
201
|
this.syncProviders();
|
|
171
202
|
this.renderTabs();
|
|
172
203
|
this.activate(this.activeId);
|
|
@@ -190,8 +221,18 @@ export class BlitzDevtoolsHost {
|
|
|
190
221
|
}
|
|
191
222
|
const t1 = performance.now();
|
|
192
223
|
if (!this.visible) return;
|
|
193
|
-
|
|
194
|
-
this.
|
|
224
|
+
// Guarded like backend.update() above — a throwing panel render can't be
|
|
225
|
+
// allowed to kill the game frame hook that drives this pump.
|
|
226
|
+
try {
|
|
227
|
+
this.syncProviders();
|
|
228
|
+
this.flush();
|
|
229
|
+
} catch (err) {
|
|
230
|
+
const tn = performance.now();
|
|
231
|
+
if (tn - this.lastSlowLog > 1000) {
|
|
232
|
+
this.lastSlowLog = tn;
|
|
233
|
+
log.warn("blitz-devtools-host", `panel render error: ${err}`);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
195
236
|
const t2 = performance.now();
|
|
196
237
|
// ≤1/sec — these diagnostics used to feed the console panel's logger
|
|
197
238
|
// sink, and each row forced another dock re-render: a self-feeding
|
|
@@ -244,12 +285,14 @@ export class BlitzDevtoolsHost {
|
|
|
244
285
|
// ── Dock layout ──
|
|
245
286
|
|
|
246
287
|
private dockRect() {
|
|
247
|
-
|
|
248
|
-
|
|
288
|
+
// A tiny surface (or a pathological resize event) must not produce a
|
|
289
|
+
// negative height — keep at least enough room for the tab strip.
|
|
290
|
+
const h = Math.max(48, Math.min(this.dockH, this.surfaceH - 60));
|
|
291
|
+
return { x: 0, y: Math.max(0, this.surfaceH - h), w: this.surfaceW, h };
|
|
249
292
|
}
|
|
250
293
|
|
|
251
294
|
private shellHtml(): string {
|
|
252
|
-
return `<html><head><style>${DEVTOOLS_CSS}</style></head><body>
|
|
295
|
+
return `<html><head><style>${this.fontCss ?? ""}${DEVTOOLS_CSS}</style></head><body>
|
|
253
296
|
<div id="dt-root">
|
|
254
297
|
<div id="dt-grip" title="drag to resize"></div>
|
|
255
298
|
<div id="dt-chrome">
|
|
@@ -266,8 +309,11 @@ export class BlitzDevtoolsHost {
|
|
|
266
309
|
|
|
267
310
|
private renderTabs(): void {
|
|
268
311
|
if (!this.panel) return;
|
|
312
|
+
// esc() both interpolations — provider names feed panel ids/titles and
|
|
313
|
+
// arrive as raw strings (a game registering "x\"><img" would otherwise
|
|
314
|
+
// inject markup into the tab strip).
|
|
269
315
|
const tabs = [...this.panels.values()].map((p) =>
|
|
270
|
-
`<span class="tab${p.id === this.activeId ? " active" : ""}" data-action="dt.tab" data-tab="${p.id}">${p.title}</span>`
|
|
316
|
+
`<span class="tab${p.id === this.activeId ? " active" : ""}" data-action="dt.tab" data-tab="${esc(p.id)}">${esc(p.title)}</span>`
|
|
271
317
|
).join("");
|
|
272
318
|
this.panel.setInnerHtml("#dt-tabs",
|
|
273
319
|
`<span class="brand">downdraft</span>${tabs}<span class="spacer"></span>
|
|
@@ -280,13 +326,21 @@ export class BlitzDevtoolsHost {
|
|
|
280
326
|
this.activeId = id;
|
|
281
327
|
const p = this.panels.get(id)!;
|
|
282
328
|
this.renderTabs();
|
|
283
|
-
|
|
284
|
-
|
|
329
|
+
try {
|
|
330
|
+
this.panel.setInnerHtml("#dt-top", p.renderTop?.() ?? "");
|
|
331
|
+
this.panel.setInnerHtml("#dt-bottom", p.renderBottom?.() ?? "");
|
|
332
|
+
} catch (err) {
|
|
333
|
+
log.warn("blitz-devtools-host", `panel ${id} chrome render error: ${err}`);
|
|
334
|
+
}
|
|
285
335
|
p.dirty = true;
|
|
286
336
|
p.shellDirty = false;
|
|
287
|
-
this.panel.setInnerHtml("#dt-body",
|
|
288
|
-
p.afterRender?.();
|
|
289
|
-
|
|
337
|
+
this.panel.setInnerHtml("#dt-body", this.safeBody(p));
|
|
338
|
+
try { p.afterRender?.(); } catch (err) {
|
|
339
|
+
log.warn("blitz-devtools-host", `panel ${id} afterRender error: ${err}`);
|
|
340
|
+
}
|
|
341
|
+
try { p.activate?.(); } catch (err) {
|
|
342
|
+
log.warn("blitz-devtools-host", `panel ${id} activate error: ${err}`);
|
|
343
|
+
}
|
|
290
344
|
// Only the snapshot tab being viewed gets periodic provider refreshes;
|
|
291
345
|
// fixed panels suspend auto-refresh entirely (manual Refresh still works).
|
|
292
346
|
this.backend.providerWatch = p.providerSlot ?? -1;
|
|
@@ -346,10 +400,25 @@ export class BlitzDevtoolsHost {
|
|
|
346
400
|
}
|
|
347
401
|
if (p.dirty) {
|
|
348
402
|
p.dirty = false;
|
|
349
|
-
|
|
350
|
-
|
|
403
|
+
let handled = false;
|
|
404
|
+
try { handled = p.flushDom?.() === true; } catch (err) {
|
|
405
|
+
log.warn("blitz-devtools-host", `panel ${p.id} flushDom error: ${err}`);
|
|
406
|
+
}
|
|
407
|
+
if (!handled) this.panel.setInnerHtml("#dt-body", this.safeBody(p));
|
|
408
|
+
try { p.afterRender?.(); } catch (err) {
|
|
409
|
+
log.warn("blitz-devtools-host", `panel ${p.id} afterRender error: ${err}`);
|
|
351
410
|
}
|
|
352
|
-
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/** renderBody that can't take the dock down — the error surfaces in-panel
|
|
415
|
+
* (and the next push retries since dirty just flips back on). */
|
|
416
|
+
private safeBody(p: DtPanel): string {
|
|
417
|
+
try {
|
|
418
|
+
return p.renderBody();
|
|
419
|
+
} catch (err) {
|
|
420
|
+
log.warn("blitz-devtools-host", `panel ${p.id} renderBody error: ${err}`);
|
|
421
|
+
return `<div class="status-banner error">panel render error: ${esc(String(err))}</div>`;
|
|
353
422
|
}
|
|
354
423
|
}
|
|
355
424
|
|
|
@@ -387,20 +456,26 @@ export class BlitzDevtoolsHost {
|
|
|
387
456
|
}
|
|
388
457
|
}
|
|
389
458
|
|
|
459
|
+
// Left-click only — routing actions on contextmenu meant a right-click
|
|
460
|
+
// on a tab/button fired it instead of just focusing.
|
|
390
461
|
const action = ev.d?.action;
|
|
391
|
-
if (
|
|
462
|
+
if (ev.t === "click" && action) {
|
|
392
463
|
if (action === "dt.tab") {
|
|
393
464
|
const id = ev.d!.tab;
|
|
394
465
|
if (id) this.activate(id);
|
|
395
466
|
return;
|
|
396
467
|
}
|
|
397
468
|
if (action === "dt.close") { this.hide(); return; }
|
|
398
|
-
this.panels.get(this.activeId)?.onAction?.(ev.d!, ev);
|
|
469
|
+
try { this.panels.get(this.activeId)?.onAction?.(ev.d!, ev); } catch (err) {
|
|
470
|
+
log.warn("blitz-devtools-host", `panel action error: ${err}`);
|
|
471
|
+
}
|
|
399
472
|
return;
|
|
400
473
|
}
|
|
401
474
|
|
|
402
475
|
// Everything else (input, keydown/up, scroll, focus…) → active panel.
|
|
403
|
-
this.panels.get(this.activeId)?.onEvent?.(ev);
|
|
476
|
+
try { this.panels.get(this.activeId)?.onEvent?.(ev); } catch (err) {
|
|
477
|
+
log.warn("blitz-devtools-host", `panel event error: ${err}`);
|
|
478
|
+
}
|
|
404
479
|
}
|
|
405
480
|
}
|
|
406
481
|
|
|
@@ -426,5 +501,6 @@ export function createSelfHostedDevtoolsUi(ctx: RendererModuleContext): Devtools
|
|
|
426
501
|
host,
|
|
427
502
|
mount: (markup, spec) => host.mount({ ...spec, html: markup }),
|
|
428
503
|
docCaps: () => host.docCaps,
|
|
504
|
+
registerResource: (url, bytes) => host.registerResource(url, bytes),
|
|
429
505
|
};
|
|
430
506
|
}
|
|
@@ -115,8 +115,8 @@ export class ConsolePanel implements DtPanel {
|
|
|
115
115
|
|
|
116
116
|
onBackendEvent(event: string, data: unknown): void {
|
|
117
117
|
if (event === "console") {
|
|
118
|
-
const e = data as { text?:
|
|
119
|
-
this.rows.push({ text: e
|
|
118
|
+
const e = data as { text?: unknown; severity?: number; thread?: string };
|
|
119
|
+
this.rows.push({ text: String(e?.text ?? ""), severity: e?.severity ?? 0, thread: e?.thread ?? "main", seq: this.seq++ });
|
|
120
120
|
if (this.rows.length > MAX_ROWS) this.rows.splice(0, this.rows.length - MAX_ROWS);
|
|
121
121
|
this.dirty = true;
|
|
122
122
|
} else if (event === "console.clear") {
|
|
@@ -125,15 +125,24 @@ export class ConsolePanel implements DtPanel {
|
|
|
125
125
|
this.dirty = true;
|
|
126
126
|
} else if (event === "threads") {
|
|
127
127
|
const list = (data as ThreadInfo[]) ?? [];
|
|
128
|
-
|
|
129
|
-
|
|
128
|
+
const next = [{ id: "main", name: "main", kind: 0 }, ...list.filter((t) => t.id !== "main")];
|
|
129
|
+
// Thread chips live in the bottom chrome — flag it so the row
|
|
130
|
+
// actually re-renders when a worker appears or disappears.
|
|
131
|
+
const changed = next.length !== this.threads.length
|
|
132
|
+
|| next.some((t, i) => t.id !== this.threads[i]!.id || t.name !== this.threads[i]!.name);
|
|
133
|
+
this.threads = next;
|
|
134
|
+
if (!this.threads.some((t) => t.id === this.replThread)) {
|
|
135
|
+
this.replThread = "main";
|
|
136
|
+
this.shellDirty = true;
|
|
137
|
+
}
|
|
138
|
+
if (changed) this.shellDirty = true;
|
|
130
139
|
}
|
|
131
140
|
}
|
|
132
141
|
|
|
133
142
|
onAction(data: Record<string, string>, _ev: OsrDomEvent): void {
|
|
134
143
|
const act = data.action;
|
|
135
144
|
if (act === "console.clear") {
|
|
136
|
-
void this.ctx.call("console.clear");
|
|
145
|
+
void this.ctx.call("console.clear").catch(() => {});
|
|
137
146
|
} else if (act === "console.sev") {
|
|
138
147
|
this.minSev = (this.minSev + 1) % SEV_NAMES.length;
|
|
139
148
|
this.needsFullRender = true;
|
|
@@ -176,8 +185,11 @@ export class ConsolePanel implements DtPanel {
|
|
|
176
185
|
} else if (ev.k === "ArrowUp" && this.histIdx > 0) {
|
|
177
186
|
this.replValue = this.history[--this.histIdx]!;
|
|
178
187
|
this.ctx.setValue("[data-dt=repl]", this.replValue);
|
|
179
|
-
} else if (ev.k === "ArrowDown" && this.histIdx < this.history.length
|
|
180
|
-
|
|
188
|
+
} else if (ev.k === "ArrowDown" && this.histIdx >= 0 && this.histIdx < this.history.length) {
|
|
189
|
+
// Stepping past the newest entry restores the empty input —
|
|
190
|
+
// previously the last history item was unreachable-to-leave.
|
|
191
|
+
this.histIdx++;
|
|
192
|
+
this.replValue = this.histIdx < this.history.length ? this.history[this.histIdx]! : "";
|
|
181
193
|
this.ctx.setValue("[data-dt=repl]", this.replValue);
|
|
182
194
|
}
|
|
183
195
|
}
|
|
@@ -212,7 +224,7 @@ export class ConsolePanel implements DtPanel {
|
|
|
212
224
|
} else {
|
|
213
225
|
const r = res?.result;
|
|
214
226
|
this.addRow({
|
|
215
|
-
text: typeof r === "string" ? r : JSON.stringify(r, null, 2),
|
|
227
|
+
text: typeof r === "string" ? r : (r === undefined ? "undefined" : JSON.stringify(r, null, 2)),
|
|
216
228
|
severity: 0, thread: this.replThread,
|
|
217
229
|
});
|
|
218
230
|
}
|