gdharness 1.2.26 → 1.2.27
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/build/cli.js +195 -34
- package/build/godot/addons/gdharness_runtime/runtime_accessibility.gd +5 -1
- package/build/index.js +190 -32
- package/build/keeper.js +1207 -753
- package/package.json +1 -1
package/build/cli.js
CHANGED
|
@@ -11898,7 +11898,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
11898
11898
|
type: "array",
|
|
11899
11899
|
items: { type: "string" },
|
|
11900
11900
|
ops: ["start", "check"],
|
|
11901
|
-
description: `The game's own arguments, what OS.get_cmdline_user_args() answers, such as ["--level=2"]. The separator is added here, so a flag the engine reads goes to the game instead, and the answer says so under argsNote. A run with any is started by this server rather than by the editor, which fixes the game's command line when it opens the project, so the debug_* tools
|
|
11901
|
+
description: `The game's own arguments, what OS.get_cmdline_user_args() answers, such as ["--level=2"]. The separator is added here, so a flag the engine reads goes to the game instead, and the answer says so under argsNote. A run with any is started by this server rather than by the editor, which fixes the game's command line when it opens the project, so its debugger is the keeper holding it rather than the editor; the debug_* tools answer for it all the same.`
|
|
11902
11902
|
},
|
|
11903
11903
|
engineArgs: {
|
|
11904
11904
|
type: "array",
|
|
@@ -11915,7 +11915,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
11915
11915
|
silent: {
|
|
11916
11916
|
type: "boolean",
|
|
11917
11917
|
ops: ["start"],
|
|
11918
|
-
description: "start: run the game with no sound out of the machine, by starting the engine with --audio-driver Dummy: every stream still plays and every playback position still advances, so what is playing can still be asked. The editor takes no engine flags for a game it plays, so a silent windowed run is started by this server,
|
|
11918
|
+
description: "start: run the game with no sound out of the machine, by starting the engine with --audio-driver Dummy: every stream still plays and every playback position still advances, so what is playing can still be asked. The editor takes no engine flags for a game it plays, so a silent windowed run is started by this server, whose keeper is its debugger rather than the editor. A headless run, check's included, is silent already. Default false, or true when GDHARNESS_SILENT is set in the server's environment to anything but 0 or false; silent: false then plays through the editor as before."
|
|
11919
11919
|
},
|
|
11920
11920
|
savesIn: {
|
|
11921
11921
|
type: "string",
|
|
@@ -12596,7 +12596,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
12596
12596
|
{
|
|
12597
12597
|
name: "debug_breakpoint",
|
|
12598
12598
|
title: "Breakpoints",
|
|
12599
|
-
description: "Sets or removes a breakpoint through the editor's debug adapter.
|
|
12599
|
+
description: "Sets or removes a breakpoint. Set them first, then editor_run, and the game stops where you asked. With the editor connected it goes through the editor's debug adapter; with none, it is held here. A game this server starts itself, which editor_run start does for a run given args, savesIn, env or silent, or with no editor, is handed every breakpoint held here as it starts, and one set while that game runs reaches it at once (runningGame says so). The editor keeps a breakpoint set this way for one play, so every breakpoint this server holds is sent again before each play editor_run starts, and the start answer lists them under breakpoints. The set is also kept in the project, for the server that comes after a reconnect: a breakpoint stays until it is removed here, whichever server set it. Every answer lists the whole set under held, and the breakpoints the user set in the editor's own gutter under setInEditor; setting one here never takes those away, and removing a line here clears it whoever set it. Godot clears every breakpoint in the editor when a debug session opens unless network/debug_adapter/sync_breakpoints is on, which the addon turns on as it loads; an editor running an older addon is said so under breakpointsAtRisk, here and in editor_status.",
|
|
12600
12600
|
parameters: {
|
|
12601
12601
|
projectPath: PROJECT_PATH,
|
|
12602
12602
|
scriptPath: SCRIPT_PATH,
|
|
@@ -12611,23 +12611,33 @@ var init_tool_definitions = __esm(() => {
|
|
|
12611
12611
|
{
|
|
12612
12612
|
name: "debug_control",
|
|
12613
12613
|
title: "Step the debugger",
|
|
12614
|
-
description: "
|
|
12614
|
+
description: "Pauses, continues or steps the debugged game. A step waits for the game to stop again and answers with the stack where it ended up; one that has not stopped the game again within ten seconds answers heldAgain false and no stack. A game the editor plays is moved through the editor's debug adapter, which cannot pause or step out: it answers a pause by reporting the game stopped and leaving it running, and implements no stepOut, so hold such a game with a breakpoint and step over or into from there. A game this server started itself has its keeper as its debugger, which does both: pause breaks into the game wherever it is, a script spinning in a loop included, which is how a game hung on its first frame is asked where it is.",
|
|
12615
12615
|
parameters: {},
|
|
12616
12616
|
requires: [],
|
|
12617
12617
|
operations: {
|
|
12618
|
+
pause: {
|
|
12619
|
+
effect: "additive",
|
|
12620
|
+
summary: "stop a game this server started where it is, answering with the stack. The game takes it between frames, and in a script every 2048 lines it runs, so a loop that sleeps between few lines can take seconds; the call waits thirty, and past that answers paused false with the request still standing. Stopped between frames, where no script runs, the game is stepped on to the next line of script it runs",
|
|
12621
|
+
requires: []
|
|
12622
|
+
},
|
|
12618
12623
|
continue: { effect: "additive", summary: "resume after a breakpoint", requires: [] },
|
|
12619
12624
|
step_over: { effect: "additive", summary: "run the current line", requires: [] },
|
|
12620
12625
|
step_into: {
|
|
12621
12626
|
effect: "additive",
|
|
12622
12627
|
summary: "run the current line, stopping inside whatever it calls",
|
|
12623
12628
|
requires: []
|
|
12629
|
+
},
|
|
12630
|
+
step_out: {
|
|
12631
|
+
effect: "additive",
|
|
12632
|
+
summary: "run to the end of the current function and stop in its caller; a game this server started only",
|
|
12633
|
+
requires: []
|
|
12624
12634
|
}
|
|
12625
12635
|
}
|
|
12626
12636
|
},
|
|
12627
12637
|
{
|
|
12628
12638
|
name: "debug_state",
|
|
12629
12639
|
title: "Debugger state",
|
|
12630
|
-
description: "Where the debugged game is stopped: the stack trace, what is in scope at a frame with the values, or the
|
|
12640
|
+
description: "Where the debugged game is stopped: the stack trace, what is in scope at a frame with the values, or the debugger's console output so far, its last thousand lines, with any earlier ones counted under notShown. For a game this server started, its keeper's debugger answers: each frame names its script as the project spells it under script and on this machine under path, and held says which thread stopped and why. For a game the editor plays, the editor's debug adapter answers. A stop is reported by the editor's adapter only to the sessions connected when it happened, so a server that connected afterwards, which is every server after a reconnect, is shown a thread and no frames about a game still sitting at its breakpoint. That server asks the runtime instead: a held game accepts a connection and never answers, and the refusal then says held rather than running, and that the stack cannot be read from here. debug_control continue lets it go, and the next stop is one this session is told about, with its stack.",
|
|
12631
12641
|
parameters: {
|
|
12632
12642
|
frameId: {
|
|
12633
12643
|
type: "number",
|
|
@@ -35698,6 +35708,18 @@ class GodotDAPClient {
|
|
|
35698
35708
|
lines: Array.from(lines).sort((a, b) => a - b)
|
|
35699
35709
|
}));
|
|
35700
35710
|
}
|
|
35711
|
+
forgetBreakpoint(scriptPath, line) {
|
|
35712
|
+
const key = fileKey(scriptPath);
|
|
35713
|
+
const lines = this.breakpoints.get(key);
|
|
35714
|
+
if (lines === undefined) {
|
|
35715
|
+
return;
|
|
35716
|
+
}
|
|
35717
|
+
lines.delete(line);
|
|
35718
|
+
if (lines.size === 0) {
|
|
35719
|
+
this.breakpoints.delete(key);
|
|
35720
|
+
this.spelled.delete(key);
|
|
35721
|
+
}
|
|
35722
|
+
}
|
|
35701
35723
|
holdBreakpoints(held) {
|
|
35702
35724
|
for (const { scriptPath, lines } of held) {
|
|
35703
35725
|
const key = fileKey(scriptPath);
|
|
@@ -36203,6 +36225,26 @@ var init_editor_log = __esm(() => {
|
|
|
36203
36225
|
SLOTS = [/"[^"]*"/g, /'[^']*'/g, /\b(?:res|user):\/\/\S+/g, /(?<=\.gd|\.tscn|\.cs):\d+(?::\d+)?/g];
|
|
36204
36226
|
});
|
|
36205
36227
|
|
|
36228
|
+
// src/godot-variant.ts
|
|
36229
|
+
var FLAG_64, MOST_MESSAGE_BYTES;
|
|
36230
|
+
var init_godot_variant = __esm(() => {
|
|
36231
|
+
FLAG_64 = 1 << 16;
|
|
36232
|
+
MOST_MESSAGE_BYTES = (8 << 20) + 4;
|
|
36233
|
+
});
|
|
36234
|
+
|
|
36235
|
+
// src/game-debugger.ts
|
|
36236
|
+
var PAUSE_MS = 30000, STEP_MS = 1e4, READ_MS = 5000, REPLIES;
|
|
36237
|
+
var init_game_debugger = __esm(() => {
|
|
36238
|
+
init_godot_variant();
|
|
36239
|
+
REPLIES = new Set([
|
|
36240
|
+
"debug_enter",
|
|
36241
|
+
"debug_exit",
|
|
36242
|
+
"stack_dump",
|
|
36243
|
+
"stack_frame_vars",
|
|
36244
|
+
"stack_frame_var"
|
|
36245
|
+
]);
|
|
36246
|
+
});
|
|
36247
|
+
|
|
36206
36248
|
// node_modules/ws/lib/constants.js
|
|
36207
36249
|
var require_constants = __commonJS(function(exports, module) {
|
|
36208
36250
|
var BINARY_TYPES = ["nodebuffer", "arraybuffer", "fragments"];
|
|
@@ -40167,7 +40209,30 @@ var init_junit = __esm(() => {
|
|
|
40167
40209
|
|
|
40168
40210
|
// src/keeper-channel.ts
|
|
40169
40211
|
import { connect as connect2, createServer as createServer2 } from "node:net";
|
|
40170
|
-
function askTheKeeperToStop(address, withinMs) {
|
|
40212
|
+
async function askTheKeeperToStop(address, withinMs) {
|
|
40213
|
+
const answered = await askTheKeeper(address, "stop", withinMs);
|
|
40214
|
+
if (typeof answered !== "string") {
|
|
40215
|
+
return answered;
|
|
40216
|
+
}
|
|
40217
|
+
return answered === "signalled" || answered === "gone" ? answered : { unreached: `it answered ${JSON.stringify(answered)}` };
|
|
40218
|
+
}
|
|
40219
|
+
async function askTheKeepersDebugger(address, request, withinMs) {
|
|
40220
|
+
const answered = await askTheKeeper(address, `${DEBUG_PREFIX}${JSON.stringify(request)}`, withinMs);
|
|
40221
|
+
if (typeof answered !== "string") {
|
|
40222
|
+
return answered;
|
|
40223
|
+
}
|
|
40224
|
+
if (answered === "unknown") {
|
|
40225
|
+
return { unreached: "its keeper was started by a gdharness from before game debuggers, and has none" };
|
|
40226
|
+
}
|
|
40227
|
+
try {
|
|
40228
|
+
const parsed = JSON.parse(answered);
|
|
40229
|
+
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
|
|
40230
|
+
return parsed;
|
|
40231
|
+
}
|
|
40232
|
+
} catch {}
|
|
40233
|
+
return { unreached: `it answered ${JSON.stringify(answered.slice(0, 200))}` };
|
|
40234
|
+
}
|
|
40235
|
+
function askTheKeeper(address, line, withinMs) {
|
|
40171
40236
|
return new Promise((resolve) => {
|
|
40172
40237
|
let said = "";
|
|
40173
40238
|
let settled = false;
|
|
@@ -40185,16 +40250,15 @@ function askTheKeeperToStop(address, withinMs) {
|
|
|
40185
40250
|
}, withinMs);
|
|
40186
40251
|
socket.setEncoding("utf8");
|
|
40187
40252
|
socket.on("connect", () => {
|
|
40188
|
-
socket.write(
|
|
40253
|
+
socket.write(`${line}
|
|
40189
40254
|
`);
|
|
40190
40255
|
});
|
|
40191
40256
|
socket.on("data", (chunk) => {
|
|
40192
40257
|
said += chunk;
|
|
40193
|
-
const line = said.split(`
|
|
40194
|
-
`)[0] ?? "";
|
|
40195
40258
|
if (said.includes(`
|
|
40196
40259
|
`)) {
|
|
40197
|
-
settle(
|
|
40260
|
+
settle(said.split(`
|
|
40261
|
+
`)[0] ?? "");
|
|
40198
40262
|
}
|
|
40199
40263
|
});
|
|
40200
40264
|
socket.on("error", (error) => {
|
|
@@ -40205,6 +40269,7 @@ function askTheKeeperToStop(address, withinMs) {
|
|
|
40205
40269
|
});
|
|
40206
40270
|
});
|
|
40207
40271
|
}
|
|
40272
|
+
var DEBUG_PREFIX = "debug ";
|
|
40208
40273
|
var init_keeper_channel = () => {};
|
|
40209
40274
|
|
|
40210
40275
|
// src/lifecycle-log.ts
|
|
@@ -43183,13 +43248,6 @@ var init_script_origins = __esm(() => {
|
|
|
43183
43248
|
RELOADED_AT = /GDScript::reload \((res:\/\/.+):(\d+)\)$/;
|
|
43184
43249
|
});
|
|
43185
43250
|
|
|
43186
|
-
// src/godot-variant.ts
|
|
43187
|
-
var FLAG_64, MOST_MESSAGE_BYTES;
|
|
43188
|
-
var init_godot_variant = __esm(() => {
|
|
43189
|
-
FLAG_64 = 1 << 16;
|
|
43190
|
-
MOST_MESSAGE_BYTES = (8 << 20) + 4;
|
|
43191
|
-
});
|
|
43192
|
-
|
|
43193
43251
|
// src/script-profile.ts
|
|
43194
43252
|
import { readFileSync as readFileSync22 } from "node:fs";
|
|
43195
43253
|
function readProfileFile(path) {
|
|
@@ -43356,7 +43414,7 @@ function serverInstructions() {
|
|
|
43356
43414
|
"Call editor_status first: it says whether an editor is connected and whether its addon matches this server.",
|
|
43357
43415
|
"Start the game with editor_run start, never by spawning an engine, and read editor_output after every run:",
|
|
43358
43416
|
"its clean verdict and its error entries are how a run is judged, and editor_run wait is how to wait for one.",
|
|
43359
|
-
"The runtime_* tools work on the running game and the debug_* tools on
|
|
43417
|
+
"The runtime_* tools work on the running game and the debug_* tools on its debugger.",
|
|
43360
43418
|
"A refusal names what would have worked, so read it before calling again."
|
|
43361
43419
|
].join(" ");
|
|
43362
43420
|
}
|
|
@@ -43486,6 +43544,30 @@ function longerWaitNote(tool, op, waitedMs) {
|
|
|
43486
43544
|
const how = tool === "runtime_wait" && op === "frames" ? `the wait is counted from the number of frames, at ${SLOWEST_FRAME_RATE} a second on top of GDHARNESS_RUNTIME_TIMEOUT_MS: a game drawing frames more slowly than that is waited on in fewer frames at a time, and a game inside one long frame, such as a long _ready, answers no call however few frames are asked for` : "the wait is GDHARNESS_RUNTIME_TIMEOUT_MS, which is set in the environment the server is started with";
|
|
43487
43545
|
return `${tool} ${op} takes no timeoutMs: it was given ${waitedMs}ms, and ${how}.`;
|
|
43488
43546
|
}
|
|
43547
|
+
function keptGameRequest(tool, op, args) {
|
|
43548
|
+
if (tool === "debug_state") {
|
|
43549
|
+
if (op === "output") {
|
|
43550
|
+
return { op: "output" };
|
|
43551
|
+
}
|
|
43552
|
+
if (op === "variables") {
|
|
43553
|
+
const frame = args["frameId"];
|
|
43554
|
+
return { op: "variables", frame: typeof frame === "number" ? frame : 0 };
|
|
43555
|
+
}
|
|
43556
|
+
return { op: "stack" };
|
|
43557
|
+
}
|
|
43558
|
+
switch (op) {
|
|
43559
|
+
case "pause":
|
|
43560
|
+
return { op: "pause" };
|
|
43561
|
+
case "step_over":
|
|
43562
|
+
return { op: "resume", how: "next" };
|
|
43563
|
+
case "step_into":
|
|
43564
|
+
return { op: "resume", how: "step" };
|
|
43565
|
+
case "step_out":
|
|
43566
|
+
return { op: "resume", how: "out" };
|
|
43567
|
+
default:
|
|
43568
|
+
return { op: "resume", how: "continue" };
|
|
43569
|
+
}
|
|
43570
|
+
}
|
|
43489
43571
|
function projectSpelling(projectPath, absolutePath) {
|
|
43490
43572
|
return `res://${relative5(realPathOr(projectPath), realPathOr(absolutePath)).replaceAll("\\", "/")}`;
|
|
43491
43573
|
}
|
|
@@ -45392,28 +45474,70 @@ class GodotServer {
|
|
|
45392
45474
|
return this.createErrorResponse(located.reason, PATH_SOLUTIONS);
|
|
45393
45475
|
}
|
|
45394
45476
|
const session = this.dapHoldingBreakpointsOf(project.value.path);
|
|
45395
|
-
const
|
|
45396
|
-
|
|
45397
|
-
|
|
45398
|
-
|
|
45399
|
-
|
|
45400
|
-
|
|
45477
|
+
const scriptPath = realPathOr(located.absolutePath);
|
|
45478
|
+
const line = readPositiveNumber(args, "line");
|
|
45479
|
+
if (line === undefined) {
|
|
45480
|
+
return this.createErrorResponse("line is required: the one-based line to stop on.");
|
|
45481
|
+
}
|
|
45482
|
+
const editor = this.godotBridge.isConnected();
|
|
45483
|
+
let answer;
|
|
45484
|
+
if (editor) {
|
|
45485
|
+
const answered = await this.handleDAP(op === "set" ? "dap_set_breakpoint" : "dap_remove_breakpoint", {
|
|
45486
|
+
...args,
|
|
45487
|
+
scriptPath
|
|
45488
|
+
});
|
|
45489
|
+
if (answered.isError === true) {
|
|
45490
|
+
return answered;
|
|
45491
|
+
}
|
|
45492
|
+
answer = asParams(JSON.parse(answered.content[0]?.text ?? "{}"));
|
|
45493
|
+
} else {
|
|
45494
|
+
if (op === "set") {
|
|
45495
|
+
session.holdBreakpoints([{ scriptPath, lines: [line] }]);
|
|
45496
|
+
} else {
|
|
45497
|
+
session.forgetBreakpoint(scriptPath, line);
|
|
45498
|
+
}
|
|
45499
|
+
answer = { [op === "set" ? "set" : "removed"]: true };
|
|
45401
45500
|
}
|
|
45402
45501
|
const held = session.breakpointsHeld();
|
|
45403
45502
|
writeBreakpointNote(project.value.path, held);
|
|
45503
|
+
const kept = this.keptRun();
|
|
45504
|
+
const told = kept === null ? null : await askTheKeepersDebugger(kept.keeper, {
|
|
45505
|
+
op: "breakpoint",
|
|
45506
|
+
script: projectSpelling(project.value.path, scriptPath),
|
|
45507
|
+
line,
|
|
45508
|
+
set: op === "set"
|
|
45509
|
+
}, KEEPER_DEBUG_MS);
|
|
45404
45510
|
return this.jsonTextResponse({
|
|
45405
|
-
...
|
|
45511
|
+
...answer,
|
|
45406
45512
|
held: this.breakpointsAsProjectSpells(project.value.path, held),
|
|
45407
|
-
|
|
45408
|
-
|
|
45409
|
-
|
|
45513
|
+
...editor ? {
|
|
45514
|
+
setInEditor: this.breakpointsAsProjectSpells(project.value.path, session.breakpointsInEditor())
|
|
45515
|
+
} : {},
|
|
45516
|
+
...told === null ? {} : ("unreached" in told) || typeof told["refused"] === "string" ? {
|
|
45517
|
+
runningGame: `The game this server started is running, and its debugger was not told: ${String("unreached" in told ? told.unreached : told["refused"])}. It takes the line when it is started again.`
|
|
45518
|
+
} : {
|
|
45519
|
+
runningGame: `Told to the running game (pid ${kept?.pid ?? "?"}), which this server started.`
|
|
45520
|
+
},
|
|
45521
|
+
note: editor ? "The editor keeps a breakpoint set this way for one play. Every breakpoint held here is sent again before each play editor_run starts, handed to every game this server starts itself, and kept in the project for the server after a reconnect." : "No editor is connected, so the set under held is kept here: every game this server starts is handed it as it starts, an editor that connects is sent it before its first play, and it is kept in the project for the server after a reconnect.",
|
|
45522
|
+
...editor ? this.breakpointsAtRisk() : {}
|
|
45410
45523
|
});
|
|
45411
45524
|
}
|
|
45412
45525
|
case "debug_control":
|
|
45413
45526
|
case "debug_state": {
|
|
45527
|
+
const kept = this.keptRun();
|
|
45528
|
+
if (kept !== null) {
|
|
45529
|
+
return await this.askTheKeptGame(kept.keeper, keptGameRequest(tool, op, args), kept.projectPath ?? "");
|
|
45530
|
+
}
|
|
45414
45531
|
if (tool === "debug_state" && op === "output") {
|
|
45415
45532
|
return await this.handleDAP("dap_get_output", args);
|
|
45416
45533
|
}
|
|
45534
|
+
const played = this.activeProcess;
|
|
45535
|
+
if ((op === "pause" || op === "step_out") && played?.throughEditor === true && !exited(played)) {
|
|
45536
|
+
return this.createErrorResponse(op === "pause" ? "A game the editor plays cannot be paused from here: Godot's debug adapter answers a pause by reporting the game stopped and leaving it running." : "A game the editor plays cannot be stepped out of a function from here: Godot's debug adapter implements no stepOut.", [
|
|
45537
|
+
"Set a breakpoint where it should stop, then step over or into from there",
|
|
45538
|
+
"A game this server starts, as editor_run start does with args, savesIn or env, has a debugger here that pauses and steps out"
|
|
45539
|
+
]);
|
|
45540
|
+
}
|
|
45417
45541
|
const held = await this.debuggedGame(tool === "debug_control" ? "move" : "read");
|
|
45418
45542
|
if (!held.ok) {
|
|
45419
45543
|
return held.response;
|
|
@@ -45557,8 +45681,8 @@ class GodotServer {
|
|
|
45557
45681
|
if (!game.throughEditor) {
|
|
45558
45682
|
return {
|
|
45559
45683
|
ok: false,
|
|
45560
|
-
response: this.createErrorResponse("The running game
|
|
45561
|
-
"editor_run start
|
|
45684
|
+
response: this.createErrorResponse("The running game has no debugger: it was started with none, or by a keeper that could not take questions, so breakpoints never hit and there is no stack to read.", [
|
|
45685
|
+
"editor_run start starts it again, and the keeper holding a game this server starts is its debugger",
|
|
45562
45686
|
"editor_status says whether an editor has reached this server"
|
|
45563
45687
|
])
|
|
45564
45688
|
};
|
|
@@ -46769,6 +46893,38 @@ class GodotServer {
|
|
|
46769
46893
|
breakpointsAtRisk: status.syncsBreakpoints === false ? "This editor clears every breakpoint in its script editor whenever a debug session opens on its adapter, which this server does for its first breakpoint or stack read: network/debug_adapter/sync_breakpoints is off. The installed addon turns it on as it loads, so the editor is running an older one; editor_launch restart puts the installed addon in, or turn the setting on in Editor Settings > Network > Debug Adapter." : "Whether this editor keeps its breakpoints when a debug session opens cannot be told: the addon it is running predates the report. Godot clears them unless network/debug_adapter/sync_breakpoints is on, which the installed addon turns on as it loads; editor_launch restart puts the installed addon in."
|
|
46770
46894
|
};
|
|
46771
46895
|
}
|
|
46896
|
+
breakpointsForAGame(projectPath) {
|
|
46897
|
+
return this.dapHoldingBreakpointsOf(projectPath).breakpointsHeld().flatMap((one) => one.lines.map((line) => `${projectSpelling(projectPath, one.scriptPath)}:${line}`));
|
|
46898
|
+
}
|
|
46899
|
+
keptRun() {
|
|
46900
|
+
const game = this.activeProcess;
|
|
46901
|
+
if (game === null || game.throughEditor || exited(game) || game.keeper === undefined) {
|
|
46902
|
+
return null;
|
|
46903
|
+
}
|
|
46904
|
+
return { ...game, keeper: game.keeper };
|
|
46905
|
+
}
|
|
46906
|
+
async askTheKeptGame(keeper, request, projectPath) {
|
|
46907
|
+
const answered = await askTheKeepersDebugger(keeper, request, KEEPER_DEBUG_MS);
|
|
46908
|
+
if ("unreached" in answered) {
|
|
46909
|
+
return this.createErrorResponse(`The debugger of the running game did not answer: ${String(answered.unreached)}.`, [
|
|
46910
|
+
"editor_run start starts the game again, with a debugger",
|
|
46911
|
+
"editor_status says whether it is still running"
|
|
46912
|
+
]);
|
|
46913
|
+
}
|
|
46914
|
+
if (typeof answered["refused"] === "string") {
|
|
46915
|
+
return this.createErrorResponse(answered["refused"]);
|
|
46916
|
+
}
|
|
46917
|
+
const stack = answered["stack"];
|
|
46918
|
+
return this.jsonTextResponse({
|
|
46919
|
+
...answered,
|
|
46920
|
+
...Array.isArray(stack) ? {
|
|
46921
|
+
stack: stack.map((frame) => ({
|
|
46922
|
+
...frame,
|
|
46923
|
+
...projectPath !== "" && typeof frame["script"] === "string" && frame["script"].startsWith("res://") ? { path: join27(projectPath, frame["script"].slice("res://".length)) } : {}
|
|
46924
|
+
}))
|
|
46925
|
+
} : {}
|
|
46926
|
+
});
|
|
46927
|
+
}
|
|
46772
46928
|
breakpointsAsProjectSpells(projectPath, held) {
|
|
46773
46929
|
return held.map((one) => ({
|
|
46774
46930
|
scriptPath: projectSpelling(projectPath, one.scriptPath),
|
|
@@ -47430,9 +47586,10 @@ class GodotServer {
|
|
|
47430
47586
|
started.announcedBefore = alreadyPlaying;
|
|
47431
47587
|
started.bootScene = runtimeWaitMs.scene;
|
|
47432
47588
|
this.activeProcess = started;
|
|
47433
|
-
const spawnedInstead = this.godotBridge.isConnected() && editorWouldPlay && (given.value.length > 0 || flags.value.length > 0) ?
|
|
47589
|
+
const spawnedInstead = this.godotBridge.isConnected() && editorWouldPlay && (given.value.length > 0 || flags.value.length > 0) ? ` The editor cannot be handed arguments for a game it plays, so this one was started here.${OWN_DEBUGGER}` : this.godotBridge.isConnected() && editorWouldPlay && needsItsOwnProcess(asked.value) ? ` The editor plays a game with its own environment, so a run given one was started here.${OWN_DEBUGGER}` : this.godotBridge.isConnected() && editorWouldPlay && silent ? " The editor cannot play a game silent, which takes an engine flag, so this one was " + `started here${args["silent"] === true ? "" : ", silent because GDHARNESS_SILENT says so"}, and silent: false plays it through the editor.${OWN_DEBUGGER}` : "";
|
|
47434
47590
|
const endedForThis = endedToStartThis(ended);
|
|
47435
47591
|
const stranded = wereHereFirst.filter((one) => one.pid !== ended?.pid && alive(one.pid));
|
|
47592
|
+
const handed = this.dapHoldingBreakpointsOf(project.value.path).breakpointsHeld();
|
|
47436
47593
|
const alsoRunning = leftRunningNote(stranded);
|
|
47437
47594
|
return this.jsonTextResponse({
|
|
47438
47595
|
started: true,
|
|
@@ -47442,6 +47599,7 @@ class GodotServer {
|
|
|
47442
47599
|
arguments: cmdArgs,
|
|
47443
47600
|
argsNote: engineFlagsNote(given.value),
|
|
47444
47601
|
transcript: started.transcript,
|
|
47602
|
+
breakpoints: started.keeper === undefined || handed.length === 0 ? undefined : this.breakpointsAsProjectSpells(project.value.path, handed),
|
|
47445
47603
|
profile: profileOnStart(profiled, "gdharness", runtimeAddonRegistered(project.value.path)),
|
|
47446
47604
|
refreshedClasses: refreshed.value,
|
|
47447
47605
|
...scanWaitAnswer(scanned),
|
|
@@ -47450,7 +47608,7 @@ class GodotServer {
|
|
|
47450
47608
|
previousRunLeft: previousRunLeft(ended),
|
|
47451
47609
|
...previousRunHow(ended),
|
|
47452
47610
|
runtime: await this.runtimeUp(project.value.path, alreadyPlaying, runtimeWaitMs, given.value.length > 0),
|
|
47453
|
-
message: `Use editor_output for what it prints and editor_run stop to end it.${spawnedInstead}${endedForThis}${alsoRunning}`
|
|
47611
|
+
message: `Use editor_output for what it prints and editor_run stop to end it.${started.keeper !== undefined && handed.length > 0 ? " It stops at the lines under breakpoints, and debug_state stack reads where it is held." : ""}${spawnedInstead}${endedForThis}${alsoRunning}`
|
|
47454
47612
|
});
|
|
47455
47613
|
}
|
|
47456
47614
|
async runtimeUp(projectPath, before, budget, withArgs) {
|
|
@@ -47977,7 +48135,8 @@ class GodotServer {
|
|
|
47977
48135
|
startedAt,
|
|
47978
48136
|
projectPath,
|
|
47979
48137
|
...this.servedBy(),
|
|
47980
|
-
...profile === undefined ? {} : { profile }
|
|
48138
|
+
...profile === undefined ? {} : { profile },
|
|
48139
|
+
breakpoints: this.breakpointsForAGame(projectPath)
|
|
47981
48140
|
},
|
|
47982
48141
|
...desktop === undefined ? {} : { desktop }
|
|
47983
48142
|
});
|
|
@@ -49830,7 +49989,7 @@ async function runGodotServer() {
|
|
|
49830
49989
|
const server = new GodotServer;
|
|
49831
49990
|
await server.run();
|
|
49832
49991
|
}
|
|
49833
|
-
var UPDATE_NOTICE_EVERY = 500, FEEDBACK_NOTICE_EVERY = 250, run4, __dirname2, PARENT_ASKED_AFTER_MS = 5000, EDITOR_SERVERS_START_MS = 20000, HOLD_PING_MS = 2000, SLOWEST_FRAME_RATE = 20, PLAY_STARTS_WITHIN_MS = 30000, KEEPER_ANSWER_MS = 15000, EXIT_REPORTED_WITHIN_MS = 2000, CONSOLE_WRAPPER, LONGEST_BOOT_NOTED_MS, PROFILE_SEND_MS = 5000, MOST_GROUPS = 20, RUNTIME_LISTENING_LINE = "[gdharness] runtime listening on ", VALUE_NAMES, BRIDGE_RETRY_MS = 2000, SUCCESSOR_CHECK_MS = 1e4, CONSOLE_WATCH_MS = 250, BOUNDARY_WAIT_MS = 2000, HANDOVER_MS, HANDOVER_POLL_MS = 250, CACHE_WRITE_MS = 2000, STREAMS_GRACE_MS = 2000, PLAYING_STATUS_MS = 1000, SCAN_START_MS = 2000, SCAN_SETTLED_MS = 500, REIMPORT_TIMEOUT_MS = 300000, EDITOR_FREE_INSTEAD, OPS_ELSEWHERE, AUDIO_BUS_OPS, STALLED_AFTER_MS = 30000, TEST_HEARTBEAT_MS = 20000, DEPENDED_FAILED = "Failed to compile depended scripts", WAIT_FOR_RUN_MS = 600000, RUN_POLL_MS = 250, PATH_SOLUTIONS, PROJECT_FILE_ARGUMENTS, DEBUG_STATE_CALLS, PROJECT_INFO_SECTIONS2, RUNNER_DIRECTORY = "addons/gdUnit4/", PROFILE_FUNCTIONS = 30, PROFILER_ON_MS = 3000, PROPERTY_PLACES = "It is a property, so each place says whether it writes it (access write), which runs its set block, or reads it (access read), which runs its get block: withinCalls on a read says nothing of the set block, nor on a write of the get block unless the write is a compound one such as +=. Inside the property's own blocks its name is the value stored, which the engine reads and writes without running them, so those lines are not listed.", CALLERS_FOUND_BY_NAME = "Found by name in the project's scripts, since the engine's profiler counts calls and time per function and records nothing about who made them: a method of the same name on another class is listed too, unless one class was asked about and it is called through another class by name or through a variable declared as one. Each place carries the profile's numbers for the function it is written in.", MOST_ENGINE_ENTRIES = 200, ERROR_ASSERT = "GdUnitGodotErrorAssertImpl.gd", EXTERNAL_MEMBER = 'Could not resolve external class member "', STATIC_LIST_HINT;
|
|
49992
|
+
var UPDATE_NOTICE_EVERY = 500, FEEDBACK_NOTICE_EVERY = 250, run4, __dirname2, PARENT_ASKED_AFTER_MS = 5000, EDITOR_SERVERS_START_MS = 20000, HOLD_PING_MS = 2000, SLOWEST_FRAME_RATE = 20, OWN_DEBUGGER = " Its debugger is the keeper holding it rather than the editor, so the debug_* tools answer for it all the same: breakpoints stop it, and debug_control pause breaks into it wherever it is, which the editor offers no way to do.", PLAY_STARTS_WITHIN_MS = 30000, KEEPER_ANSWER_MS = 15000, KEEPER_DEBUG_MS, EXIT_REPORTED_WITHIN_MS = 2000, CONSOLE_WRAPPER, LONGEST_BOOT_NOTED_MS, PROFILE_SEND_MS = 5000, MOST_GROUPS = 20, RUNTIME_LISTENING_LINE = "[gdharness] runtime listening on ", VALUE_NAMES, BRIDGE_RETRY_MS = 2000, SUCCESSOR_CHECK_MS = 1e4, CONSOLE_WATCH_MS = 250, BOUNDARY_WAIT_MS = 2000, HANDOVER_MS, HANDOVER_POLL_MS = 250, CACHE_WRITE_MS = 2000, STREAMS_GRACE_MS = 2000, PLAYING_STATUS_MS = 1000, SCAN_START_MS = 2000, SCAN_SETTLED_MS = 500, REIMPORT_TIMEOUT_MS = 300000, EDITOR_FREE_INSTEAD, OPS_ELSEWHERE, AUDIO_BUS_OPS, STALLED_AFTER_MS = 30000, TEST_HEARTBEAT_MS = 20000, DEPENDED_FAILED = "Failed to compile depended scripts", WAIT_FOR_RUN_MS = 600000, RUN_POLL_MS = 250, PATH_SOLUTIONS, PROJECT_FILE_ARGUMENTS, DEBUG_STATE_CALLS, PROJECT_INFO_SECTIONS2, RUNNER_DIRECTORY = "addons/gdUnit4/", PROFILE_FUNCTIONS = 30, PROFILER_ON_MS = 3000, PROPERTY_PLACES = "It is a property, so each place says whether it writes it (access write), which runs its set block, or reads it (access read), which runs its get block: withinCalls on a read says nothing of the set block, nor on a write of the get block unless the write is a compound one such as +=. Inside the property's own blocks its name is the value stored, which the engine reads and writes without running them, so those lines are not listed.", CALLERS_FOUND_BY_NAME = "Found by name in the project's scripts, since the engine's profiler counts calls and time per function and records nothing about who made them: a method of the same name on another class is listed too, unless one class was asked about and it is called through another class by name or through a variable declared as one. Each place carries the profile's numbers for the function it is written in.", MOST_ENGINE_ENTRIES = 200, ERROR_ASSERT = "GdUnitGodotErrorAssertImpl.gd", EXTERNAL_MEMBER = 'Could not resolve external class member "', STATIC_LIST_HINT;
|
|
49834
49993
|
var init_server = __esm(() => {
|
|
49835
49994
|
init_dist();
|
|
49836
49995
|
init_stdio();
|
|
@@ -49849,6 +50008,7 @@ var init_server = __esm(() => {
|
|
|
49849
50008
|
init_editor_log();
|
|
49850
50009
|
init_engine_run();
|
|
49851
50010
|
init_errors();
|
|
50011
|
+
init_game_debugger();
|
|
49852
50012
|
init_game_log();
|
|
49853
50013
|
init_gdscript_source();
|
|
49854
50014
|
init_godot_bridge();
|
|
@@ -49886,6 +50046,7 @@ var init_server = __esm(() => {
|
|
|
49886
50046
|
init_update_check();
|
|
49887
50047
|
run4 = promisify5(execFile5);
|
|
49888
50048
|
__dirname2 = dirname20(fileURLToPath5(import.meta.url));
|
|
50049
|
+
KEEPER_DEBUG_MS = PAUSE_MS + STEP_MS + 2 * READ_MS;
|
|
49889
50050
|
CONSOLE_WRAPPER = /_console(\.exe)?$/i;
|
|
49890
50051
|
LONGEST_BOOT_NOTED_MS = 5 * 60000;
|
|
49891
50052
|
VALUE_NAMES = {
|
|
@@ -100,15 +100,19 @@ try {
|
|
|
100
100
|
$windows = @()
|
|
101
101
|
foreach ($handle in $Handles) {
|
|
102
102
|
$root = [System.Windows.Automation.AutomationElement]::FromHandle([IntPtr][long]$handle)
|
|
103
|
-
$r = $root.Current.BoundingRectangle
|
|
104
103
|
$deadline = [DateTime]::UtcNow.AddMilliseconds($SettleMs)
|
|
105
104
|
$empty = [DateTime]::UtcNow.AddMilliseconds($EmptyMs)
|
|
105
|
+
# The window's corner is read on every walk: before the engine builds its tree the window is
|
|
106
|
+
# its frame, and after it the client area inside it, so a corner read once placed everything
|
|
107
|
+
# by the frame's.
|
|
108
|
+
$r = $root.Current.BoundingRectangle
|
|
106
109
|
$tree = Describe $root $r.X $r.Y 0
|
|
107
110
|
$last = Count $tree
|
|
108
111
|
# Walked again until two walks in a row agree, since the first question is what makes the
|
|
109
112
|
# engine build the tree, which arrives over the frames after it.
|
|
110
113
|
while ([DateTime]::UtcNow -lt $deadline) {
|
|
111
114
|
Start-Sleep -Milliseconds 150
|
|
115
|
+
$r = $root.Current.BoundingRectangle
|
|
112
116
|
$tree = Describe $root $r.X $r.Y 0
|
|
113
117
|
$count = Count $tree
|
|
114
118
|
if ($count -gt 1 -and $count -eq $last) { break }
|