gdharness 0.13.39 → 0.13.40
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 +244 -74
- package/build/godot/addons/gdharness_editor/bridge_client.gd +53 -0
- package/build/index.js +235 -72
- package/package.json +1 -1
package/build/index.js
CHANGED
|
@@ -25248,6 +25248,8 @@ class GodotDAPClient {
|
|
|
25248
25248
|
halt = null;
|
|
25249
25249
|
holdKnown = false;
|
|
25250
25250
|
breakpoints = new Map;
|
|
25251
|
+
inEditor = new Map;
|
|
25252
|
+
onBreakpointsChanged = null;
|
|
25251
25253
|
constructor(port = portFromEnv("GDHARNESS_DAP_PORT", DEFAULT_DAP_PORT), host = "127.0.0.1") {
|
|
25252
25254
|
this.port = port;
|
|
25253
25255
|
this.host = host;
|
|
@@ -25337,6 +25339,7 @@ class GodotDAPClient {
|
|
|
25337
25339
|
this.attached = false;
|
|
25338
25340
|
this.halt = null;
|
|
25339
25341
|
this.holdKnown = false;
|
|
25342
|
+
this.inEditor.clear();
|
|
25340
25343
|
}
|
|
25341
25344
|
async ensureConnected() {
|
|
25342
25345
|
if (!this.connected) {
|
|
@@ -25433,6 +25436,31 @@ class GodotDAPClient {
|
|
|
25433
25436
|
this.holdKnown = true;
|
|
25434
25437
|
return;
|
|
25435
25438
|
}
|
|
25439
|
+
if (eventName === "breakpoint") {
|
|
25440
|
+
const body = event.body;
|
|
25441
|
+
const breakpoint = body?.["breakpoint"];
|
|
25442
|
+
const source = typeof breakpoint === "object" && breakpoint !== null ? breakpoint["source"] : undefined;
|
|
25443
|
+
const path = typeof source === "object" && source !== null ? source["path"] : undefined;
|
|
25444
|
+
const line = typeof breakpoint === "object" && breakpoint !== null ? breakpoint["line"] : undefined;
|
|
25445
|
+
if (typeof path !== "string" || typeof line !== "number") {
|
|
25446
|
+
return;
|
|
25447
|
+
}
|
|
25448
|
+
if (said(body, "reason") === "removed") {
|
|
25449
|
+
this.inEditor.get(path)?.delete(line);
|
|
25450
|
+
const held = this.breakpoints.get(path);
|
|
25451
|
+
if (held?.delete(line) === true) {
|
|
25452
|
+
if (held.size === 0) {
|
|
25453
|
+
this.breakpoints.delete(path);
|
|
25454
|
+
}
|
|
25455
|
+
this.onBreakpointsChanged?.(this.breakpointsHeld());
|
|
25456
|
+
}
|
|
25457
|
+
return;
|
|
25458
|
+
}
|
|
25459
|
+
const known = this.inEditor.get(path) ?? new Set;
|
|
25460
|
+
known.add(line);
|
|
25461
|
+
this.inEditor.set(path, known);
|
|
25462
|
+
return;
|
|
25463
|
+
}
|
|
25436
25464
|
if (eventName === "terminated" || eventName === "exited") {
|
|
25437
25465
|
this.attached = false;
|
|
25438
25466
|
this.halt = null;
|
|
@@ -25525,23 +25553,46 @@ class GodotDAPClient {
|
|
|
25525
25553
|
async removeBreakpoint(filePath, line) {
|
|
25526
25554
|
const wanted = new Set(this.breakpoints.get(filePath) ?? []);
|
|
25527
25555
|
wanted.delete(line);
|
|
25528
|
-
return await this.sendBreakpoints(filePath, wanted);
|
|
25556
|
+
return await this.sendBreakpoints(filePath, wanted, line);
|
|
25529
25557
|
}
|
|
25530
|
-
async sendBreakpoints(filePath,
|
|
25558
|
+
async sendBreakpoints(filePath, mine, clearing) {
|
|
25531
25559
|
await this.ensureConnected();
|
|
25532
25560
|
await this.initialize();
|
|
25561
|
+
const lines = new Set([...mine, ...this.inEditor.get(filePath) ?? []]);
|
|
25562
|
+
if (clearing !== undefined) {
|
|
25563
|
+
lines.delete(clearing);
|
|
25564
|
+
}
|
|
25533
25565
|
const sorted = Array.from(lines).sort((a, b) => a - b);
|
|
25534
25566
|
const answer = await this.sendRequest("setBreakpoints", {
|
|
25535
25567
|
source: { path: filePath },
|
|
25536
25568
|
breakpoints: sorted.map((breakpointLine) => ({ line: breakpointLine }))
|
|
25537
25569
|
});
|
|
25538
|
-
if (
|
|
25570
|
+
if (mine.size === 0) {
|
|
25539
25571
|
this.breakpoints.delete(filePath);
|
|
25540
25572
|
} else {
|
|
25541
|
-
this.breakpoints.set(filePath, new Set(
|
|
25573
|
+
this.breakpoints.set(filePath, new Set(mine));
|
|
25574
|
+
}
|
|
25575
|
+
if (lines.size === 0) {
|
|
25576
|
+
this.inEditor.delete(filePath);
|
|
25577
|
+
} else {
|
|
25578
|
+
this.inEditor.set(filePath, lines);
|
|
25542
25579
|
}
|
|
25543
25580
|
return answer;
|
|
25544
25581
|
}
|
|
25582
|
+
setBreakpointsSink(sink) {
|
|
25583
|
+
this.onBreakpointsChanged = sink;
|
|
25584
|
+
}
|
|
25585
|
+
breakpointsInEditor() {
|
|
25586
|
+
const theirs = [];
|
|
25587
|
+
for (const [scriptPath, lines] of this.inEditor) {
|
|
25588
|
+
const mine = this.breakpoints.get(scriptPath);
|
|
25589
|
+
const rest = Array.from(lines).filter((line) => !mine?.has(line)).sort((a, b) => a - b);
|
|
25590
|
+
if (rest.length > 0) {
|
|
25591
|
+
theirs.push({ scriptPath, lines: rest });
|
|
25592
|
+
}
|
|
25593
|
+
}
|
|
25594
|
+
return theirs;
|
|
25595
|
+
}
|
|
25545
25596
|
async reapplyBreakpoints() {
|
|
25546
25597
|
const applied = [];
|
|
25547
25598
|
const refused = [];
|
|
@@ -25557,6 +25608,9 @@ class GodotDAPClient {
|
|
|
25557
25608
|
});
|
|
25558
25609
|
}
|
|
25559
25610
|
}
|
|
25611
|
+
if (refused.length > 0) {
|
|
25612
|
+
this.onBreakpointsChanged?.(this.breakpointsHeld());
|
|
25613
|
+
}
|
|
25560
25614
|
return { applied, refused };
|
|
25561
25615
|
}
|
|
25562
25616
|
breakpointsHeld() {
|
|
@@ -25759,14 +25813,10 @@ async function handleDAPTool(client, toolName, args) {
|
|
|
25759
25813
|
}
|
|
25760
25814
|
} catch (error) {
|
|
25761
25815
|
const message = error instanceof Error ? error.message : String(error);
|
|
25816
|
+
const cure = toolName === "dap_set_breakpoint" || toolName === "dap_remove_breakpoint" ? "Breakpoints need the editor open, not a running game: editor_status says whether one is connected, and the path is the script as the project spells it." : "The debug tools answer for a game the editor is playing: start one with editor_run.";
|
|
25762
25817
|
return {
|
|
25763
25818
|
isError: true,
|
|
25764
|
-
content: [
|
|
25765
|
-
{
|
|
25766
|
-
type: "text",
|
|
25767
|
-
text: `DAP tool '${toolName}' failed: ${message}. ` + "The debug tools answer for a game the editor is playing: start one with editor_run."
|
|
25768
|
-
}
|
|
25769
|
-
]
|
|
25819
|
+
content: [{ type: "text", text: `DAP tool '${toolName}' failed: ${message}. ${cure}` }]
|
|
25770
25820
|
};
|
|
25771
25821
|
}
|
|
25772
25822
|
}
|
|
@@ -26071,6 +26121,7 @@ class GodotBridge extends EventEmitter {
|
|
|
26071
26121
|
dapPort: this.connectionInfo?.dapPort,
|
|
26072
26122
|
debugPort: this.connectionInfo?.debugPort,
|
|
26073
26123
|
openedByAServer: this.connectionInfo?.openedByAServer,
|
|
26124
|
+
syncsBreakpoints: this.connectionInfo?.syncsBreakpoints,
|
|
26074
26125
|
pendingRequests: this.pendingRequests.size,
|
|
26075
26126
|
queuedResources: this.resourceQueues.size,
|
|
26076
26127
|
listeningSince: this.listeningSince ?? undefined
|
|
@@ -26213,6 +26264,7 @@ class GodotBridge extends EventEmitter {
|
|
|
26213
26264
|
this.connectionInfo.dapPort = servedPort(message.dap_port);
|
|
26214
26265
|
this.connectionInfo.debugPort = servedPort(message.debug_port);
|
|
26215
26266
|
this.connectionInfo.openedByAServer = message.opened_by_a_server === true;
|
|
26267
|
+
this.connectionInfo.syncsBreakpoints = typeof message.syncs_breakpoints === "boolean" ? message.syncs_breakpoints : undefined;
|
|
26216
26268
|
this.log("info", `Godot ready: ${message.project_path}`);
|
|
26217
26269
|
this.emitBridgeEvent("godot_connected", { projectPath: message.project_path });
|
|
26218
26270
|
}
|
|
@@ -28316,7 +28368,25 @@ function tooNew(unspoken) {
|
|
|
28316
28368
|
const half = unspoken.some((game) => game.protocol > RUNTIME_PROTOCOL) ? "this server is the older half: reconnect it so it spawns the installed version" : "the addon is the older half: reinstall it and restart the game";
|
|
28317
28369
|
return `A game is running, in a protocol this server does not speak ${named}. This server speaks ${RUNTIME_PROTOCOL}, so ${half}.`;
|
|
28318
28370
|
}
|
|
28319
|
-
function chooseRuntime(endpoints, projectPath, unspoken = []) {
|
|
28371
|
+
function chooseRuntime(endpoints, projectPath, unspoken = [], pid) {
|
|
28372
|
+
if (pid !== undefined) {
|
|
28373
|
+
const named = endpoints.find((endpoint) => endpoint.pid === pid);
|
|
28374
|
+
if (named !== undefined) {
|
|
28375
|
+
if (projectPath !== undefined && resolve4(named.project.path) !== resolve4(projectPath)) {
|
|
28376
|
+
return {
|
|
28377
|
+
problem: `The game with pid ${pid} is running ${named.project.path}, not ${resolve4(projectPath)}. Running: ${endpoints.map(describe2).join("; ")}.`
|
|
28378
|
+
};
|
|
28379
|
+
}
|
|
28380
|
+
return { endpoint: named };
|
|
28381
|
+
}
|
|
28382
|
+
const tooNewToo = unspoken.find((game) => game.pid === pid);
|
|
28383
|
+
if (tooNewToo !== undefined) {
|
|
28384
|
+
return { problem: tooNew([tooNewToo]) };
|
|
28385
|
+
}
|
|
28386
|
+
return {
|
|
28387
|
+
problem: endpoints.length === 0 ? `No game with the runtime addon is running, so there is none with pid ${pid}. Start one with editor_run, or play the project from the editor with the addon enabled.` : `No running game has pid ${pid}. Running: ${endpoints.map(describe2).join("; ")}. editor_status lists them under runtimes as they come and go.`
|
|
28388
|
+
};
|
|
28389
|
+
}
|
|
28320
28390
|
if (endpoints.length === 0) {
|
|
28321
28391
|
if (unspoken.length > 0) {
|
|
28322
28392
|
return { problem: tooNew(unspoken) };
|
|
@@ -28347,14 +28417,14 @@ function chooseRuntime(endpoints, projectPath, unspoken = []) {
|
|
|
28347
28417
|
return { endpoint: matching[0] };
|
|
28348
28418
|
}
|
|
28349
28419
|
return {
|
|
28350
|
-
problem: `Several games are running from ${wanted}: ${matching.map(describe2).join("; ")}.
|
|
28420
|
+
problem: `Several games are running from ${wanted}: ${matching.map(describe2).join("; ")}. Pass pid to choose one.`
|
|
28351
28421
|
};
|
|
28352
28422
|
}
|
|
28353
28423
|
if (endpoints.length === 1 && endpoints[0]) {
|
|
28354
28424
|
return { endpoint: endpoints[0] };
|
|
28355
28425
|
}
|
|
28356
28426
|
return {
|
|
28357
|
-
problem: `Several games are running: ${endpoints.map(describe2).join("; ")}. Pass projectPath to choose one.`
|
|
28427
|
+
problem: `Several games are running: ${endpoints.map(describe2).join("; ")}. Pass projectPath to choose one, or pid when they are from one project.`
|
|
28358
28428
|
};
|
|
28359
28429
|
}
|
|
28360
28430
|
var nextRequestId = 1;
|
|
@@ -28765,6 +28835,10 @@ var RUNNING_PROJECT_PATH = {
|
|
|
28765
28835
|
type: "string",
|
|
28766
28836
|
description: "Which game, when more than one is running: the project directory it was started from. Not needed with one game."
|
|
28767
28837
|
};
|
|
28838
|
+
var RUNNING_PID = {
|
|
28839
|
+
type: "number",
|
|
28840
|
+
description: "Which game, when several are running from one project, such as a bench and its workers: its process id, as editor_status lists under runtimes. Not needed with one game per project."
|
|
28841
|
+
};
|
|
28768
28842
|
var SCENE_PATH = {
|
|
28769
28843
|
type: "string",
|
|
28770
28844
|
description: 'Scene file inside the project, such as "scenes/main.tscn" or "res://scenes/main.tscn".'
|
|
@@ -29488,6 +29562,7 @@ var TOOL_SPECS = [
|
|
|
29488
29562
|
description: "Questions about the running game: what is written on the screen, the scene tree, the nodes matching a query, where one node is on screen, what one property reads, or the performance metrics. Needs the game running with the runtime addon.",
|
|
29489
29563
|
parameters: {
|
|
29490
29564
|
projectPath: RUNNING_PROJECT_PATH,
|
|
29565
|
+
pid: RUNNING_PID,
|
|
29491
29566
|
nodePath: {
|
|
29492
29567
|
type: "string",
|
|
29493
29568
|
ops: ["tree", "text", "find", "rect", "property"],
|
|
@@ -29566,6 +29641,7 @@ var TOOL_SPECS = [
|
|
|
29566
29641
|
description: "Sets a property or calls a method on a node in the running game. Needs the game running with the runtime addon.",
|
|
29567
29642
|
parameters: {
|
|
29568
29643
|
projectPath: RUNNING_PROJECT_PATH,
|
|
29644
|
+
pid: RUNNING_PID,
|
|
29569
29645
|
nodePath: { type: "string", description: 'Absolute node path, such as "/root/Main/Player".' },
|
|
29570
29646
|
property: {
|
|
29571
29647
|
type: "string",
|
|
@@ -29605,6 +29681,7 @@ var TOOL_SPECS = [
|
|
|
29605
29681
|
description: "A picture of the running game: the whole screen or one viewport, as an image. Needs the game running with a window.",
|
|
29606
29682
|
parameters: {
|
|
29607
29683
|
projectPath: RUNNING_PROJECT_PATH,
|
|
29684
|
+
pid: RUNNING_PID,
|
|
29608
29685
|
viewportPath: {
|
|
29609
29686
|
type: "string",
|
|
29610
29687
|
ops: ["viewport"],
|
|
@@ -29625,6 +29702,7 @@ var TOOL_SPECS = [
|
|
|
29625
29702
|
description: "Input to the running game: a whole click on a Control or a 3D node named by path, an item chosen out of a menu, typing into whatever has the focus, or a raw action, key, mouse button or mouse motion. All of it works headless, where the window is 64 by 64 and the GUI only takes what is inside it.",
|
|
29626
29703
|
parameters: {
|
|
29627
29704
|
projectPath: RUNNING_PROJECT_PATH,
|
|
29705
|
+
pid: RUNNING_PID,
|
|
29628
29706
|
nodePath: {
|
|
29629
29707
|
type: "string",
|
|
29630
29708
|
ops: ["click", "choose"],
|
|
@@ -29721,6 +29799,7 @@ var TOOL_SPECS = [
|
|
|
29721
29799
|
description: "Lets the running game get on with it and answers when something has happened: a number of frames, a signal, a property reaching a value, or words appearing on a screen. Needs the game running with the runtime addon.",
|
|
29722
29800
|
parameters: {
|
|
29723
29801
|
projectPath: RUNNING_PROJECT_PATH,
|
|
29802
|
+
pid: RUNNING_PID,
|
|
29724
29803
|
frames: {
|
|
29725
29804
|
type: "number",
|
|
29726
29805
|
ops: ["frames"],
|
|
@@ -29756,7 +29835,7 @@ var TOOL_SPECS = [
|
|
|
29756
29835
|
},
|
|
29757
29836
|
{
|
|
29758
29837
|
name: "debug_breakpoint",
|
|
29759
|
-
description: "Sets or removes a breakpoint through the editor's debug adapter. Needs the editor, not a running game: set them first, then editor_run, and the game stops where you asked. 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.",
|
|
29838
|
+
description: "Sets or removes a breakpoint through the editor's debug adapter. Needs the editor, not a running game: set them first, then editor_run, and the game stops where you asked. 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.",
|
|
29760
29839
|
parameters: {
|
|
29761
29840
|
projectPath: PROJECT_PATH,
|
|
29762
29841
|
scriptPath: SCRIPT_PATH,
|
|
@@ -29858,6 +29937,9 @@ function patienceForFrames(frames, atLeast) {
|
|
|
29858
29937
|
function projectSpelling(projectPath, absolutePath) {
|
|
29859
29938
|
return `res://${relative3(realPathOr(projectPath), realPathOr(absolutePath)).replaceAll("\\", "/")}`;
|
|
29860
29939
|
}
|
|
29940
|
+
function whichGame(args) {
|
|
29941
|
+
return { projectPath: args["projectPath"], pid: args["pid"] };
|
|
29942
|
+
}
|
|
29861
29943
|
function describeHalt(halt) {
|
|
29862
29944
|
switch (halt.reason) {
|
|
29863
29945
|
case "breakpoint":
|
|
@@ -29893,6 +29975,19 @@ function runIsUp(run, editorSays) {
|
|
|
29893
29975
|
}
|
|
29894
29976
|
return editorSays ?? stillRunning(run);
|
|
29895
29977
|
}
|
|
29978
|
+
function endedPreviousRun(ended) {
|
|
29979
|
+
if (ended === null) {
|
|
29980
|
+
return;
|
|
29981
|
+
}
|
|
29982
|
+
return ended.pid ?? true;
|
|
29983
|
+
}
|
|
29984
|
+
function endedToStartThis(ended) {
|
|
29985
|
+
if (ended === null) {
|
|
29986
|
+
return "";
|
|
29987
|
+
}
|
|
29988
|
+
const which = ended.pid === null ? "The game the editor was playing" : `The run that was going, pid ${ended.pid},`;
|
|
29989
|
+
return ` ${which} was ended to start this one; its output is no longer what editor_output answers about.`;
|
|
29990
|
+
}
|
|
29896
29991
|
function runtimeVerdict(endpoint, after) {
|
|
29897
29992
|
if (endpoint !== null) {
|
|
29898
29993
|
return { listening: true, pid: endpoint.pid, port: endpoint.port };
|
|
@@ -30633,7 +30728,7 @@ class GodotServer {
|
|
|
30633
30728
|
switch (op) {
|
|
30634
30729
|
case "tree":
|
|
30635
30730
|
return await this.handleRuntimeCommand("get_tree", {
|
|
30636
|
-
|
|
30731
|
+
...whichGame(args),
|
|
30637
30732
|
root: readNonEmptyString(args, "nodePath") ?? "/root",
|
|
30638
30733
|
depth: readPositiveNumber(args, "depth") ?? 3,
|
|
30639
30734
|
include_properties: readBoolean(args, "includeProperties") ?? false
|
|
@@ -30642,36 +30737,36 @@ class GodotServer {
|
|
|
30642
30737
|
return await this.handleFindRuntimeNodes(args);
|
|
30643
30738
|
case "text":
|
|
30644
30739
|
return await this.handleRuntimeCommand("read_text", {
|
|
30645
|
-
|
|
30740
|
+
...whichGame(args),
|
|
30646
30741
|
root: readNonEmptyString(args, "nodePath") ?? "/root",
|
|
30647
30742
|
include_hidden: readBoolean(args, "includeHidden") ?? false,
|
|
30648
30743
|
limit: readPositiveNumber(args, "limit") ?? 500
|
|
30649
30744
|
});
|
|
30650
30745
|
case "rect":
|
|
30651
30746
|
return await this.handleRuntimeCommand("get_rect", {
|
|
30652
|
-
|
|
30747
|
+
...whichGame(args),
|
|
30653
30748
|
path: readNonEmptyString(args, "nodePath") ?? ""
|
|
30654
30749
|
});
|
|
30655
30750
|
case "property":
|
|
30656
30751
|
return await this.handleRuntimeCommand("get_property", {
|
|
30657
|
-
|
|
30752
|
+
...whichGame(args),
|
|
30658
30753
|
path: readNonEmptyString(args, "nodePath") ?? "",
|
|
30659
30754
|
property: readNonEmptyString(args, "property") ?? ""
|
|
30660
30755
|
});
|
|
30661
30756
|
default:
|
|
30662
30757
|
return await this.handleRuntimeCommand("get_metrics", {
|
|
30663
|
-
|
|
30758
|
+
...whichGame(args),
|
|
30664
30759
|
metrics: readArray(args, "metrics") ?? []
|
|
30665
30760
|
});
|
|
30666
30761
|
}
|
|
30667
30762
|
case "runtime_invoke":
|
|
30668
30763
|
return op === "set" ? await this.handleRuntimeCommand("set_property", {
|
|
30669
|
-
|
|
30764
|
+
...whichGame(args),
|
|
30670
30765
|
path: readNonEmptyString(args, "nodePath") ?? "",
|
|
30671
30766
|
property: readString(args, "property") ?? "",
|
|
30672
30767
|
value: args["value"]
|
|
30673
30768
|
}) : await this.handleRuntimeCommand("call_method", {
|
|
30674
|
-
|
|
30769
|
+
...whichGame(args),
|
|
30675
30770
|
path: readNonEmptyString(args, "nodePath") ?? "",
|
|
30676
30771
|
method: readString(args, "method") ?? "",
|
|
30677
30772
|
args: readArray(args, "args") ?? []
|
|
@@ -30681,7 +30776,7 @@ class GodotServer {
|
|
|
30681
30776
|
case "runtime_input":
|
|
30682
30777
|
if (op === "click") {
|
|
30683
30778
|
return await this.handleRuntimeCommand("click", {
|
|
30684
|
-
|
|
30779
|
+
...whichGame(args),
|
|
30685
30780
|
path: readNonEmptyString(args, "nodePath") ?? "",
|
|
30686
30781
|
button: readString(args, "button") ?? "left",
|
|
30687
30782
|
double: readBoolean(args, "doubleClick") ?? false
|
|
@@ -30689,7 +30784,7 @@ class GodotServer {
|
|
|
30689
30784
|
}
|
|
30690
30785
|
if (op === "choose") {
|
|
30691
30786
|
return await this.handleRuntimeCommand("choose", {
|
|
30692
|
-
|
|
30787
|
+
...whichGame(args),
|
|
30693
30788
|
path: readNonEmptyString(args, "nodePath") ?? "",
|
|
30694
30789
|
text: readString(args, "text") ?? "",
|
|
30695
30790
|
...args["index"] === undefined ? {} : { index: args["index"] }
|
|
@@ -30720,7 +30815,9 @@ class GodotServer {
|
|
|
30720
30815
|
return this.jsonTextResponse({
|
|
30721
30816
|
...asParams(JSON.parse(answered.content[0]?.text ?? "{}")),
|
|
30722
30817
|
held: this.breakpointsAsProjectSpells(project.value.path, held),
|
|
30723
|
-
|
|
30818
|
+
setInEditor: this.breakpointsAsProjectSpells(project.value.path, session.breakpointsInEditor()),
|
|
30819
|
+
note: "The editor keeps a breakpoint set this way for one play. Every breakpoint held here is sent again before each play editor_run starts, and kept in the project for the server after a reconnect.",
|
|
30820
|
+
...this.breakpointsAtRisk()
|
|
30724
30821
|
});
|
|
30725
30822
|
}
|
|
30726
30823
|
case "debug_control":
|
|
@@ -30794,6 +30891,16 @@ class GodotServer {
|
|
|
30794
30891
|
response: this.createErrorResponse("No game is running, so there is no debug session to answer for.", ["Start one with editor_run start, which has the editor play it"])
|
|
30795
30892
|
};
|
|
30796
30893
|
}
|
|
30894
|
+
const going = await this.runStillGoing(game);
|
|
30895
|
+
if (!going) {
|
|
30896
|
+
return {
|
|
30897
|
+
ok: false,
|
|
30898
|
+
response: this.createErrorResponse("The last run has already ended, so there is no debug session to answer for.", [
|
|
30899
|
+
"editor_output still reads what it printed, until the next run starts",
|
|
30900
|
+
"editor_run start plays another, which the debugger can hold"
|
|
30901
|
+
])
|
|
30902
|
+
};
|
|
30903
|
+
}
|
|
30797
30904
|
if (!game.throughEditor) {
|
|
30798
30905
|
return {
|
|
30799
30906
|
ok: false,
|
|
@@ -30815,7 +30922,7 @@ class GodotServer {
|
|
|
30815
30922
|
};
|
|
30816
30923
|
}
|
|
30817
30924
|
if (!this.dap().isStopped()) {
|
|
30818
|
-
const hold = await this.holdOf(game);
|
|
30925
|
+
const hold = await this.holdOf(game, going);
|
|
30819
30926
|
if (hold.heldAt !== null) {
|
|
30820
30927
|
if (intent === "move") {
|
|
30821
30928
|
return { ok: true, value: game };
|
|
@@ -31381,6 +31488,12 @@ class GodotServer {
|
|
|
31381
31488
|
this.dapClient.setOutputSink((line) => {
|
|
31382
31489
|
this.writeToTranscript(line);
|
|
31383
31490
|
});
|
|
31491
|
+
this.dapClient.setBreakpointsSink((held) => {
|
|
31492
|
+
const project = this.godotBridge.getStatus().projectPath ?? this.ownProject;
|
|
31493
|
+
if (project !== null) {
|
|
31494
|
+
writeBreakpointNote(project, held);
|
|
31495
|
+
}
|
|
31496
|
+
});
|
|
31384
31497
|
}
|
|
31385
31498
|
return this.dapClient;
|
|
31386
31499
|
}
|
|
@@ -31392,6 +31505,15 @@ class GodotServer {
|
|
|
31392
31505
|
}
|
|
31393
31506
|
return client;
|
|
31394
31507
|
}
|
|
31508
|
+
breakpointsAtRisk() {
|
|
31509
|
+
const status = this.godotBridge.getStatus();
|
|
31510
|
+
if (!status.connected || status.syncsBreakpoints === true) {
|
|
31511
|
+
return {};
|
|
31512
|
+
}
|
|
31513
|
+
return {
|
|
31514
|
+
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."
|
|
31515
|
+
};
|
|
31516
|
+
}
|
|
31395
31517
|
breakpointsAsProjectSpells(projectPath, held) {
|
|
31396
31518
|
return held.map((one) => ({
|
|
31397
31519
|
scriptPath: projectSpelling(projectPath, one.scriptPath),
|
|
@@ -31454,6 +31576,7 @@ class GodotServer {
|
|
|
31454
31576
|
...status.connected ? this.whatTheLaunchedEditorDropped() : {},
|
|
31455
31577
|
startupError: this.bridgeStartupError,
|
|
31456
31578
|
staleNote: stale ? addonMismatch(status.addonVersion, SERVER_VERSION) : undefined,
|
|
31579
|
+
...this.breakpointsAtRisk(),
|
|
31457
31580
|
retryingBridge: this.bridgeRetry === null ? undefined : true,
|
|
31458
31581
|
announcedAt: this.announcedAt ?? undefined,
|
|
31459
31582
|
projectIs: this.projectHasMovedOn() ?? undefined,
|
|
@@ -31505,9 +31628,14 @@ class GodotServer {
|
|
|
31505
31628
|
const playing = await this.editorPlayingState();
|
|
31506
31629
|
await this.pickUpWhatTheEditorIsPlaying();
|
|
31507
31630
|
const current = this.currentRun();
|
|
31508
|
-
|
|
31631
|
+
const active = current === null ? false : await this.runStillGoing(current, playing?.playing ?? null);
|
|
31632
|
+
return {
|
|
31633
|
+
playing,
|
|
31634
|
+
active,
|
|
31635
|
+
hold: current === null ? {} : await this.holdOf(current, active)
|
|
31636
|
+
};
|
|
31509
31637
|
})();
|
|
31510
|
-
const [games, { playing, hold }] = await Promise.all([pinged, asked]);
|
|
31638
|
+
const [games, { playing, active, hold }] = await Promise.all([pinged, asked]);
|
|
31511
31639
|
return this.jsonTextResponse({
|
|
31512
31640
|
editor: {
|
|
31513
31641
|
...this.getEditorStatusPayload(),
|
|
@@ -31519,7 +31647,7 @@ class GodotServer {
|
|
|
31519
31647
|
problem: located.ok ? undefined : GodotLocator.refusal(located).message
|
|
31520
31648
|
},
|
|
31521
31649
|
game: {
|
|
31522
|
-
processActive:
|
|
31650
|
+
processActive: active,
|
|
31523
31651
|
playingInEditor: playing,
|
|
31524
31652
|
...hold,
|
|
31525
31653
|
runtimeConnected: games.some((game) => game.reachable),
|
|
@@ -31732,10 +31860,12 @@ class GodotServer {
|
|
|
31732
31860
|
if (op === "check") {
|
|
31733
31861
|
return await this.checkBoot(engine.value, project.value.path, sceneArgument, args, given.value);
|
|
31734
31862
|
}
|
|
31863
|
+
await this.pickUpWhatTheEditorIsPlaying();
|
|
31735
31864
|
let ended = null;
|
|
31736
|
-
|
|
31865
|
+
const before = this.currentRun();
|
|
31866
|
+
if (before !== null && await this.runStillGoing(before)) {
|
|
31737
31867
|
this.logDebug("Ending the running game before starting another");
|
|
31738
|
-
ended = this.
|
|
31868
|
+
ended = { pid: before.pid ?? this.announcedPidOf(before) ?? null };
|
|
31739
31869
|
await this.endActiveGame("editor_run start, which ends the run that was going before it starts another");
|
|
31740
31870
|
}
|
|
31741
31871
|
const alreadyPlaying = new Set(discoverRuntimes().map((endpoint) => endpoint.pid));
|
|
@@ -31759,8 +31889,8 @@ class GodotServer {
|
|
|
31759
31889
|
const started = this.spawnKeptGame(engine.value, cmdArgs, project.value.path);
|
|
31760
31890
|
this.activeProcess = started;
|
|
31761
31891
|
const spawnedInstead = this.godotBridge.isConnected() && editorWouldPlay && given.value.length > 0 ? " The editor cannot be handed arguments for a game it plays, so this one was started " + "here: the debug_* tools answer only for a game the editor is playing." : "";
|
|
31762
|
-
const endedForThis = ended
|
|
31763
|
-
const stranded = wereHereFirst.filter((one) => one.pid !== ended && alive(one.pid));
|
|
31892
|
+
const endedForThis = endedToStartThis(ended);
|
|
31893
|
+
const stranded = wereHereFirst.filter((one) => one.pid !== ended?.pid && alive(one.pid));
|
|
31764
31894
|
const named = stranded.map((one) => one.protocol === null ? `pid ${one.pid}` : `pid ${one.pid} on protocol ${one.protocol}`).join(", ");
|
|
31765
31895
|
const alsoRunning = stranded.length === 0 ? "" : ` A game of this project was already running and still is: ${named}. Starting this one did not end it and nothing here can, because this server did not start it. End it yourself, or bring the halves to one version where a protocol is named.`;
|
|
31766
31896
|
return this.jsonTextResponse({
|
|
@@ -31771,23 +31901,38 @@ class GodotServer {
|
|
|
31771
31901
|
arguments: cmdArgs,
|
|
31772
31902
|
transcript: started.transcript,
|
|
31773
31903
|
refreshedClasses: refreshed.value,
|
|
31774
|
-
endedPreviousRun: ended
|
|
31904
|
+
endedPreviousRun: endedPreviousRun(ended),
|
|
31775
31905
|
runtime: await this.runtimeUp(project.value.path, alreadyPlaying, runtimeWaitMs, given.value.length > 0),
|
|
31776
31906
|
message: `Use editor_output for what it prints and editor_run stop to end it.${spawnedInstead}${endedForThis}${alsoRunning}`
|
|
31777
31907
|
});
|
|
31778
31908
|
}
|
|
31779
|
-
async gameIsUp() {
|
|
31780
|
-
const run = this.currentRun();
|
|
31781
|
-
const editorSays = run?.throughEditor === true ? (await this.editorPlayingState())?.playing ?? null : null;
|
|
31782
|
-
return runIsUp(run, editorSays);
|
|
31783
|
-
}
|
|
31784
31909
|
async runtimeUp(projectPath, before, budgetMs, withArgs) {
|
|
31785
31910
|
if (!existsSync9(join13(projectPath, RUNTIME_AUTOLOAD.path))) {
|
|
31786
31911
|
return runtimeVerdict(null, { addon: false, budgetMs, heldAt: null, running: false, withArgs });
|
|
31787
31912
|
}
|
|
31913
|
+
let asking = null;
|
|
31914
|
+
let editorAskedAt = 0;
|
|
31915
|
+
let editorSaysGoing = true;
|
|
31788
31916
|
const endpoint = await announcedSince(projectPath, before, {
|
|
31789
31917
|
budgetMs,
|
|
31790
|
-
giveUp: () =>
|
|
31918
|
+
giveUp: () => {
|
|
31919
|
+
if (this.dapClient?.isStopped() === true) {
|
|
31920
|
+
return true;
|
|
31921
|
+
}
|
|
31922
|
+
const run = this.currentRun();
|
|
31923
|
+
if (run === null || !run.throughEditor) {
|
|
31924
|
+
return !stillRunning(run);
|
|
31925
|
+
}
|
|
31926
|
+
if (asking === null && Date.now() - editorAskedAt >= RUN_POLL_MS) {
|
|
31927
|
+
editorAskedAt = Date.now();
|
|
31928
|
+
asking = this.runStillGoing(run).then((going) => {
|
|
31929
|
+
editorSaysGoing = going;
|
|
31930
|
+
}).finally(() => {
|
|
31931
|
+
asking = null;
|
|
31932
|
+
});
|
|
31933
|
+
}
|
|
31934
|
+
return !editorSaysGoing;
|
|
31935
|
+
}
|
|
31791
31936
|
});
|
|
31792
31937
|
const going = this.currentRun();
|
|
31793
31938
|
if (endpoint !== null && going?.throughEditor === true && going.announcedPid === undefined) {
|
|
@@ -31797,7 +31942,7 @@ class GodotServer {
|
|
|
31797
31942
|
addon: true,
|
|
31798
31943
|
budgetMs,
|
|
31799
31944
|
heldAt: this.dapClient?.whereItStopped() ?? null,
|
|
31800
|
-
running: await this.
|
|
31945
|
+
running: endpoint !== null || going !== null && await this.runStillGoing(going),
|
|
31801
31946
|
withArgs
|
|
31802
31947
|
});
|
|
31803
31948
|
}
|
|
@@ -31838,7 +31983,8 @@ class GodotServer {
|
|
|
31838
31983
|
startedAt,
|
|
31839
31984
|
exitCode: null,
|
|
31840
31985
|
throughEditor: true,
|
|
31841
|
-
brokeOn: null
|
|
31986
|
+
brokeOn: null,
|
|
31987
|
+
announcedBefore: alreadyPlaying
|
|
31842
31988
|
};
|
|
31843
31989
|
this.activeProcess = played;
|
|
31844
31990
|
writeEditorRunNote({ projectPath, transcript: transcript.path, startedAt });
|
|
@@ -31849,14 +31995,14 @@ class GodotServer {
|
|
|
31849
31995
|
scene: scene === null ? "the main scene" : `res://${scene}`,
|
|
31850
31996
|
refreshedClasses,
|
|
31851
31997
|
debugPort: readNumber(asParams(JSON.parse(answer.content[0]?.text ?? "{}")), "debugPort"),
|
|
31852
|
-
endedPreviousRun: ended
|
|
31998
|
+
endedPreviousRun: endedPreviousRun(ended),
|
|
31853
31999
|
breakpoints: breakpoints.applied.length === 0 ? undefined : this.breakpointsAsProjectSpells(projectPath, breakpoints.applied),
|
|
31854
32000
|
breakpointsRefused: breakpoints.refused.length === 0 ? undefined : breakpoints.refused.map((one) => ({
|
|
31855
32001
|
scriptPath: projectSpelling(projectPath, one.scriptPath),
|
|
31856
32002
|
reason: one.reason
|
|
31857
32003
|
})),
|
|
31858
32004
|
runtime: await this.runtimeUp(projectPath, alreadyPlaying, runtimeWaitMs, false),
|
|
31859
|
-
message: "The editor is playing it, so its debugger holds it: the debug_* tools can reach it, " + "editor_output reads its console through the debug adapter, and editor_run stop ends it." + (ended
|
|
32005
|
+
message: "The editor is playing it, so its debugger holds it: the debug_* tools can reach it, " + "editor_output reads its console through the debug adapter, and editor_run stop ends it." + endedToStartThis(ended)
|
|
31860
32006
|
});
|
|
31861
32007
|
}
|
|
31862
32008
|
drainEditorOutput(game) {
|
|
@@ -32072,15 +32218,19 @@ class GodotServer {
|
|
|
32072
32218
|
brokeOn: null,
|
|
32073
32219
|
pickedUpPlaying: true
|
|
32074
32220
|
};
|
|
32075
|
-
const announced = this.theOneAnnouncedForOurProject();
|
|
32076
|
-
if (announced !== undefined) {
|
|
32077
|
-
picked.announcedPid = announced.pid;
|
|
32078
|
-
}
|
|
32079
32221
|
this.activeProcess = picked;
|
|
32080
32222
|
}
|
|
32081
|
-
|
|
32082
|
-
|
|
32083
|
-
|
|
32223
|
+
announcedPidOf(run) {
|
|
32224
|
+
if (!run.throughEditor || run.announcedPid !== undefined) {
|
|
32225
|
+
return run.announcedPid;
|
|
32226
|
+
}
|
|
32227
|
+
const before = run.announcedBefore ?? new Set;
|
|
32228
|
+
const fresh = this.allAnnouncedForOurProject(runtimesAnnounced().running).filter((one) => !before.has(one.pid));
|
|
32229
|
+
const theOne = fresh.length === 1 ? fresh[0] : undefined;
|
|
32230
|
+
if (theOne !== undefined) {
|
|
32231
|
+
run.announcedPid = theOne.pid;
|
|
32232
|
+
}
|
|
32233
|
+
return run.announcedPid;
|
|
32084
32234
|
}
|
|
32085
32235
|
adoptRecordedRun() {
|
|
32086
32236
|
const record = readRunRecord();
|
|
@@ -32210,12 +32360,22 @@ class GodotServer {
|
|
|
32210
32360
|
}
|
|
32211
32361
|
const budgetMs = readPositiveNumber(args, "timeoutMs") ?? WAIT_FOR_RUN_MS;
|
|
32212
32362
|
const until = Date.now() + budgetMs;
|
|
32213
|
-
while (
|
|
32363
|
+
while (await this.runStillGoing(run) && Date.now() < until) {
|
|
32214
32364
|
this.drainEditorOutput(run);
|
|
32215
32365
|
this.drainTranscript(run);
|
|
32216
32366
|
await delay3(RUN_POLL_MS);
|
|
32217
32367
|
}
|
|
32218
|
-
return await this.handleGetDebugOutput(args,
|
|
32368
|
+
return await this.handleGetDebugOutput(args, await this.runStillGoing(run) ? budgetMs : undefined);
|
|
32369
|
+
}
|
|
32370
|
+
async runStillGoing(run, editorSays) {
|
|
32371
|
+
if (!run.throughEditor) {
|
|
32372
|
+
return stillRunning(run);
|
|
32373
|
+
}
|
|
32374
|
+
const announced = this.announcedPidOf(run);
|
|
32375
|
+
if (announced !== undefined && !alive(announced)) {
|
|
32376
|
+
return false;
|
|
32377
|
+
}
|
|
32378
|
+
return runIsUp(run, editorSays === undefined ? (await this.editorPlayingState())?.playing ?? null : editorSays);
|
|
32219
32379
|
}
|
|
32220
32380
|
async handleGetDebugOutput(args, waitedMs) {
|
|
32221
32381
|
await this.pickUpWhatTheEditorIsPlaying();
|
|
@@ -32225,7 +32385,8 @@ class GodotServer {
|
|
|
32225
32385
|
}
|
|
32226
32386
|
this.drainEditorOutput(run);
|
|
32227
32387
|
this.drainTranscript(run);
|
|
32228
|
-
|
|
32388
|
+
const going = await this.runStillGoing(run);
|
|
32389
|
+
if (run.endedUnwatched !== true && !going) {
|
|
32229
32390
|
run.log.finish();
|
|
32230
32391
|
run.endedUnwatched = run.exitCode === null;
|
|
32231
32392
|
}
|
|
@@ -32236,12 +32397,13 @@ class GodotServer {
|
|
|
32236
32397
|
contains: readNonEmptyString(args, "contains"),
|
|
32237
32398
|
limit: readPositiveNumber(args, "limit") ?? 200
|
|
32238
32399
|
});
|
|
32239
|
-
const hold = await this.holdOf(run);
|
|
32400
|
+
const hold = await this.holdOf(run, going);
|
|
32240
32401
|
const wanted = readBoolean(args, "cpu") ?? false;
|
|
32241
|
-
const
|
|
32402
|
+
const processToAsk = run.pid ?? this.announcedPidOf(run) ?? null;
|
|
32403
|
+
const cpuSeconds = wanted && processToAsk !== null && stillRunning(run) ? await cpuSecondsOf(processToAsk) : undefined;
|
|
32242
32404
|
const notes = [];
|
|
32243
32405
|
if (wanted && cpuSeconds === undefined && stillRunning(run)) {
|
|
32244
|
-
notes.push(
|
|
32406
|
+
notes.push(processToAsk === null ? "cpu was asked for and there is no process to ask: the editor is playing this run and holds it, and the game announced no runtime, so nothing here has its process id. A game with the runtime addon announces one; editor_run start without a connected editor, or with args, starts the game here instead and that run answers cpuSeconds either way." : "cpu was asked for and this platform would not say what the run has used.");
|
|
32245
32407
|
}
|
|
32246
32408
|
if (waitedMs !== undefined) {
|
|
32247
32409
|
notes.push(`The wait of ${waitedMs}ms ran out and the run is still going, so this is what it had printed by then rather than everything it will print. editor_run wait again to keep waiting.`);
|
|
@@ -32265,10 +32427,10 @@ class GodotServer {
|
|
|
32265
32427
|
notes.push(`Everything this run has printed is in ${run.transcript}, uncapped and still being written. Read it for output, not for whether the run is alive: a transcript that stops growing is a run between prints. running above is asked of the operating system while there is a process to ask about, and for an editor-played run that announced no runtime it is the editor's answer instead, which can lag after the game has gone.`);
|
|
32266
32428
|
}
|
|
32267
32429
|
return this.jsonTextResponse({
|
|
32268
|
-
running:
|
|
32430
|
+
running: going,
|
|
32269
32431
|
exitCode: run.exitCode,
|
|
32270
32432
|
through: run.throughEditor ? "editor" : "gdharness",
|
|
32271
|
-
pid: run.pid,
|
|
32433
|
+
pid: run.pid ?? this.announcedPidOf(run) ?? null,
|
|
32272
32434
|
errors: run.log.count("error"),
|
|
32273
32435
|
warnings: run.log.count("warning"),
|
|
32274
32436
|
clean: run.consoleLost === true ? undefined : run.log.count("error") === 0,
|
|
@@ -32294,18 +32456,19 @@ class GodotServer {
|
|
|
32294
32456
|
}
|
|
32295
32457
|
this.drainEditorOutput(stopped);
|
|
32296
32458
|
this.drainTranscript(stopped);
|
|
32459
|
+
const wasRunning = await this.runStillGoing(stopped);
|
|
32297
32460
|
this.logDebug("Stopping the running game");
|
|
32298
32461
|
await this.endActiveGame("editor_run stop");
|
|
32299
32462
|
return this.jsonTextResponse({
|
|
32300
32463
|
stopped: true,
|
|
32301
32464
|
through: stopped.throughEditor ? "editor" : "gdharness",
|
|
32302
|
-
endedPid: stopped.pid,
|
|
32303
|
-
exitedBeforeStop: stopped.exitCode !== null,
|
|
32465
|
+
endedPid: stopped.pid ?? this.announcedPidOf(stopped) ?? null,
|
|
32466
|
+
exitedBeforeStop: stopped.exitCode !== null || !wasRunning,
|
|
32304
32467
|
exitCode: stopped.exitCode,
|
|
32305
32468
|
errors: stopped.log.count("error"),
|
|
32306
32469
|
warnings: stopped.log.count("warning"),
|
|
32307
32470
|
clean: stopped.log.count("error") === 0,
|
|
32308
|
-
note: stopped.throughEditor ? "The editor was asked to stop the scene it is playing. Anything that game started for itself, with OS.create_process or otherwise, is not the editor's to stop and is still running." : "The process named under endedPid was ended. Anything that game started for itself, with OS.create_process or otherwise, is a separate process and is still running.",
|
|
32471
|
+
note: !wasRunning ? "This run was over before the stop, so nothing was ended here: editor_output has what it printed and how it ended. Anything that game started for itself, with OS.create_process or otherwise, is a separate process and may still be running." : stopped.throughEditor ? "The editor was asked to stop the scene it is playing. Anything that game started for itself, with OS.create_process or otherwise, is not the editor's to stop and is still running." : "The process named under endedPid was ended. Anything that game started for itself, with OS.create_process or otherwise, is a separate process and is still running.",
|
|
32309
32472
|
entries: forAnswer(stopped.log.select({ severity: "warning", sinceLastCall: false, limit: 200 }).entries)
|
|
32310
32473
|
});
|
|
32311
32474
|
}
|
|
@@ -32462,21 +32625,21 @@ class GodotServer {
|
|
|
32462
32625
|
if (run === null || !run.throughEditor || !stillRunning(run) || session === null) {
|
|
32463
32626
|
return null;
|
|
32464
32627
|
}
|
|
32465
|
-
|
|
32466
|
-
if (its !== endpoint.pid || !session.holdIsKnown()) {
|
|
32628
|
+
if (this.announcedPidOf(run) !== endpoint.pid || !session.holdIsKnown()) {
|
|
32467
32629
|
return null;
|
|
32468
32630
|
}
|
|
32469
32631
|
return session.whereItStopped();
|
|
32470
32632
|
}
|
|
32471
|
-
async holdOf(run) {
|
|
32472
|
-
if (!run.throughEditor || !
|
|
32633
|
+
async holdOf(run, going) {
|
|
32634
|
+
if (!run.throughEditor || !(going ?? await this.runStillGoing(run))) {
|
|
32473
32635
|
return { heldAt: null };
|
|
32474
32636
|
}
|
|
32475
32637
|
const session = this.dapClient;
|
|
32476
32638
|
if (session?.holdIsKnown()) {
|
|
32477
32639
|
return { heldAt: session.whereItStopped() };
|
|
32478
32640
|
}
|
|
32479
|
-
const
|
|
32641
|
+
const its = this.announcedPidOf(run);
|
|
32642
|
+
const endpoint = its === undefined ? undefined : runtimesAnnounced().running.find((one) => one.pid === its);
|
|
32480
32643
|
if (endpoint === undefined) {
|
|
32481
32644
|
return {
|
|
32482
32645
|
heldAt: undefined,
|
|
@@ -32501,9 +32664,9 @@ class GodotServer {
|
|
|
32501
32664
|
return { heldAt: undefined, heldUnknown: true, heldNote: reply.message };
|
|
32502
32665
|
}
|
|
32503
32666
|
async handleRuntimeCommand(command, args, timeoutMs = this.runtimeTimeoutMs()) {
|
|
32504
|
-
const { op: _op, projectPath, ...params } = asParams(args);
|
|
32667
|
+
const { op: _op, projectPath, pid, ...params } = asParams(args);
|
|
32505
32668
|
const announced = runtimesAnnounced();
|
|
32506
|
-
const choice = chooseRuntime(announced.running, typeof projectPath === "string" ? projectPath : this.ownProject ?? undefined, announced.unspoken);
|
|
32669
|
+
const choice = chooseRuntime(announced.running, typeof projectPath === "string" ? projectPath : this.ownProject ?? undefined, announced.unspoken, typeof pid === "number" ? pid : undefined);
|
|
32507
32670
|
if ("problem" in choice) {
|
|
32508
32671
|
return this.createErrorResponse(choice.problem);
|
|
32509
32672
|
}
|
|
@@ -32569,7 +32732,7 @@ class GodotServer {
|
|
|
32569
32732
|
const includeHidden = readBoolean(args, "includeHidden");
|
|
32570
32733
|
return await this.handleRuntimeCommand("find_nodes", {
|
|
32571
32734
|
...filters,
|
|
32572
|
-
|
|
32735
|
+
...whichGame(args),
|
|
32573
32736
|
root: readNonEmptyString(args, "nodePath") ?? "/root",
|
|
32574
32737
|
limit: readPositiveNumber(args, "limit") ?? 100,
|
|
32575
32738
|
...property === undefined ? {} : { property },
|
|
@@ -32580,18 +32743,18 @@ class GodotServer {
|
|
|
32580
32743
|
if (op === "frames") {
|
|
32581
32744
|
const frames = readPositiveNumber(args, "frames") ?? 1;
|
|
32582
32745
|
const waited = patienceForFrames(frames, this.runtimeTimeoutMs());
|
|
32583
|
-
return await this.handleRuntimeCommand("wait_frames", {
|
|
32746
|
+
return await this.handleRuntimeCommand("wait_frames", { ...whichGame(args), frames }, waited);
|
|
32584
32747
|
}
|
|
32585
32748
|
const timeoutMs = readPositiveNumber(args, "timeoutMs") ?? 5000;
|
|
32586
32749
|
const nodePath = readNonEmptyString(args, "nodePath") ?? "";
|
|
32587
32750
|
const patience = Math.max(this.runtimeTimeoutMs(), timeoutMs + 5000);
|
|
32588
32751
|
return op === "signal" ? await this.handleRuntimeCommand("wait_signal", {
|
|
32589
|
-
|
|
32752
|
+
...whichGame(args),
|
|
32590
32753
|
path: nodePath,
|
|
32591
32754
|
signal: readString(args, "signal") ?? "",
|
|
32592
32755
|
timeout_ms: timeoutMs
|
|
32593
32756
|
}, patience) : await this.handleRuntimeCommand("wait_until", {
|
|
32594
|
-
|
|
32757
|
+
...whichGame(args),
|
|
32595
32758
|
path: nodePath,
|
|
32596
32759
|
property: readString(args, "property") ?? "",
|
|
32597
32760
|
value: args["value"],
|