@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
|
@@ -235,6 +235,21 @@ export class UniversalPhysicsAPI {
|
|
|
235
235
|
setIntegrationDt(realmId, dt) {
|
|
236
236
|
this.backend.setIntegrationDt(realmId, dt);
|
|
237
237
|
}
|
|
238
|
+
/**
|
|
239
|
+
* Change gravity across all realms (applies to near/mid/far). Best-effort:
|
|
240
|
+
* backends that can't mutate realm gravity omit `setRealmGravity`, in which
|
|
241
|
+
* case callers should fall back to per-body `gravityScale` in BodyDesc.
|
|
242
|
+
*/
|
|
243
|
+
setGravity(gravity) {
|
|
244
|
+
[RealmTier.Near, RealmTier.Mid, RealmTier.Far].forEach((tier) => {
|
|
245
|
+
try {
|
|
246
|
+
const realm = this.realmManager.getRealm(tier);
|
|
247
|
+
this.backend.setRealmGravity?.(realm.id, gravity);
|
|
248
|
+
}
|
|
249
|
+
catch { /* realm tier not initialized */ }
|
|
250
|
+
});
|
|
251
|
+
this.config.gravity = gravity;
|
|
252
|
+
}
|
|
238
253
|
// --- Realm ---
|
|
239
254
|
getRealmTier(body) {
|
|
240
255
|
return this.realmManager.getRealmForBody(body);
|
|
@@ -442,6 +442,21 @@ export class RapierPhysicsBackend {
|
|
|
442
442
|
this.lib.setIntegrationDt(realmId, dt);
|
|
443
443
|
}
|
|
444
444
|
}
|
|
445
|
+
/**
|
|
446
|
+
* Change a live realm's gravity. Updates the stored realm config (the
|
|
447
|
+
* no-lib JS fallback stepper reads it) and forwards to the lib when it
|
|
448
|
+
* supports runtime gravity (the WASM Rapier world.gravity is mutable; the
|
|
449
|
+
* native FFI lib may not implement it — callers should treat this as
|
|
450
|
+
* best-effort).
|
|
451
|
+
*/
|
|
452
|
+
setRealmGravity(realmId, gravity) {
|
|
453
|
+
const realm = this.realms.get(realmId);
|
|
454
|
+
if (realm)
|
|
455
|
+
realm.config.gravity = gravity;
|
|
456
|
+
if (this.lib && this.lib.setRealmGravity) {
|
|
457
|
+
this.lib.setRealmGravity(realmId, gravity);
|
|
458
|
+
}
|
|
459
|
+
}
|
|
445
460
|
setSleepThresholds(realmId, linearThreshold, angularThreshold) {
|
|
446
461
|
if (this.lib) {
|
|
447
462
|
this.lib.setSleepThresholds(realmId, linearThreshold, angularThreshold);
|
|
@@ -154,6 +154,11 @@ async function doLoadPhysicsLib() {
|
|
|
154
154
|
entityByBodyHandle.delete(id);
|
|
155
155
|
bodyIdByHandle.delete(id);
|
|
156
156
|
},
|
|
157
|
+
setRealmGravity(id, gravity) {
|
|
158
|
+
const world = realms.get(id);
|
|
159
|
+
if (world)
|
|
160
|
+
world.gravity = { x: gravity[0], y: gravity[1], z: gravity[2] };
|
|
161
|
+
},
|
|
157
162
|
createBody(realmId, bodyId, desc, entity) {
|
|
158
163
|
const world = realms.get(realmId);
|
|
159
164
|
if (!world)
|
|
@@ -443,9 +443,7 @@ export class PostProcessStack {
|
|
|
443
443
|
createGlyphAtlas() {
|
|
444
444
|
const chars = " .:-=+*#%@";
|
|
445
445
|
const gw = 16, gh = 16, cols = chars.length;
|
|
446
|
-
const canvas =
|
|
447
|
-
canvas.width = cols * gw;
|
|
448
|
-
canvas.height = gh;
|
|
446
|
+
const canvas = new OffscreenCanvas(cols * gw, gh);
|
|
449
447
|
const ctx = canvas.getContext("2d");
|
|
450
448
|
ctx.fillStyle = "black";
|
|
451
449
|
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
@@ -72,32 +72,18 @@ export var Material;
|
|
|
72
72
|
// --- Ingredients ---
|
|
73
73
|
// Ice (frozen water — produced by cooling)
|
|
74
74
|
Material[Material["Ice"] = 64] = "Ice";
|
|
75
|
+
// (IDs 67–68, 71–73, 76–77, 79–81, 83–88 were inert alchemy materials —
|
|
76
|
+
// retired; keep the gaps so old saves don't remap onto new materials.)
|
|
75
77
|
// Liquids
|
|
76
78
|
Material[Material["Ether"] = 65] = "Ether";
|
|
77
79
|
Material[Material["Blood"] = 66] = "Blood";
|
|
78
|
-
Material[Material["Syrup"] = 67] = "Syrup";
|
|
79
|
-
Material[Material["NightshadeExtract"] = 68] = "NightshadeExtract";
|
|
80
80
|
Material[Material["TrollBlood"] = 69] = "TrollBlood";
|
|
81
81
|
Material[Material["LiquidShadow"] = 70] = "LiquidShadow";
|
|
82
|
-
Material[Material["LoveEssence"] = 71] = "LoveEssence";
|
|
83
|
-
Material[Material["HateEssence"] = 72] = "HateEssence";
|
|
84
|
-
Material[Material["DreamMist"] = 73] = "DreamMist";
|
|
85
82
|
Material[Material["VoidEssence"] = 74] = "VoidEssence";
|
|
86
83
|
// Powders (solid, fall)
|
|
87
84
|
Material[Material["Sulfur"] = 75] = "Sulfur";
|
|
88
|
-
Material[Material["GroundEyeOfNewt"] = 76] = "GroundEyeOfNewt";
|
|
89
|
-
Material[Material["GroundBatWing"] = 77] = "GroundBatWing";
|
|
90
85
|
Material[Material["BoneDust"] = 78] = "BoneDust";
|
|
91
|
-
Material[Material["IronFilings"] = 79] = "IronFilings";
|
|
92
|
-
Material[Material["MoonstoneDust"] = 80] = "MoonstoneDust";
|
|
93
|
-
Material[Material["CrystalDust"] = 81] = "CrystalDust";
|
|
94
86
|
Material[Material["MushroomSpores"] = 82] = "MushroomSpores";
|
|
95
|
-
Material[Material["DragonScale"] = 83] = "DragonScale";
|
|
96
|
-
Material[Material["PhoenixFeather"] = 84] = "PhoenixFeather";
|
|
97
|
-
Material[Material["UnicornHorn"] = 85] = "UnicornHorn";
|
|
98
|
-
Material[Material["MandrakeRoot"] = 86] = "MandrakeRoot";
|
|
99
|
-
Material[Material["SpiderSilk"] = 87] = "SpiderSilk";
|
|
100
|
-
Material[Material["GraveDust"] = 88] = "GraveDust";
|
|
101
87
|
Material[Material["StarShard"] = 89] = "StarShard";
|
|
102
88
|
Material[Material["TimeSand"] = 90] = "TimeSand";
|
|
103
89
|
// Gases
|
|
@@ -232,29 +218,13 @@ export const MATERIALS = {
|
|
|
232
218
|
// --- Liquids ---
|
|
233
219
|
[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 }),
|
|
234
220
|
[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 }),
|
|
235
|
-
[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 }),
|
|
236
|
-
[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 }),
|
|
237
221
|
[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 }),
|
|
238
222
|
[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 }),
|
|
239
|
-
[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 }),
|
|
240
|
-
[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 }),
|
|
241
|
-
[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 }),
|
|
242
223
|
[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 }),
|
|
243
224
|
// --- Alchemy game: powders (solid, fall) ---
|
|
244
225
|
[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 }),
|
|
245
|
-
[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 }),
|
|
246
|
-
[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 }),
|
|
247
226
|
[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 }),
|
|
248
|
-
[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 }),
|
|
249
|
-
[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 }),
|
|
250
|
-
[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 }),
|
|
251
227
|
[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 }),
|
|
252
|
-
[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 }),
|
|
253
|
-
[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 }),
|
|
254
|
-
[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 }),
|
|
255
|
-
[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 }),
|
|
256
|
-
[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 }),
|
|
257
|
-
[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 }),
|
|
258
228
|
[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 }),
|
|
259
229
|
[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 }),
|
|
260
230
|
// --- Alchemy game: gases + byproducts ---
|
|
@@ -2207,9 +2207,6 @@ export class SandWorld {
|
|
|
2207
2207
|
continue;
|
|
2208
2208
|
}
|
|
2209
2209
|
}
|
|
2210
|
-
// --- PhoenixFeather near fire/lava: glows brighter, resists burning ---
|
|
2211
|
-
// (flammable but long burnTime already set; here we just let it shimmer)
|
|
2212
|
-
// --- DragonScale: very dense, sinks through liquids (handled by density) ---
|
|
2213
2210
|
// --- LiquidShadow + light source (fire/plasma) → dissipates to smoke ---
|
|
2214
2211
|
if (mat === Material.LiquidShadow) {
|
|
2215
2212
|
let lightNi = -1;
|
|
@@ -3,16 +3,15 @@
|
|
|
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
|
import { resourceToken } from "@downdraft/engine/ecs/resource";
|
|
18
17
|
import { createLogger } from "@downdraft/engine/util/logger";
|
|
@@ -50,7 +49,7 @@ export const DEVTOOLS_REALM = detectRealm();
|
|
|
50
49
|
// synchronously. No RPC polling needed.
|
|
51
50
|
//
|
|
52
51
|
// Layout:
|
|
53
|
-
// [0..3] manifestVersion: Uint32 (bumped when
|
|
52
|
+
// [0..3] manifestVersion: Uint32 (bumped when feeds/commands change)
|
|
54
53
|
// [4..7] feedCount: Uint32
|
|
55
54
|
// [8..] feed entries: each is [lengthBytes: Uint32][JSON bytes...]
|
|
56
55
|
// (lengthBytes = 0 means "not yet written" / "unavailable")
|
|
@@ -81,8 +80,6 @@ export function allocateDevToolsSAB(maxFeedBlobBytes, statsBytes) {
|
|
|
81
80
|
}
|
|
82
81
|
class DevToolsAPIImpl {
|
|
83
82
|
realm;
|
|
84
|
-
panels = new Map();
|
|
85
|
-
toggles = new Map();
|
|
86
83
|
dataFeeds = new Map();
|
|
87
84
|
dataFeedNames = []; // ordered list for feedIndex assignment
|
|
88
85
|
commands = new Map();
|
|
@@ -99,14 +96,6 @@ class DevToolsAPIImpl {
|
|
|
99
96
|
constructor(realm) {
|
|
100
97
|
this.realm = realm;
|
|
101
98
|
}
|
|
102
|
-
registerPanel(ext) {
|
|
103
|
-
this.panels.set(ext.id, ext);
|
|
104
|
-
this.bumpManifest();
|
|
105
|
-
}
|
|
106
|
-
registerOverlayToggle(toggle) {
|
|
107
|
-
this.toggles.set(toggle.id, toggle);
|
|
108
|
-
this.bumpManifest();
|
|
109
|
-
}
|
|
110
99
|
registerDataFeed(name, fn, writeRateHz = 10) {
|
|
111
100
|
if (this.dataFeeds.has(name)) {
|
|
112
101
|
// Update fn/rate, keep feedIndex and hasWritten
|
|
@@ -183,8 +172,6 @@ class DevToolsAPIImpl {
|
|
|
183
172
|
}
|
|
184
173
|
getManifest() {
|
|
185
174
|
return {
|
|
186
|
-
panels: Array.from(this.panels.values()),
|
|
187
|
-
toggles: Array.from(this.toggles.values()),
|
|
188
175
|
dataFeeds: this.dataFeedNames.map((name, i) => ({
|
|
189
176
|
name,
|
|
190
177
|
feedIndex: i,
|
|
@@ -276,8 +263,6 @@ class DevToolsAPIImpl {
|
|
|
276
263
|
return Array.from(this.sabStats.keys());
|
|
277
264
|
}
|
|
278
265
|
clear() {
|
|
279
|
-
this.panels.clear();
|
|
280
|
-
this.toggles.clear();
|
|
281
266
|
this.dataFeeds.clear();
|
|
282
267
|
this.dataFeedNames = [];
|
|
283
268
|
this.commands.clear();
|
|
@@ -301,7 +286,7 @@ const _devtools = new DevToolsAPIImpl(DEVTOOLS_REALM);
|
|
|
301
286
|
* the appropriate transport for data feeds (direct call vs SAB) and
|
|
302
287
|
* commands (direct call vs RPC forwarding).
|
|
303
288
|
*
|
|
304
|
-
* Plugins import this and call
|
|
289
|
+
* Plugins import this and call registerDataFeed/registerCommand.
|
|
305
290
|
* The same code works in both realms.
|
|
306
291
|
*/
|
|
307
292
|
export const devtools = _devtools;
|
|
@@ -0,0 +1,75 @@
|
|
|
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
|
+
import { execFileSync } from "node:child_process";
|
|
14
|
+
import { readFileSync } from "node:fs";
|
|
15
|
+
// Blitz-dom only fetches a @font-face source whose format is detectable —
|
|
16
|
+
// either an explicit format() hint or a file extension on the url — so the
|
|
17
|
+
// urls carry the resolved file's real extension.
|
|
18
|
+
const FACES = [
|
|
19
|
+
{ family: "DevTools Sans", pattern: "sans-serif", urlName: "sans", weight: 400 },
|
|
20
|
+
{ family: "DevTools Sans", pattern: "sans-serif:weight=bold", urlName: "sans-bold", weight: 700 },
|
|
21
|
+
{ family: "DevTools Mono", pattern: "monospace", urlName: "mono", weight: 400 },
|
|
22
|
+
{ family: "DevTools Mono", pattern: "monospace:weight=bold", urlName: "mono-bold", weight: 700 },
|
|
23
|
+
];
|
|
24
|
+
/** `fc-match -f %{file}` for a fontconfig pattern; null when fc-match is
|
|
25
|
+
* absent or the match isn't a font file Blitz can load. */
|
|
26
|
+
function fcFontFile(pattern) {
|
|
27
|
+
try {
|
|
28
|
+
const p = execFileSync("fc-match", ["-f", "%{file}", pattern], { timeout: 5000 }).toString().trim();
|
|
29
|
+
return /\.(ttf|otf|woff2?)$/i.test(p) ? p : null;
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function fontExt(path) {
|
|
36
|
+
return path.match(/\.(ttf|otf|woff2?)$/i)?.[0] ?? ".ttf";
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Register system sans + mono faces as `ui://` resources and return the
|
|
40
|
+
* @font-face CSS to prepend to the dock stylesheet. Returns "" when nothing
|
|
41
|
+
* registered — the stylesheet's named families then fail to resolve and the
|
|
42
|
+
* generic fallbacks (embedded DejaVu) apply.
|
|
43
|
+
*
|
|
44
|
+
* Call before mount — the doc backend applies resources synchronously and in
|
|
45
|
+
* order, so the first doc resolve sees them.
|
|
46
|
+
*/
|
|
47
|
+
export function registerDevtoolsFonts(sink) {
|
|
48
|
+
try {
|
|
49
|
+
// Both weights of a family are required: a 700 rule pointing at the
|
|
50
|
+
// regular file keeps bold text in-family instead of falling back.
|
|
51
|
+
const files = new Map();
|
|
52
|
+
for (const f of FACES)
|
|
53
|
+
files.set(f.urlName, fcFontFile(f.pattern) ?? "");
|
|
54
|
+
if (!files.get("sans"))
|
|
55
|
+
return "";
|
|
56
|
+
if (!files.get("mono"))
|
|
57
|
+
files.set("mono", files.get("sans"));
|
|
58
|
+
if (!files.get("sans-bold"))
|
|
59
|
+
files.set("sans-bold", files.get("sans"));
|
|
60
|
+
if (!files.get("mono-bold"))
|
|
61
|
+
files.set("mono-bold", files.get("mono"));
|
|
62
|
+
let css = "";
|
|
63
|
+
for (const f of FACES) {
|
|
64
|
+
const file = files.get(f.urlName);
|
|
65
|
+
const url = `ui://devtools-fonts/${f.urlName}${fontExt(file)}`;
|
|
66
|
+
sink.registerResource(url, readFileSync(file));
|
|
67
|
+
css += `@font-face { font-family:'${f.family}'; font-weight:${f.weight}; src:url('${url}'); }\n`;
|
|
68
|
+
}
|
|
69
|
+
return css;
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
// fc-match absent / unreadable font file — the embedded fallback applies.
|
|
73
|
+
return "";
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -13,11 +13,13 @@
|
|
|
13
13
|
import { createLogger } from "@downdraft/engine";
|
|
14
14
|
import { CdpBridge, DevtoolsBackend } from "@downdraft/engine/libraries/devtools";
|
|
15
15
|
import { HtmlUiHost } from "@downdraft/engine/modules/html-ui";
|
|
16
|
+
import { registerDevtoolsFonts } from "./fonts.js";
|
|
16
17
|
import { ConsolePanel } from "./panels/console.js";
|
|
17
18
|
import { ElementsPanel } from "./panels/elements.js";
|
|
18
19
|
import { GpuPanel } from "./panels/gpu.js";
|
|
19
20
|
import { PerformancePanel } from "./panels/performance.js";
|
|
20
21
|
import { SnapshotPanel } from "./panels/snapshot.js";
|
|
22
|
+
import { esc } from "./panels/types.js";
|
|
21
23
|
import { DEVTOOLS_CSS } from "./theme.js";
|
|
22
24
|
const log = createLogger("info");
|
|
23
25
|
const MIN_DOCK = 120;
|
|
@@ -42,6 +44,9 @@ export class BlitzDevtoolsHost {
|
|
|
42
44
|
panelCtx;
|
|
43
45
|
lastPanelRender = -1e9;
|
|
44
46
|
lastSlowLog = 0;
|
|
47
|
+
/** @font-face css from registerDevtoolsFonts — "" means "embedded font
|
|
48
|
+
* fallback", null means "not yet attempted". */
|
|
49
|
+
fontCss = null;
|
|
45
50
|
constructor(opts) {
|
|
46
51
|
this.opts = opts;
|
|
47
52
|
this.surfaceW = opts.surface.clientWidth;
|
|
@@ -113,19 +118,42 @@ export class BlitzDevtoolsHost {
|
|
|
113
118
|
show() {
|
|
114
119
|
if (!this._ready || this.visible || this.disposed)
|
|
115
120
|
return;
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
121
|
+
// Register real system fonts once per host — ui:// resources persist on
|
|
122
|
+
// the surface across dock remounts, and must land before the doc mounts
|
|
123
|
+
// so the first resolve sees them (embedded DejaVu fallback otherwise).
|
|
124
|
+
if (this.fontCss === null) {
|
|
125
|
+
const ui = this.opts.ui;
|
|
126
|
+
const rr = ui.registerResource?.bind(ui)
|
|
127
|
+
?? (ui.host ? ui.host.registerResource.bind(ui.host) : undefined);
|
|
128
|
+
this.fontCss = rr ? registerDevtoolsFonts({ registerResource: rr }) : "";
|
|
129
|
+
}
|
|
130
|
+
try {
|
|
131
|
+
this.panel = this.opts.ui.mount(this.shellHtml(), {
|
|
132
|
+
id: "devtools",
|
|
133
|
+
rect: this.dockRect(),
|
|
134
|
+
// Above game overlays — menus/loading screens shouldn't bury the dock.
|
|
135
|
+
z: 1000,
|
|
136
|
+
// Native-res raster: the vendored glifo/FreeType path grid-fits hinted
|
|
137
|
+
// glyph stems to whole pixels — supersample factors >1 just blur that
|
|
138
|
+
// back out on downscale (and cost 2.25x the texture memory).
|
|
139
|
+
scale: 1,
|
|
140
|
+
// Ease wheel detents at pump cadence instead of jumping per event.
|
|
141
|
+
smoothWheel: true,
|
|
142
|
+
interactive: true,
|
|
143
|
+
// Devtools UI doesn't need vsync-rate raster — cap it so mutation
|
|
144
|
+
// bursts (console streams, snapshot refreshes) coalesce into ~66ms
|
|
145
|
+
// rasters instead of one raster per message.
|
|
146
|
+
maxFps: 15,
|
|
147
|
+
onEvent: (ev) => this.handleDocEvent(ev),
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
catch (err) {
|
|
151
|
+
// Mount fails when the ui worker is dead — stay hidden so the next F12
|
|
152
|
+
// retries cleanly instead of wedging on a half-mounted doc.
|
|
153
|
+
this.panel = null;
|
|
154
|
+
log.error("blitz-devtools-host", `dock mount failed: ${err}`);
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
129
157
|
this.syncProviders();
|
|
130
158
|
this.renderTabs();
|
|
131
159
|
this.activate(this.activeId);
|
|
@@ -150,8 +178,19 @@ export class BlitzDevtoolsHost {
|
|
|
150
178
|
const t1 = performance.now();
|
|
151
179
|
if (!this.visible)
|
|
152
180
|
return;
|
|
153
|
-
|
|
154
|
-
this.
|
|
181
|
+
// Guarded like backend.update() above — a throwing panel render can't be
|
|
182
|
+
// allowed to kill the game frame hook that drives this pump.
|
|
183
|
+
try {
|
|
184
|
+
this.syncProviders();
|
|
185
|
+
this.flush();
|
|
186
|
+
}
|
|
187
|
+
catch (err) {
|
|
188
|
+
const tn = performance.now();
|
|
189
|
+
if (tn - this.lastSlowLog > 1000) {
|
|
190
|
+
this.lastSlowLog = tn;
|
|
191
|
+
log.warn("blitz-devtools-host", `panel render error: ${err}`);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
155
194
|
const t2 = performance.now();
|
|
156
195
|
// ≤1/sec — these diagnostics used to feed the console panel's logger
|
|
157
196
|
// sink, and each row forced another dock re-render: a self-feeding
|
|
@@ -204,11 +243,13 @@ export class BlitzDevtoolsHost {
|
|
|
204
243
|
}
|
|
205
244
|
// ── Dock layout ──
|
|
206
245
|
dockRect() {
|
|
207
|
-
|
|
208
|
-
|
|
246
|
+
// A tiny surface (or a pathological resize event) must not produce a
|
|
247
|
+
// negative height — keep at least enough room for the tab strip.
|
|
248
|
+
const h = Math.max(48, Math.min(this.dockH, this.surfaceH - 60));
|
|
249
|
+
return { x: 0, y: Math.max(0, this.surfaceH - h), w: this.surfaceW, h };
|
|
209
250
|
}
|
|
210
251
|
shellHtml() {
|
|
211
|
-
return `<html><head><style>${DEVTOOLS_CSS}</style></head><body>
|
|
252
|
+
return `<html><head><style>${this.fontCss ?? ""}${DEVTOOLS_CSS}</style></head><body>
|
|
212
253
|
<div id="dt-root">
|
|
213
254
|
<div id="dt-grip" title="drag to resize"></div>
|
|
214
255
|
<div id="dt-chrome">
|
|
@@ -224,7 +265,10 @@ export class BlitzDevtoolsHost {
|
|
|
224
265
|
renderTabs() {
|
|
225
266
|
if (!this.panel)
|
|
226
267
|
return;
|
|
227
|
-
|
|
268
|
+
// esc() both interpolations — provider names feed panel ids/titles and
|
|
269
|
+
// arrive as raw strings (a game registering "x\"><img" would otherwise
|
|
270
|
+
// inject markup into the tab strip).
|
|
271
|
+
const tabs = [...this.panels.values()].map((p) => `<span class="tab${p.id === this.activeId ? " active" : ""}" data-action="dt.tab" data-tab="${esc(p.id)}">${esc(p.title)}</span>`).join("");
|
|
228
272
|
this.panel.setInnerHtml("#dt-tabs", `<span class="brand">downdraft</span>${tabs}<span class="spacer"></span>
|
|
229
273
|
<span class="hint">docked · F12</span><span id="dt-close" data-action="dt.close">✕</span>`);
|
|
230
274
|
this.tabsDirty = false;
|
|
@@ -235,13 +279,28 @@ export class BlitzDevtoolsHost {
|
|
|
235
279
|
this.activeId = id;
|
|
236
280
|
const p = this.panels.get(id);
|
|
237
281
|
this.renderTabs();
|
|
238
|
-
|
|
239
|
-
|
|
282
|
+
try {
|
|
283
|
+
this.panel.setInnerHtml("#dt-top", p.renderTop?.() ?? "");
|
|
284
|
+
this.panel.setInnerHtml("#dt-bottom", p.renderBottom?.() ?? "");
|
|
285
|
+
}
|
|
286
|
+
catch (err) {
|
|
287
|
+
log.warn("blitz-devtools-host", `panel ${id} chrome render error: ${err}`);
|
|
288
|
+
}
|
|
240
289
|
p.dirty = true;
|
|
241
290
|
p.shellDirty = false;
|
|
242
|
-
this.panel.setInnerHtml("#dt-body",
|
|
243
|
-
|
|
244
|
-
|
|
291
|
+
this.panel.setInnerHtml("#dt-body", this.safeBody(p));
|
|
292
|
+
try {
|
|
293
|
+
p.afterRender?.();
|
|
294
|
+
}
|
|
295
|
+
catch (err) {
|
|
296
|
+
log.warn("blitz-devtools-host", `panel ${id} afterRender error: ${err}`);
|
|
297
|
+
}
|
|
298
|
+
try {
|
|
299
|
+
p.activate?.();
|
|
300
|
+
}
|
|
301
|
+
catch (err) {
|
|
302
|
+
log.warn("blitz-devtools-host", `panel ${id} activate error: ${err}`);
|
|
303
|
+
}
|
|
245
304
|
// Only the snapshot tab being viewed gets periodic provider refreshes;
|
|
246
305
|
// fixed panels suspend auto-refresh entirely (manual Refresh still works).
|
|
247
306
|
this.backend.providerWatch = p.providerSlot ?? -1;
|
|
@@ -307,10 +366,32 @@ export class BlitzDevtoolsHost {
|
|
|
307
366
|
}
|
|
308
367
|
if (p.dirty) {
|
|
309
368
|
p.dirty = false;
|
|
310
|
-
|
|
311
|
-
|
|
369
|
+
let handled = false;
|
|
370
|
+
try {
|
|
371
|
+
handled = p.flushDom?.() === true;
|
|
312
372
|
}
|
|
313
|
-
|
|
373
|
+
catch (err) {
|
|
374
|
+
log.warn("blitz-devtools-host", `panel ${p.id} flushDom error: ${err}`);
|
|
375
|
+
}
|
|
376
|
+
if (!handled)
|
|
377
|
+
this.panel.setInnerHtml("#dt-body", this.safeBody(p));
|
|
378
|
+
try {
|
|
379
|
+
p.afterRender?.();
|
|
380
|
+
}
|
|
381
|
+
catch (err) {
|
|
382
|
+
log.warn("blitz-devtools-host", `panel ${p.id} afterRender error: ${err}`);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
/** renderBody that can't take the dock down — the error surfaces in-panel
|
|
387
|
+
* (and the next push retries since dirty just flips back on). */
|
|
388
|
+
safeBody(p) {
|
|
389
|
+
try {
|
|
390
|
+
return p.renderBody();
|
|
391
|
+
}
|
|
392
|
+
catch (err) {
|
|
393
|
+
log.warn("blitz-devtools-host", `panel ${p.id} renderBody error: ${err}`);
|
|
394
|
+
return `<div class="status-banner error">panel render error: ${esc(String(err))}</div>`;
|
|
314
395
|
}
|
|
315
396
|
}
|
|
316
397
|
setStatus(text) {
|
|
@@ -345,8 +426,10 @@ export class BlitzDevtoolsHost {
|
|
|
345
426
|
return;
|
|
346
427
|
}
|
|
347
428
|
}
|
|
429
|
+
// Left-click only — routing actions on contextmenu meant a right-click
|
|
430
|
+
// on a tab/button fired it instead of just focusing.
|
|
348
431
|
const action = ev.d?.action;
|
|
349
|
-
if (
|
|
432
|
+
if (ev.t === "click" && action) {
|
|
350
433
|
if (action === "dt.tab") {
|
|
351
434
|
const id = ev.d.tab;
|
|
352
435
|
if (id)
|
|
@@ -357,11 +440,21 @@ export class BlitzDevtoolsHost {
|
|
|
357
440
|
this.hide();
|
|
358
441
|
return;
|
|
359
442
|
}
|
|
360
|
-
|
|
443
|
+
try {
|
|
444
|
+
this.panels.get(this.activeId)?.onAction?.(ev.d, ev);
|
|
445
|
+
}
|
|
446
|
+
catch (err) {
|
|
447
|
+
log.warn("blitz-devtools-host", `panel action error: ${err}`);
|
|
448
|
+
}
|
|
361
449
|
return;
|
|
362
450
|
}
|
|
363
451
|
// Everything else (input, keydown/up, scroll, focus…) → active panel.
|
|
364
|
-
|
|
452
|
+
try {
|
|
453
|
+
this.panels.get(this.activeId)?.onEvent?.(ev);
|
|
454
|
+
}
|
|
455
|
+
catch (err) {
|
|
456
|
+
log.warn("blitz-devtools-host", `panel event error: ${err}`);
|
|
457
|
+
}
|
|
365
458
|
}
|
|
366
459
|
}
|
|
367
460
|
/**
|
|
@@ -387,5 +480,6 @@ export function createSelfHostedDevtoolsUi(ctx) {
|
|
|
387
480
|
host,
|
|
388
481
|
mount: (markup, spec) => host.mount({ ...spec, html: markup }),
|
|
389
482
|
docCaps: () => host.docCaps,
|
|
483
|
+
registerResource: (url, bytes) => host.registerResource(url, bytes),
|
|
390
484
|
};
|
|
391
485
|
}
|
|
@@ -101,7 +101,7 @@ export class ConsolePanel {
|
|
|
101
101
|
onBackendEvent(event, data) {
|
|
102
102
|
if (event === "console") {
|
|
103
103
|
const e = data;
|
|
104
|
-
this.rows.push({ text: e
|
|
104
|
+
this.rows.push({ text: String(e?.text ?? ""), severity: e?.severity ?? 0, thread: e?.thread ?? "main", seq: this.seq++ });
|
|
105
105
|
if (this.rows.length > MAX_ROWS)
|
|
106
106
|
this.rows.splice(0, this.rows.length - MAX_ROWS);
|
|
107
107
|
this.dirty = true;
|
|
@@ -113,15 +113,24 @@ export class ConsolePanel {
|
|
|
113
113
|
}
|
|
114
114
|
else if (event === "threads") {
|
|
115
115
|
const list = data ?? [];
|
|
116
|
-
|
|
117
|
-
|
|
116
|
+
const next = [{ id: "main", name: "main", kind: 0 }, ...list.filter((t) => t.id !== "main")];
|
|
117
|
+
// Thread chips live in the bottom chrome — flag it so the row
|
|
118
|
+
// actually re-renders when a worker appears or disappears.
|
|
119
|
+
const changed = next.length !== this.threads.length
|
|
120
|
+
|| next.some((t, i) => t.id !== this.threads[i].id || t.name !== this.threads[i].name);
|
|
121
|
+
this.threads = next;
|
|
122
|
+
if (!this.threads.some((t) => t.id === this.replThread)) {
|
|
118
123
|
this.replThread = "main";
|
|
124
|
+
this.shellDirty = true;
|
|
125
|
+
}
|
|
126
|
+
if (changed)
|
|
127
|
+
this.shellDirty = true;
|
|
119
128
|
}
|
|
120
129
|
}
|
|
121
130
|
onAction(data, _ev) {
|
|
122
131
|
const act = data.action;
|
|
123
132
|
if (act === "console.clear") {
|
|
124
|
-
void this.ctx.call("console.clear");
|
|
133
|
+
void this.ctx.call("console.clear").catch(() => { });
|
|
125
134
|
}
|
|
126
135
|
else if (act === "console.sev") {
|
|
127
136
|
this.minSev = (this.minSev + 1) % SEV_NAMES.length;
|
|
@@ -171,8 +180,11 @@ export class ConsolePanel {
|
|
|
171
180
|
this.replValue = this.history[--this.histIdx];
|
|
172
181
|
this.ctx.setValue("[data-dt=repl]", this.replValue);
|
|
173
182
|
}
|
|
174
|
-
else if (ev.k === "ArrowDown" && this.histIdx < this.history.length
|
|
175
|
-
|
|
183
|
+
else if (ev.k === "ArrowDown" && this.histIdx >= 0 && this.histIdx < this.history.length) {
|
|
184
|
+
// Stepping past the newest entry restores the empty input —
|
|
185
|
+
// previously the last history item was unreachable-to-leave.
|
|
186
|
+
this.histIdx++;
|
|
187
|
+
this.replValue = this.histIdx < this.history.length ? this.history[this.histIdx] : "";
|
|
176
188
|
this.ctx.setValue("[data-dt=repl]", this.replValue);
|
|
177
189
|
}
|
|
178
190
|
}
|
|
@@ -205,7 +217,7 @@ export class ConsolePanel {
|
|
|
205
217
|
else {
|
|
206
218
|
const r = res?.result;
|
|
207
219
|
this.addRow({
|
|
208
|
-
text: typeof r === "string" ? r : JSON.stringify(r, null, 2),
|
|
220
|
+
text: typeof r === "string" ? r : (r === undefined ? "undefined" : JSON.stringify(r, null, 2)),
|
|
209
221
|
severity: 0, thread: this.replThread,
|
|
210
222
|
});
|
|
211
223
|
}
|