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/index.js
CHANGED
|
@@ -27929,6 +27929,18 @@ class GodotDAPClient {
|
|
|
27929
27929
|
lines: Array.from(lines).sort((a, b) => a - b)
|
|
27930
27930
|
}));
|
|
27931
27931
|
}
|
|
27932
|
+
forgetBreakpoint(scriptPath, line) {
|
|
27933
|
+
const key = fileKey(scriptPath);
|
|
27934
|
+
const lines = this.breakpoints.get(key);
|
|
27935
|
+
if (lines === undefined) {
|
|
27936
|
+
return;
|
|
27937
|
+
}
|
|
27938
|
+
lines.delete(line);
|
|
27939
|
+
if (lines.size === 0) {
|
|
27940
|
+
this.breakpoints.delete(key);
|
|
27941
|
+
this.spelled.delete(key);
|
|
27942
|
+
}
|
|
27943
|
+
}
|
|
27932
27944
|
holdBreakpoints(held) {
|
|
27933
27945
|
for (const { scriptPath, lines } of held) {
|
|
27934
27946
|
const key = fileKey(scriptPath);
|
|
@@ -28707,6 +28719,22 @@ function howItEnded(run) {
|
|
|
28707
28719
|
return run.exitSignal === null ? `exit code ${run.exitCode ?? "unknown"}` : `ended by ${run.exitSignal}`;
|
|
28708
28720
|
}
|
|
28709
28721
|
|
|
28722
|
+
// src/godot-variant.ts
|
|
28723
|
+
var FLAG_64 = 1 << 16;
|
|
28724
|
+
var MOST_MESSAGE_BYTES = (8 << 20) + 4;
|
|
28725
|
+
|
|
28726
|
+
// src/game-debugger.ts
|
|
28727
|
+
var PAUSE_MS = 30000;
|
|
28728
|
+
var STEP_MS = 1e4;
|
|
28729
|
+
var READ_MS = 5000;
|
|
28730
|
+
var REPLIES = new Set([
|
|
28731
|
+
"debug_enter",
|
|
28732
|
+
"debug_exit",
|
|
28733
|
+
"stack_dump",
|
|
28734
|
+
"stack_frame_vars",
|
|
28735
|
+
"stack_frame_var"
|
|
28736
|
+
]);
|
|
28737
|
+
|
|
28710
28738
|
// src/godot-bridge.ts
|
|
28711
28739
|
import { randomUUID } from "node:crypto";
|
|
28712
28740
|
import { EventEmitter } from "node:events";
|
|
@@ -30340,7 +30368,31 @@ function testProgressMessage(printed, elapsedMs) {
|
|
|
30340
30368
|
|
|
30341
30369
|
// src/keeper-channel.ts
|
|
30342
30370
|
import { connect, createServer as createServer2 } from "node:net";
|
|
30343
|
-
|
|
30371
|
+
var DEBUG_PREFIX = "debug ";
|
|
30372
|
+
async function askTheKeeperToStop(address, withinMs) {
|
|
30373
|
+
const answered = await askTheKeeper(address, "stop", withinMs);
|
|
30374
|
+
if (typeof answered !== "string") {
|
|
30375
|
+
return answered;
|
|
30376
|
+
}
|
|
30377
|
+
return answered === "signalled" || answered === "gone" ? answered : { unreached: `it answered ${JSON.stringify(answered)}` };
|
|
30378
|
+
}
|
|
30379
|
+
async function askTheKeepersDebugger(address, request, withinMs) {
|
|
30380
|
+
const answered = await askTheKeeper(address, `${DEBUG_PREFIX}${JSON.stringify(request)}`, withinMs);
|
|
30381
|
+
if (typeof answered !== "string") {
|
|
30382
|
+
return answered;
|
|
30383
|
+
}
|
|
30384
|
+
if (answered === "unknown") {
|
|
30385
|
+
return { unreached: "its keeper was started by a gdharness from before game debuggers, and has none" };
|
|
30386
|
+
}
|
|
30387
|
+
try {
|
|
30388
|
+
const parsed = JSON.parse(answered);
|
|
30389
|
+
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
|
|
30390
|
+
return parsed;
|
|
30391
|
+
}
|
|
30392
|
+
} catch {}
|
|
30393
|
+
return { unreached: `it answered ${JSON.stringify(answered.slice(0, 200))}` };
|
|
30394
|
+
}
|
|
30395
|
+
function askTheKeeper(address, line, withinMs) {
|
|
30344
30396
|
return new Promise((resolve) => {
|
|
30345
30397
|
let said = "";
|
|
30346
30398
|
let settled = false;
|
|
@@ -30358,16 +30410,15 @@ function askTheKeeperToStop(address, withinMs) {
|
|
|
30358
30410
|
}, withinMs);
|
|
30359
30411
|
socket.setEncoding("utf8");
|
|
30360
30412
|
socket.on("connect", () => {
|
|
30361
|
-
socket.write(
|
|
30413
|
+
socket.write(`${line}
|
|
30362
30414
|
`);
|
|
30363
30415
|
});
|
|
30364
30416
|
socket.on("data", (chunk) => {
|
|
30365
30417
|
said += chunk;
|
|
30366
|
-
const line = said.split(`
|
|
30367
|
-
`)[0] ?? "";
|
|
30368
30418
|
if (said.includes(`
|
|
30369
30419
|
`)) {
|
|
30370
|
-
settle(
|
|
30420
|
+
settle(said.split(`
|
|
30421
|
+
`)[0] ?? "");
|
|
30371
30422
|
}
|
|
30372
30423
|
});
|
|
30373
30424
|
socket.on("error", (error) => {
|
|
@@ -33868,12 +33919,6 @@ function withoutListed(entries, errors) {
|
|
|
33868
33919
|
|
|
33869
33920
|
// src/script-profile.ts
|
|
33870
33921
|
import { readFileSync as readFileSync19 } from "node:fs";
|
|
33871
|
-
|
|
33872
|
-
// src/godot-variant.ts
|
|
33873
|
-
var FLAG_64 = 1 << 16;
|
|
33874
|
-
var MOST_MESSAGE_BYTES = (8 << 20) + 4;
|
|
33875
|
-
|
|
33876
|
-
// src/script-profile.ts
|
|
33877
33922
|
function readProfileFile(path) {
|
|
33878
33923
|
try {
|
|
33879
33924
|
const parsed = JSON.parse(readFileSync19(path, "utf8"));
|
|
@@ -34992,7 +35037,7 @@ var TOOL_SPECS = [
|
|
|
34992
35037
|
type: "array",
|
|
34993
35038
|
items: { type: "string" },
|
|
34994
35039
|
ops: ["start", "check"],
|
|
34995
|
-
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
|
|
35040
|
+
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.`
|
|
34996
35041
|
},
|
|
34997
35042
|
engineArgs: {
|
|
34998
35043
|
type: "array",
|
|
@@ -35009,7 +35054,7 @@ var TOOL_SPECS = [
|
|
|
35009
35054
|
silent: {
|
|
35010
35055
|
type: "boolean",
|
|
35011
35056
|
ops: ["start"],
|
|
35012
|
-
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,
|
|
35057
|
+
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."
|
|
35013
35058
|
},
|
|
35014
35059
|
savesIn: {
|
|
35015
35060
|
type: "string",
|
|
@@ -35690,7 +35735,7 @@ var TOOL_SPECS = [
|
|
|
35690
35735
|
{
|
|
35691
35736
|
name: "debug_breakpoint",
|
|
35692
35737
|
title: "Breakpoints",
|
|
35693
|
-
description: "Sets or removes a breakpoint through the editor's debug adapter.
|
|
35738
|
+
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.",
|
|
35694
35739
|
parameters: {
|
|
35695
35740
|
projectPath: PROJECT_PATH2,
|
|
35696
35741
|
scriptPath: SCRIPT_PATH,
|
|
@@ -35705,23 +35750,33 @@ var TOOL_SPECS = [
|
|
|
35705
35750
|
{
|
|
35706
35751
|
name: "debug_control",
|
|
35707
35752
|
title: "Step the debugger",
|
|
35708
|
-
description: "
|
|
35753
|
+
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.",
|
|
35709
35754
|
parameters: {},
|
|
35710
35755
|
requires: [],
|
|
35711
35756
|
operations: {
|
|
35757
|
+
pause: {
|
|
35758
|
+
effect: "additive",
|
|
35759
|
+
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",
|
|
35760
|
+
requires: []
|
|
35761
|
+
},
|
|
35712
35762
|
continue: { effect: "additive", summary: "resume after a breakpoint", requires: [] },
|
|
35713
35763
|
step_over: { effect: "additive", summary: "run the current line", requires: [] },
|
|
35714
35764
|
step_into: {
|
|
35715
35765
|
effect: "additive",
|
|
35716
35766
|
summary: "run the current line, stopping inside whatever it calls",
|
|
35717
35767
|
requires: []
|
|
35768
|
+
},
|
|
35769
|
+
step_out: {
|
|
35770
|
+
effect: "additive",
|
|
35771
|
+
summary: "run to the end of the current function and stop in its caller; a game this server started only",
|
|
35772
|
+
requires: []
|
|
35718
35773
|
}
|
|
35719
35774
|
}
|
|
35720
35775
|
},
|
|
35721
35776
|
{
|
|
35722
35777
|
name: "debug_state",
|
|
35723
35778
|
title: "Debugger state",
|
|
35724
|
-
description: "Where the debugged game is stopped: the stack trace, what is in scope at a frame with the values, or the
|
|
35779
|
+
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.",
|
|
35725
35780
|
parameters: {
|
|
35726
35781
|
frameId: {
|
|
35727
35782
|
type: "number",
|
|
@@ -35811,7 +35866,7 @@ function serverInstructions() {
|
|
|
35811
35866
|
"Call editor_status first: it says whether an editor is connected and whether its addon matches this server.",
|
|
35812
35867
|
"Start the game with editor_run start, never by spawning an engine, and read editor_output after every run:",
|
|
35813
35868
|
"its clean verdict and its error entries are how a run is judged, and editor_run wait is how to wait for one.",
|
|
35814
|
-
"The runtime_* tools work on the running game and the debug_* tools on
|
|
35869
|
+
"The runtime_* tools work on the running game and the debug_* tools on its debugger.",
|
|
35815
35870
|
"A refusal names what would have worked, so read it before calling again."
|
|
35816
35871
|
].join(" ");
|
|
35817
35872
|
}
|
|
@@ -36152,6 +36207,31 @@ function longerWaitNote(tool, op, waitedMs) {
|
|
|
36152
36207
|
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";
|
|
36153
36208
|
return `${tool} ${op} takes no timeoutMs: it was given ${waitedMs}ms, and ${how}.`;
|
|
36154
36209
|
}
|
|
36210
|
+
function keptGameRequest(tool, op, args) {
|
|
36211
|
+
if (tool === "debug_state") {
|
|
36212
|
+
if (op === "output") {
|
|
36213
|
+
return { op: "output" };
|
|
36214
|
+
}
|
|
36215
|
+
if (op === "variables") {
|
|
36216
|
+
const frame = args["frameId"];
|
|
36217
|
+
return { op: "variables", frame: typeof frame === "number" ? frame : 0 };
|
|
36218
|
+
}
|
|
36219
|
+
return { op: "stack" };
|
|
36220
|
+
}
|
|
36221
|
+
switch (op) {
|
|
36222
|
+
case "pause":
|
|
36223
|
+
return { op: "pause" };
|
|
36224
|
+
case "step_over":
|
|
36225
|
+
return { op: "resume", how: "next" };
|
|
36226
|
+
case "step_into":
|
|
36227
|
+
return { op: "resume", how: "step" };
|
|
36228
|
+
case "step_out":
|
|
36229
|
+
return { op: "resume", how: "out" };
|
|
36230
|
+
default:
|
|
36231
|
+
return { op: "resume", how: "continue" };
|
|
36232
|
+
}
|
|
36233
|
+
}
|
|
36234
|
+
var 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.";
|
|
36155
36235
|
function projectSpelling(projectPath, absolutePath) {
|
|
36156
36236
|
return `res://${relative4(realPathOr(projectPath), realPathOr(absolutePath)).replaceAll("\\", "/")}`;
|
|
36157
36237
|
}
|
|
@@ -36239,6 +36319,7 @@ function stopWaitMs() {
|
|
|
36239
36319
|
return waitFromEnv("GDHARNESS_STOP_WAIT_MS", 1e4);
|
|
36240
36320
|
}
|
|
36241
36321
|
var KEEPER_ANSWER_MS = 15000;
|
|
36322
|
+
var KEEPER_DEBUG_MS = PAUSE_MS + STEP_MS + 2 * READ_MS;
|
|
36242
36323
|
var EXIT_REPORTED_WITHIN_MS = 2000;
|
|
36243
36324
|
async function untilGone(pids, withinMs = stopWaitMs()) {
|
|
36244
36325
|
const gone = () => pids.every((pid) => !alive(pid));
|
|
@@ -38139,28 +38220,70 @@ class GodotServer {
|
|
|
38139
38220
|
return this.createErrorResponse(located.reason, PATH_SOLUTIONS);
|
|
38140
38221
|
}
|
|
38141
38222
|
const session = this.dapHoldingBreakpointsOf(project.value.path);
|
|
38142
|
-
const
|
|
38143
|
-
|
|
38144
|
-
|
|
38145
|
-
|
|
38146
|
-
|
|
38147
|
-
|
|
38223
|
+
const scriptPath = realPathOr(located.absolutePath);
|
|
38224
|
+
const line = readPositiveNumber(args, "line");
|
|
38225
|
+
if (line === undefined) {
|
|
38226
|
+
return this.createErrorResponse("line is required: the one-based line to stop on.");
|
|
38227
|
+
}
|
|
38228
|
+
const editor = this.godotBridge.isConnected();
|
|
38229
|
+
let answer;
|
|
38230
|
+
if (editor) {
|
|
38231
|
+
const answered = await this.handleDAP(op === "set" ? "dap_set_breakpoint" : "dap_remove_breakpoint", {
|
|
38232
|
+
...args,
|
|
38233
|
+
scriptPath
|
|
38234
|
+
});
|
|
38235
|
+
if (answered.isError === true) {
|
|
38236
|
+
return answered;
|
|
38237
|
+
}
|
|
38238
|
+
answer = asParams(JSON.parse(answered.content[0]?.text ?? "{}"));
|
|
38239
|
+
} else {
|
|
38240
|
+
if (op === "set") {
|
|
38241
|
+
session.holdBreakpoints([{ scriptPath, lines: [line] }]);
|
|
38242
|
+
} else {
|
|
38243
|
+
session.forgetBreakpoint(scriptPath, line);
|
|
38244
|
+
}
|
|
38245
|
+
answer = { [op === "set" ? "set" : "removed"]: true };
|
|
38148
38246
|
}
|
|
38149
38247
|
const held = session.breakpointsHeld();
|
|
38150
38248
|
writeBreakpointNote(project.value.path, held);
|
|
38249
|
+
const kept = this.keptRun();
|
|
38250
|
+
const told = kept === null ? null : await askTheKeepersDebugger(kept.keeper, {
|
|
38251
|
+
op: "breakpoint",
|
|
38252
|
+
script: projectSpelling(project.value.path, scriptPath),
|
|
38253
|
+
line,
|
|
38254
|
+
set: op === "set"
|
|
38255
|
+
}, KEEPER_DEBUG_MS);
|
|
38151
38256
|
return this.jsonTextResponse({
|
|
38152
|
-
...
|
|
38257
|
+
...answer,
|
|
38153
38258
|
held: this.breakpointsAsProjectSpells(project.value.path, held),
|
|
38154
|
-
|
|
38155
|
-
|
|
38156
|
-
|
|
38259
|
+
...editor ? {
|
|
38260
|
+
setInEditor: this.breakpointsAsProjectSpells(project.value.path, session.breakpointsInEditor())
|
|
38261
|
+
} : {},
|
|
38262
|
+
...told === null ? {} : ("unreached" in told) || typeof told["refused"] === "string" ? {
|
|
38263
|
+
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.`
|
|
38264
|
+
} : {
|
|
38265
|
+
runningGame: `Told to the running game (pid ${kept?.pid ?? "?"}), which this server started.`
|
|
38266
|
+
},
|
|
38267
|
+
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.",
|
|
38268
|
+
...editor ? this.breakpointsAtRisk() : {}
|
|
38157
38269
|
});
|
|
38158
38270
|
}
|
|
38159
38271
|
case "debug_control":
|
|
38160
38272
|
case "debug_state": {
|
|
38273
|
+
const kept = this.keptRun();
|
|
38274
|
+
if (kept !== null) {
|
|
38275
|
+
return await this.askTheKeptGame(kept.keeper, keptGameRequest(tool, op, args), kept.projectPath ?? "");
|
|
38276
|
+
}
|
|
38161
38277
|
if (tool === "debug_state" && op === "output") {
|
|
38162
38278
|
return await this.handleDAP("dap_get_output", args);
|
|
38163
38279
|
}
|
|
38280
|
+
const played = this.activeProcess;
|
|
38281
|
+
if ((op === "pause" || op === "step_out") && played?.throughEditor === true && !exited(played)) {
|
|
38282
|
+
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.", [
|
|
38283
|
+
"Set a breakpoint where it should stop, then step over or into from there",
|
|
38284
|
+
"A game this server starts, as editor_run start does with args, savesIn or env, has a debugger here that pauses and steps out"
|
|
38285
|
+
]);
|
|
38286
|
+
}
|
|
38164
38287
|
const held = await this.debuggedGame(tool === "debug_control" ? "move" : "read");
|
|
38165
38288
|
if (!held.ok) {
|
|
38166
38289
|
return held.response;
|
|
@@ -38304,8 +38427,8 @@ class GodotServer {
|
|
|
38304
38427
|
if (!game.throughEditor) {
|
|
38305
38428
|
return {
|
|
38306
38429
|
ok: false,
|
|
38307
|
-
response: this.createErrorResponse("The running game
|
|
38308
|
-
"editor_run start
|
|
38430
|
+
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.", [
|
|
38431
|
+
"editor_run start starts it again, and the keeper holding a game this server starts is its debugger",
|
|
38309
38432
|
"editor_status says whether an editor has reached this server"
|
|
38310
38433
|
])
|
|
38311
38434
|
};
|
|
@@ -39516,6 +39639,38 @@ class GodotServer {
|
|
|
39516
39639
|
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."
|
|
39517
39640
|
};
|
|
39518
39641
|
}
|
|
39642
|
+
breakpointsForAGame(projectPath) {
|
|
39643
|
+
return this.dapHoldingBreakpointsOf(projectPath).breakpointsHeld().flatMap((one) => one.lines.map((line) => `${projectSpelling(projectPath, one.scriptPath)}:${line}`));
|
|
39644
|
+
}
|
|
39645
|
+
keptRun() {
|
|
39646
|
+
const game = this.activeProcess;
|
|
39647
|
+
if (game === null || game.throughEditor || exited(game) || game.keeper === undefined) {
|
|
39648
|
+
return null;
|
|
39649
|
+
}
|
|
39650
|
+
return { ...game, keeper: game.keeper };
|
|
39651
|
+
}
|
|
39652
|
+
async askTheKeptGame(keeper, request, projectPath) {
|
|
39653
|
+
const answered = await askTheKeepersDebugger(keeper, request, KEEPER_DEBUG_MS);
|
|
39654
|
+
if ("unreached" in answered) {
|
|
39655
|
+
return this.createErrorResponse(`The debugger of the running game did not answer: ${String(answered.unreached)}.`, [
|
|
39656
|
+
"editor_run start starts the game again, with a debugger",
|
|
39657
|
+
"editor_status says whether it is still running"
|
|
39658
|
+
]);
|
|
39659
|
+
}
|
|
39660
|
+
if (typeof answered["refused"] === "string") {
|
|
39661
|
+
return this.createErrorResponse(answered["refused"]);
|
|
39662
|
+
}
|
|
39663
|
+
const stack = answered["stack"];
|
|
39664
|
+
return this.jsonTextResponse({
|
|
39665
|
+
...answered,
|
|
39666
|
+
...Array.isArray(stack) ? {
|
|
39667
|
+
stack: stack.map((frame) => ({
|
|
39668
|
+
...frame,
|
|
39669
|
+
...projectPath !== "" && typeof frame["script"] === "string" && frame["script"].startsWith("res://") ? { path: join23(projectPath, frame["script"].slice("res://".length)) } : {}
|
|
39670
|
+
}))
|
|
39671
|
+
} : {}
|
|
39672
|
+
});
|
|
39673
|
+
}
|
|
39519
39674
|
breakpointsAsProjectSpells(projectPath, held) {
|
|
39520
39675
|
return held.map((one) => ({
|
|
39521
39676
|
scriptPath: projectSpelling(projectPath, one.scriptPath),
|
|
@@ -40177,9 +40332,10 @@ class GodotServer {
|
|
|
40177
40332
|
started.announcedBefore = alreadyPlaying;
|
|
40178
40333
|
started.bootScene = runtimeWaitMs.scene;
|
|
40179
40334
|
this.activeProcess = started;
|
|
40180
|
-
const spawnedInstead = this.godotBridge.isConnected() && editorWouldPlay && (given.value.length > 0 || flags.value.length > 0) ?
|
|
40335
|
+
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}` : "";
|
|
40181
40336
|
const endedForThis = endedToStartThis(ended);
|
|
40182
40337
|
const stranded = wereHereFirst.filter((one) => one.pid !== ended?.pid && alive(one.pid));
|
|
40338
|
+
const handed = this.dapHoldingBreakpointsOf(project.value.path).breakpointsHeld();
|
|
40183
40339
|
const alsoRunning = leftRunningNote(stranded);
|
|
40184
40340
|
return this.jsonTextResponse({
|
|
40185
40341
|
started: true,
|
|
@@ -40189,6 +40345,7 @@ class GodotServer {
|
|
|
40189
40345
|
arguments: cmdArgs,
|
|
40190
40346
|
argsNote: engineFlagsNote(given.value),
|
|
40191
40347
|
transcript: started.transcript,
|
|
40348
|
+
breakpoints: started.keeper === undefined || handed.length === 0 ? undefined : this.breakpointsAsProjectSpells(project.value.path, handed),
|
|
40192
40349
|
profile: profileOnStart(profiled, "gdharness", runtimeAddonRegistered(project.value.path)),
|
|
40193
40350
|
refreshedClasses: refreshed.value,
|
|
40194
40351
|
...scanWaitAnswer(scanned),
|
|
@@ -40197,7 +40354,7 @@ class GodotServer {
|
|
|
40197
40354
|
previousRunLeft: previousRunLeft(ended),
|
|
40198
40355
|
...previousRunHow(ended),
|
|
40199
40356
|
runtime: await this.runtimeUp(project.value.path, alreadyPlaying, runtimeWaitMs, given.value.length > 0),
|
|
40200
|
-
message: `Use editor_output for what it prints and editor_run stop to end it.${spawnedInstead}${endedForThis}${alsoRunning}`
|
|
40357
|
+
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}`
|
|
40201
40358
|
});
|
|
40202
40359
|
}
|
|
40203
40360
|
async runtimeUp(projectPath, before, budget, withArgs) {
|
|
@@ -40724,7 +40881,8 @@ class GodotServer {
|
|
|
40724
40881
|
startedAt,
|
|
40725
40882
|
projectPath,
|
|
40726
40883
|
...this.servedBy(),
|
|
40727
|
-
...profile === undefined ? {} : { profile }
|
|
40884
|
+
...profile === undefined ? {} : { profile },
|
|
40885
|
+
breakpoints: this.breakpointsForAGame(projectPath)
|
|
40728
40886
|
},
|
|
40729
40887
|
...desktop === undefined ? {} : { desktop }
|
|
40730
40888
|
});
|