gdharness 0.13.41 → 0.13.43
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/index.js
CHANGED
|
@@ -10168,7 +10168,7 @@ function defectReport(where, error, context = {}) {
|
|
|
10168
10168
|
}
|
|
10169
10169
|
|
|
10170
10170
|
// src/server.ts
|
|
10171
|
-
import { execFile as
|
|
10171
|
+
import { execFile as execFile5, spawn } from "node:child_process";
|
|
10172
10172
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
10173
10173
|
import {
|
|
10174
10174
|
appendFileSync,
|
|
@@ -10183,10 +10183,10 @@ import {
|
|
|
10183
10183
|
readSync
|
|
10184
10184
|
} from "node:fs";
|
|
10185
10185
|
import { tmpdir as tmpdir4 } from "node:os";
|
|
10186
|
-
import { basename as
|
|
10186
|
+
import { basename as basename3, dirname as dirname6, join as join13, normalize as normalize2, relative as relative3 } from "node:path";
|
|
10187
10187
|
import { setTimeout as delay3 } from "node:timers/promises";
|
|
10188
10188
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
10189
|
-
import { promisify as
|
|
10189
|
+
import { promisify as promisify5 } from "node:util";
|
|
10190
10190
|
|
|
10191
10191
|
// node_modules/zod/v3/helpers/util.js
|
|
10192
10192
|
var util;
|
|
@@ -25831,7 +25831,7 @@ function dictionary(entries) {
|
|
|
25831
25831
|
|
|
25832
25832
|
// src/game-log.ts
|
|
25833
25833
|
import { StringDecoder } from "node:string_decoder";
|
|
25834
|
-
var HEADLINE = /^(USER )?(SCRIPT ERROR|ERROR|WARNING):\s?(.*)$/;
|
|
25834
|
+
var HEADLINE = /^(USER )?(SCRIPT ERROR|SHADER ERROR|ERROR|WARNING):\s?(.*)$/;
|
|
25835
25835
|
var COLOUR_CODE = new RegExp(`${String.fromCharCode(27)}\\[[0-9;]*m`, "g");
|
|
25836
25836
|
function announced(line) {
|
|
25837
25837
|
const match = HEADLINE.exec(line);
|
|
@@ -27709,25 +27709,159 @@ async function handleLSPTool(client, toolName, args) {
|
|
|
27709
27709
|
}
|
|
27710
27710
|
}
|
|
27711
27711
|
|
|
27712
|
-
// src/process-
|
|
27712
|
+
// src/process-children.ts
|
|
27713
27713
|
import { execFile as execFile3 } from "node:child_process";
|
|
27714
|
+
import { basename } from "node:path";
|
|
27714
27715
|
import { promisify as promisify3 } from "node:util";
|
|
27715
27716
|
var run3 = promisify3(execFile3);
|
|
27716
|
-
var ASK_TIMEOUT_MS =
|
|
27717
|
+
var ASK_TIMEOUT_MS = 15000;
|
|
27718
|
+
async function processTree() {
|
|
27719
|
+
try {
|
|
27720
|
+
const printed = process.platform === "win32" ? await askWindows() : await askPosix();
|
|
27721
|
+
return parseProcessTable(printed);
|
|
27722
|
+
} catch {
|
|
27723
|
+
return;
|
|
27724
|
+
}
|
|
27725
|
+
}
|
|
27726
|
+
function parseProcessTable(printed) {
|
|
27727
|
+
const tree = new Map;
|
|
27728
|
+
for (const line of printed.split(/\r?\n/)) {
|
|
27729
|
+
const fields = /^\s*(\d+)\s+(\d+)(?:\s+(.*))?$/.exec(line);
|
|
27730
|
+
if (fields === null) {
|
|
27731
|
+
continue;
|
|
27732
|
+
}
|
|
27733
|
+
const pid = Number(fields[1]);
|
|
27734
|
+
const parent = Number(fields[2]);
|
|
27735
|
+
if (pid > 0) {
|
|
27736
|
+
tree.set(pid, { parent, command: (fields[3] ?? "").trim() });
|
|
27737
|
+
}
|
|
27738
|
+
}
|
|
27739
|
+
return tree;
|
|
27740
|
+
}
|
|
27741
|
+
function childrenIn(tree, pid) {
|
|
27742
|
+
const children = [];
|
|
27743
|
+
for (const [child, listed] of tree) {
|
|
27744
|
+
if (listed.parent === pid) {
|
|
27745
|
+
children.push(child);
|
|
27746
|
+
}
|
|
27747
|
+
}
|
|
27748
|
+
return children;
|
|
27749
|
+
}
|
|
27750
|
+
function descendantsIn(tree, pid) {
|
|
27751
|
+
const found = [];
|
|
27752
|
+
const queue = [pid];
|
|
27753
|
+
const seen = new Set([pid]);
|
|
27754
|
+
while (queue.length > 0) {
|
|
27755
|
+
const next = queue.shift();
|
|
27756
|
+
if (next === undefined) {
|
|
27757
|
+
break;
|
|
27758
|
+
}
|
|
27759
|
+
for (const child of childrenIn(tree, next)) {
|
|
27760
|
+
if (!seen.has(child)) {
|
|
27761
|
+
seen.add(child);
|
|
27762
|
+
found.push(child);
|
|
27763
|
+
queue.push(child);
|
|
27764
|
+
}
|
|
27765
|
+
}
|
|
27766
|
+
}
|
|
27767
|
+
return found;
|
|
27768
|
+
}
|
|
27769
|
+
function ancestorsIn(tree, pid, root) {
|
|
27770
|
+
const chain = [];
|
|
27771
|
+
const seen = new Set([pid]);
|
|
27772
|
+
let parent = tree.get(pid)?.parent;
|
|
27773
|
+
while (parent !== undefined && !seen.has(parent)) {
|
|
27774
|
+
chain.push(parent);
|
|
27775
|
+
if (parent === root) {
|
|
27776
|
+
break;
|
|
27777
|
+
}
|
|
27778
|
+
seen.add(parent);
|
|
27779
|
+
parent = tree.get(parent)?.parent;
|
|
27780
|
+
}
|
|
27781
|
+
return chain;
|
|
27782
|
+
}
|
|
27783
|
+
function readCommandLine(command) {
|
|
27784
|
+
const words = commandWords(command);
|
|
27785
|
+
const executable = words[0] === undefined ? "" : basename(words[0].replaceAll("\\", "/"));
|
|
27786
|
+
const at = words.indexOf("--path");
|
|
27787
|
+
let projectPath = null;
|
|
27788
|
+
if (at !== -1 && at + 1 < words.length) {
|
|
27789
|
+
const taken = [];
|
|
27790
|
+
for (const word of words.slice(at + 1)) {
|
|
27791
|
+
if (taken.length > 0 && (word.startsWith("-") || /^[a-z]+:\/\//.test(word))) {
|
|
27792
|
+
break;
|
|
27793
|
+
}
|
|
27794
|
+
taken.push(word);
|
|
27795
|
+
}
|
|
27796
|
+
projectPath = taken.join(" ");
|
|
27797
|
+
}
|
|
27798
|
+
return {
|
|
27799
|
+
executable,
|
|
27800
|
+
projectPath,
|
|
27801
|
+
editor: words.some((word) => word === "-e" || word === "--editor")
|
|
27802
|
+
};
|
|
27803
|
+
}
|
|
27804
|
+
function commandWords(command) {
|
|
27805
|
+
const words = [];
|
|
27806
|
+
let word = "";
|
|
27807
|
+
let quoted = false;
|
|
27808
|
+
let started = false;
|
|
27809
|
+
for (const char of command) {
|
|
27810
|
+
if (char === '"') {
|
|
27811
|
+
quoted = !quoted;
|
|
27812
|
+
started = true;
|
|
27813
|
+
} else if (!quoted && /\s/.test(char)) {
|
|
27814
|
+
if (started) {
|
|
27815
|
+
words.push(word);
|
|
27816
|
+
word = "";
|
|
27817
|
+
started = false;
|
|
27818
|
+
}
|
|
27819
|
+
} else {
|
|
27820
|
+
word += char;
|
|
27821
|
+
started = true;
|
|
27822
|
+
}
|
|
27823
|
+
}
|
|
27824
|
+
if (started) {
|
|
27825
|
+
words.push(word);
|
|
27826
|
+
}
|
|
27827
|
+
return words;
|
|
27828
|
+
}
|
|
27829
|
+
async function askWindows() {
|
|
27830
|
+
const { stdout } = await run3("powershell.exe", [
|
|
27831
|
+
"-NoProfile",
|
|
27832
|
+
"-NonInteractive",
|
|
27833
|
+
"-Command",
|
|
27834
|
+
'Get-CimInstance Win32_Process | ForEach-Object { "$($_.ProcessId) $($_.ParentProcessId) $($_.CommandLine)" }'
|
|
27835
|
+
], { timeout: ASK_TIMEOUT_MS, windowsHide: true, maxBuffer: 16 * 1024 * 1024 });
|
|
27836
|
+
return stdout;
|
|
27837
|
+
}
|
|
27838
|
+
async function askPosix() {
|
|
27839
|
+
const { stdout } = await run3("ps", ["-e", "-o", "pid=,ppid=,args="], {
|
|
27840
|
+
timeout: ASK_TIMEOUT_MS,
|
|
27841
|
+
maxBuffer: 16 * 1024 * 1024
|
|
27842
|
+
});
|
|
27843
|
+
return stdout;
|
|
27844
|
+
}
|
|
27845
|
+
|
|
27846
|
+
// src/process-time.ts
|
|
27847
|
+
import { execFile as execFile4 } from "node:child_process";
|
|
27848
|
+
import { promisify as promisify4 } from "node:util";
|
|
27849
|
+
var run4 = promisify4(execFile4);
|
|
27850
|
+
var ASK_TIMEOUT_MS2 = 6000;
|
|
27717
27851
|
async function cpuSecondsOf(pid) {
|
|
27718
27852
|
try {
|
|
27719
|
-
return process.platform === "win32" ? await
|
|
27853
|
+
return process.platform === "win32" ? await askWindows2(pid) : await askPosix2(pid);
|
|
27720
27854
|
} catch {
|
|
27721
27855
|
return;
|
|
27722
27856
|
}
|
|
27723
27857
|
}
|
|
27724
|
-
async function
|
|
27725
|
-
const { stdout } = await
|
|
27858
|
+
async function askWindows2(pid) {
|
|
27859
|
+
const { stdout } = await run4("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", `(Get-Process -Id ${pid}).CPU`], { timeout: ASK_TIMEOUT_MS2, windowsHide: true });
|
|
27726
27860
|
const seconds = Number(stdout.trim().replace(",", "."));
|
|
27727
27861
|
return Number.isFinite(seconds) ? seconds : undefined;
|
|
27728
27862
|
}
|
|
27729
|
-
async function
|
|
27730
|
-
const { stdout } = await
|
|
27863
|
+
async function askPosix2(pid) {
|
|
27864
|
+
const { stdout } = await run4("ps", ["-o", "time=", "-p", String(pid)], { timeout: ASK_TIMEOUT_MS2 });
|
|
27731
27865
|
return secondsFromClock(stdout.trim());
|
|
27732
27866
|
}
|
|
27733
27867
|
function secondsFromClock(printed) {
|
|
@@ -28156,11 +28290,11 @@ function restartSettled(projectPath) {
|
|
|
28156
28290
|
|
|
28157
28291
|
// src/run-record.ts
|
|
28158
28292
|
import { execFileSync } from "node:child_process";
|
|
28159
|
-
import { mkdirSync as mkdirSync5, openSync, readdirSync as readdirSync5, readFileSync as readFileSync11, rmSync as rmSync5, statSync as
|
|
28160
|
-
import { basename, join as join11 } from "node:path";
|
|
28293
|
+
import { mkdirSync as mkdirSync5, openSync, readdirSync as readdirSync5, readFileSync as readFileSync11, rmSync as rmSync5, statSync as statSync4, writeFileSync as writeFileSync6 } from "node:fs";
|
|
28294
|
+
import { basename as basename2, join as join11 } from "node:path";
|
|
28161
28295
|
|
|
28162
28296
|
// src/runtime-client.ts
|
|
28163
|
-
import { existsSync as existsSync7, readdirSync as readdirSync4, readFileSync as readFileSync10, unlinkSync } from "node:fs";
|
|
28297
|
+
import { existsSync as existsSync7, readdirSync as readdirSync4, readFileSync as readFileSync10, statSync as statSync3, unlinkSync } from "node:fs";
|
|
28164
28298
|
import { createConnection as createConnection3 } from "node:net";
|
|
28165
28299
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
28166
28300
|
import { join as join10, resolve as resolve4 } from "node:path";
|
|
@@ -28241,6 +28375,17 @@ function runtimeDirectories(variables = process.env) {
|
|
|
28241
28375
|
}
|
|
28242
28376
|
return [...new Set(candidates.map((path) => resolve4(path)))];
|
|
28243
28377
|
}
|
|
28378
|
+
var ERROR_REPORT_PATTERN = /^runtime-(\d+)\.log$/;
|
|
28379
|
+
function errorReportOf(pid, directories = runtimeDirectories()) {
|
|
28380
|
+
for (const directory of directories) {
|
|
28381
|
+
const path = join10(directory, `runtime-${pid}.log`);
|
|
28382
|
+
if (existsSync7(path)) {
|
|
28383
|
+
return path;
|
|
28384
|
+
}
|
|
28385
|
+
}
|
|
28386
|
+
return null;
|
|
28387
|
+
}
|
|
28388
|
+
var ERROR_REPORT_KEEP_MS = 60 * 60 * 1000;
|
|
28244
28389
|
function processAlive(pid) {
|
|
28245
28390
|
try {
|
|
28246
28391
|
process.kill(pid, 0);
|
|
@@ -28332,6 +28477,11 @@ function announcedIn(directory) {
|
|
|
28332
28477
|
}
|
|
28333
28478
|
const found = [];
|
|
28334
28479
|
for (const entry of readdirSync4(directory)) {
|
|
28480
|
+
const report = ERROR_REPORT_PATTERN.exec(entry);
|
|
28481
|
+
if (report) {
|
|
28482
|
+
sweepErrorReport(join10(directory, entry), Number.parseInt(report[1] ?? "", 10));
|
|
28483
|
+
continue;
|
|
28484
|
+
}
|
|
28335
28485
|
const match = ANNOUNCEMENT_PATTERN.exec(entry);
|
|
28336
28486
|
if (!match) {
|
|
28337
28487
|
continue;
|
|
@@ -28353,6 +28503,16 @@ function announcedIn(directory) {
|
|
|
28353
28503
|
}
|
|
28354
28504
|
return found;
|
|
28355
28505
|
}
|
|
28506
|
+
function sweepErrorReport(path, pid, now = Date.now()) {
|
|
28507
|
+
if (processAlive(pid)) {
|
|
28508
|
+
return;
|
|
28509
|
+
}
|
|
28510
|
+
try {
|
|
28511
|
+
if (now - statSync3(path).mtimeMs >= ERROR_REPORT_KEEP_MS) {
|
|
28512
|
+
unlinkSync(path);
|
|
28513
|
+
}
|
|
28514
|
+
} catch {}
|
|
28515
|
+
}
|
|
28356
28516
|
var LOOK_EVERY_MS = 50;
|
|
28357
28517
|
var ANNOUNCE_BUDGET_MS = 5000;
|
|
28358
28518
|
async function announcedSince(projectPath, before, waiting = {}) {
|
|
@@ -28745,7 +28905,7 @@ function judgeRun(record, running, needed) {
|
|
|
28745
28905
|
if (running.startedAt !== undefined && running.startedAt - record.startedAt > SAME_RUN_WINDOW_MS) {
|
|
28746
28906
|
return false;
|
|
28747
28907
|
}
|
|
28748
|
-
const engine = record.command === undefined ? null :
|
|
28908
|
+
const engine = record.command === undefined ? null : basename2(record.command);
|
|
28749
28909
|
const said = process.platform === "win32" ? running.text.toLowerCase() : running.text;
|
|
28750
28910
|
const wanted = engine === null || process.platform !== "win32" ? engine : engine.toLowerCase();
|
|
28751
28911
|
if (wanted !== null && !said.includes(wanted)) {
|
|
@@ -28776,7 +28936,7 @@ function clearRunRecord(ours = () => true) {
|
|
|
28776
28936
|
}
|
|
28777
28937
|
function ageMs(path, now) {
|
|
28778
28938
|
try {
|
|
28779
|
-
return now -
|
|
28939
|
+
return now - statSync4(path).mtimeMs;
|
|
28780
28940
|
} catch {
|
|
28781
28941
|
return 0;
|
|
28782
28942
|
}
|
|
@@ -28848,7 +29008,7 @@ var RUNNING_PROJECT_PATH = {
|
|
|
28848
29008
|
};
|
|
28849
29009
|
var RUNNING_PID = {
|
|
28850
29010
|
type: "number",
|
|
28851
|
-
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.
|
|
29011
|
+
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. Without it the game this server started or plays is the one asked when it is among them, and the answer says so under answeredBy; with one game per project it is not needed at all."
|
|
28852
29012
|
};
|
|
28853
29013
|
var SCENE_PATH = {
|
|
28854
29014
|
type: "string",
|
|
@@ -29466,6 +29626,11 @@ var TOOL_SPECS = [
|
|
|
29466
29626
|
type: "number",
|
|
29467
29627
|
ops: ["check", "wait"],
|
|
29468
29628
|
description: "check: how long to give the boot before it is called hung. Default 60000. wait: how long to wait for the run to end before answering anyway. Default 600000."
|
|
29629
|
+
},
|
|
29630
|
+
andChildren: {
|
|
29631
|
+
type: "boolean",
|
|
29632
|
+
ops: ["stop"],
|
|
29633
|
+
description: "stop: also end what the game started for itself, with OS.create_process or otherwise, such as a bench's workers and whatever those started in turn. Only the processes under the run's, to any depth, that are games of this project are ended: announced as one, or this project's engine run with --path on this project by its command line, which is how a bench whose workers deliberately carry no runtime is reached. They are named under endedChildren, with what identified each under identifiedBy; any other process under the run is left and named under childrenLeft, and childrenUnknown is true when nothing could be listed, with the note saying whether the platform would not or the run has no process to list under. Default false, and the run's process is ended either way."
|
|
29469
29634
|
}
|
|
29470
29635
|
},
|
|
29471
29636
|
requires: [],
|
|
@@ -29475,7 +29640,7 @@ var TOOL_SPECS = [
|
|
|
29475
29640
|
requires: ["projectPath"]
|
|
29476
29641
|
},
|
|
29477
29642
|
stop: {
|
|
29478
|
-
summary: "end the run and answer with what it printed last, naming the process ended under endedPid: a game that started processes of its own keeps them",
|
|
29643
|
+
summary: "end the run and answer with what it printed last, naming the process ended under endedPid: a game that started processes of its own keeps them unless andChildren says to end those too",
|
|
29479
29644
|
requires: []
|
|
29480
29645
|
},
|
|
29481
29646
|
check: {
|
|
@@ -29488,7 +29653,7 @@ var TOOL_SPECS = [
|
|
|
29488
29653
|
},
|
|
29489
29654
|
{
|
|
29490
29655
|
name: "editor_output",
|
|
29491
|
-
description: "What the project started by editor_run has printed, as entries with a severity: the errors and warnings the engine reported, each with where it happened, and everything else as info. Answers with the counts and the verdict as well as the entries. heldAt is three answers: null for a run this server knows is not held, running or over, an object for one it knows is held at a breakpoint, whether the adapter said so or the runtime did by accepting a connection and never answering, and absent with heldUnknown beside it for a server that connected to the debugger after the stop and has no runtime to ask, since the adapter reports a stop only to the sessions connected when it happened. A run that has quit still answers here, with running false and its exit code, until the next one starts. A run this server started also answers with transcript, the file both its streams are written to: uncapped, written while the run is going, and the thing to read or tail for a long run rather than the engine's own log, which every engine start rotates away. Read the file for output and this answer for state: endedBy says whether gdharness ended the run, and a transcript that has stopped growing is a run between prints rather than a run that is over. running is asked of the operating system whenever there is a process to ask about, which is every run this server started and every editor-played run whose game announced its runtime. For an editor-played run that never announced, there is no process id here and running is the editor's answer, which can lag: one measured elsewhere reported a game as playing for fifteen seconds after its process had been ended from outside. editor_status names that game under runtimes, which does not depend on the editor answering.",
|
|
29656
|
+
description: "What the project started by editor_run has printed, as entries with a severity: the errors and warnings the engine reported, each with where it happened, and everything else as info. For a run the editor plays the reports come from the game itself, through the runtime addon, since the editor's debugger relays what a game prints and not what it reports; a played game without the runtime addon answers here with its prints alone. Answers with the counts and the verdict as well as the entries. heldAt is three answers: null for a run this server knows is not held, running or over, an object for one it knows is held at a breakpoint, whether the adapter said so or the runtime did by accepting a connection and never answering, and absent with heldUnknown beside it for a server that connected to the debugger after the stop and has no runtime to ask, since the adapter reports a stop only to the sessions connected when it happened. A run that has quit still answers here, with running false and its exit code, until the next one starts. A run this server started also answers with transcript, the file both its streams are written to: uncapped, written while the run is going, and the thing to read or tail for a long run rather than the engine's own log, which every engine start rotates away. Read the file for output and this answer for state: endedBy says whether gdharness ended the run, and a transcript that has stopped growing is a run between prints rather than a run that is over. running is asked of the operating system whenever there is a process to ask about, which is every run this server started and every editor-played run whose game announced its runtime. For an editor-played run that never announced, there is no process id here and running is the editor's answer, which can lag: one measured elsewhere reported a game as playing for fifteen seconds after its process had been ended from outside. editor_status names that game under runtimes, which does not depend on the editor answering.",
|
|
29492
29657
|
parameters: {
|
|
29493
29658
|
severity: {
|
|
29494
29659
|
type: "string",
|
|
@@ -29937,7 +30102,7 @@ function buildToolDefinitions() {
|
|
|
29937
30102
|
// src/server.ts
|
|
29938
30103
|
var UPDATE_NOTICE_EVERY = 500;
|
|
29939
30104
|
var FEEDBACK_NOTICE_EVERY = 250;
|
|
29940
|
-
var
|
|
30105
|
+
var run5 = promisify5(execFile5);
|
|
29941
30106
|
var __dirname2 = dirname6(fileURLToPath2(import.meta.url));
|
|
29942
30107
|
var EDITOR_RESTART_TIMEOUT_MS = 90000;
|
|
29943
30108
|
var HOLD_PING_MS = 2000;
|
|
@@ -29992,6 +30157,46 @@ function endedPreviousRun(ended) {
|
|
|
29992
30157
|
}
|
|
29993
30158
|
return ended.pid ?? true;
|
|
29994
30159
|
}
|
|
30160
|
+
var CONSOLE_WRAPPER = /_console(\.exe)?$/i;
|
|
30161
|
+
function engineNamesOf(executable) {
|
|
30162
|
+
return CONSOLE_WRAPPER.test(executable) ? [executable, executable.replace(CONSOLE_WRAPPER, "$1")] : [executable];
|
|
30163
|
+
}
|
|
30164
|
+
function gameBetween(tree, pid, root, games) {
|
|
30165
|
+
return ancestorsIn(tree, pid, root).some((one) => one !== root && games.has(one));
|
|
30166
|
+
}
|
|
30167
|
+
function endChildrenAmong(started) {
|
|
30168
|
+
if (started.unknown !== false) {
|
|
30169
|
+
return { ended: [], left: [], unknown: started.unknown };
|
|
30170
|
+
}
|
|
30171
|
+
for (const game of started.games) {
|
|
30172
|
+
try {
|
|
30173
|
+
process.kill(game.pid);
|
|
30174
|
+
} catch {}
|
|
30175
|
+
}
|
|
30176
|
+
return { ended: started.games, left: [...started.others], unknown: false };
|
|
30177
|
+
}
|
|
30178
|
+
function aboutTheChildren(ended, throughEditor) {
|
|
30179
|
+
const reaches = "andChildren ends what it started that is a game of this project: announced as one, or this project's engine run with --path on it.";
|
|
30180
|
+
if (ended === null) {
|
|
30181
|
+
return throughEditor ? `Anything that game started for itself, with OS.create_process or otherwise, is not the editor's to stop and was not signalled; ${reaches}` : `Anything that game started for itself, with OS.create_process or otherwise, is a separate process this stop did not signal; ${reaches}`;
|
|
30182
|
+
}
|
|
30183
|
+
if (ended.unknown === "platform") {
|
|
30184
|
+
return "andChildren was asked for and this platform would not list what the game had started, so nothing else was ended.";
|
|
30185
|
+
}
|
|
30186
|
+
if (ended.unknown === "no process") {
|
|
30187
|
+
return "andChildren was asked for and this run has no process id to list under: the editor plays it and its game announced none, so nothing else was ended.";
|
|
30188
|
+
}
|
|
30189
|
+
const counted = (n) => `${n} ${n === 1 ? "process" : "processes"}`;
|
|
30190
|
+
const announced = ended.ended.filter((one) => one.how === "announced").length;
|
|
30191
|
+
const byCommandLine = ended.ended.length - announced;
|
|
30192
|
+
const how = [
|
|
30193
|
+
announced === 0 ? "" : `${announced} announced as ${announced === 1 ? "a game" : "games"} of this project`,
|
|
30194
|
+
byCommandLine === 0 ? "" : `${byCommandLine} this project's engine run with --path on it, by ${byCommandLine === 1 ? "its" : "their"} command line`
|
|
30195
|
+
].filter((part) => part !== "").join(" and ");
|
|
30196
|
+
const endedToo = ended.ended.length === 0 ? "The game had started nothing that is a game of this project, announced as one or this project's engine run with --path on it, so nothing else was ended." : `${counted(ended.ended.length)} the game had started for itself ${ended.ended.length === 1 ? "was" : "were"} ended with it, named under endedChildren with what identified ${ended.ended.length === 1 ? "it" : "each"} under identifiedBy: ${how}.`;
|
|
30197
|
+
const leftToo = ended.left.length === 0 ? "" : ` ${counted(ended.left.length)} it had started ${ended.left.length === 1 ? "was" : "were"} left, named under childrenLeft: neither announced as a game of this project nor this project's engine run with --path on it, so what ${ended.left.length === 1 ? "it is" : "they are"} cannot be told from here.`;
|
|
30198
|
+
return `${endedToo}${leftToo}`;
|
|
30199
|
+
}
|
|
29995
30200
|
function endedToStartThis(ended) {
|
|
29996
30201
|
if (ended === null) {
|
|
29997
30202
|
return "";
|
|
@@ -30733,7 +30938,7 @@ class GodotServer {
|
|
|
30733
30938
|
return op === "restart" ? await this.handleRestartEditor() : await this.handleLaunchEditor(args);
|
|
30734
30939
|
case "editor_run":
|
|
30735
30940
|
if (op === "stop") {
|
|
30736
|
-
return await this.handleStopProject();
|
|
30941
|
+
return await this.handleStopProject(args);
|
|
30737
30942
|
}
|
|
30738
30943
|
if (op === "wait") {
|
|
30739
30944
|
return await this.handleWaitForRun(args);
|
|
@@ -31097,7 +31302,7 @@ class GodotServer {
|
|
|
31097
31302
|
const name = application["config/name"];
|
|
31098
31303
|
const mainScene = application["run/main_scene"];
|
|
31099
31304
|
const info = {
|
|
31100
|
-
name: typeof name === "string" && name !== "" ? name :
|
|
31305
|
+
name: typeof name === "string" && name !== "" ? name : basename3(project.value.path),
|
|
31101
31306
|
path: project.value.path,
|
|
31102
31307
|
mainScene: typeof mainScene === "string" && mainScene !== "" ? mainScene : null,
|
|
31103
31308
|
godotVersion: await this.godotVersion(engine.value),
|
|
@@ -31176,7 +31381,7 @@ class GodotServer {
|
|
|
31176
31381
|
const log = new GameLog;
|
|
31177
31382
|
let exitCode = 0;
|
|
31178
31383
|
try {
|
|
31179
|
-
const { stdout, stderr } = await
|
|
31384
|
+
const { stdout, stderr } = await run5(engine.value, exportArgs, { timeout: 300000 });
|
|
31180
31385
|
log.append("stdout", stdout);
|
|
31181
31386
|
log.append("stderr", stderr);
|
|
31182
31387
|
} catch (error) {
|
|
@@ -31373,7 +31578,7 @@ class GodotServer {
|
|
|
31373
31578
|
}
|
|
31374
31579
|
async godotVersion(godotPath) {
|
|
31375
31580
|
try {
|
|
31376
|
-
const { stdout } = await
|
|
31581
|
+
const { stdout } = await run5(godotPath, ["--version"], { timeout: 1e4 });
|
|
31377
31582
|
this.lastGodotVersion = stdout.trim();
|
|
31378
31583
|
return this.lastGodotVersion;
|
|
31379
31584
|
} catch (error) {
|
|
@@ -31909,6 +32114,7 @@ class GodotServer {
|
|
|
31909
32114
|
});
|
|
31910
32115
|
this.logDebug(`Running Godot project: ${engine.value} ${cmdArgs.join(" ")}`);
|
|
31911
32116
|
const started = this.spawnKeptGame(engine.value, cmdArgs, project.value.path);
|
|
32117
|
+
started.announcedBefore = alreadyPlaying;
|
|
31912
32118
|
this.activeProcess = started;
|
|
31913
32119
|
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." : "";
|
|
31914
32120
|
const endedForThis = endedToStartThis(ended);
|
|
@@ -31958,7 +32164,7 @@ class GodotServer {
|
|
|
31958
32164
|
}
|
|
31959
32165
|
});
|
|
31960
32166
|
const going = this.currentRun();
|
|
31961
|
-
if (endpoint !== null && going
|
|
32167
|
+
if (endpoint !== null && going !== null && going.announcedPid === undefined) {
|
|
31962
32168
|
going.announcedPid = endpoint.pid;
|
|
31963
32169
|
}
|
|
31964
32170
|
return runtimeVerdict(endpoint, {
|
|
@@ -32029,7 +32235,11 @@ class GodotServer {
|
|
|
32029
32235
|
});
|
|
32030
32236
|
}
|
|
32031
32237
|
drainEditorOutput(game) {
|
|
32032
|
-
if (!game.throughEditor
|
|
32238
|
+
if (!game.throughEditor) {
|
|
32239
|
+
return;
|
|
32240
|
+
}
|
|
32241
|
+
this.drainErrorReport(game);
|
|
32242
|
+
if (!this.dapClient) {
|
|
32033
32243
|
return;
|
|
32034
32244
|
}
|
|
32035
32245
|
if (game.transcript === null) {
|
|
@@ -32044,9 +32254,44 @@ class GodotServer {
|
|
|
32044
32254
|
}
|
|
32045
32255
|
this.recordWhatItBrokeOn(game);
|
|
32046
32256
|
}
|
|
32257
|
+
drainErrorReport(game) {
|
|
32258
|
+
if (game.errorReport === undefined) {
|
|
32259
|
+
const its = this.announcedPidOf(game);
|
|
32260
|
+
const found = its === undefined ? null : errorReportOf(its);
|
|
32261
|
+
if (found === null) {
|
|
32262
|
+
return;
|
|
32263
|
+
}
|
|
32264
|
+
game.errorReport = found;
|
|
32265
|
+
game.errorReportOffset = 0;
|
|
32266
|
+
}
|
|
32267
|
+
let handle;
|
|
32268
|
+
try {
|
|
32269
|
+
handle = openSync2(game.errorReport, "r");
|
|
32270
|
+
} catch {
|
|
32271
|
+
return;
|
|
32272
|
+
}
|
|
32273
|
+
try {
|
|
32274
|
+
const from = game.errorReportOffset ?? 0;
|
|
32275
|
+
const size = fstatSync(handle).size;
|
|
32276
|
+
if (size <= from) {
|
|
32277
|
+
return;
|
|
32278
|
+
}
|
|
32279
|
+
const buffer = Buffer.alloc(size - from);
|
|
32280
|
+
const read = readSync(handle, buffer, 0, buffer.length, from);
|
|
32281
|
+
game.errorReportOffset = from + read;
|
|
32282
|
+
const reported = buffer.subarray(0, read);
|
|
32283
|
+
if (game.transcript !== null) {
|
|
32284
|
+
appendFileSync(game.transcript, reported);
|
|
32285
|
+
} else {
|
|
32286
|
+
game.log.append("stderr", reported);
|
|
32287
|
+
}
|
|
32288
|
+
} finally {
|
|
32289
|
+
closeSync(handle);
|
|
32290
|
+
}
|
|
32291
|
+
}
|
|
32047
32292
|
recordWhatItBrokeOn(game) {
|
|
32048
32293
|
const halt = this.dapClient?.whereItStopped() ?? null;
|
|
32049
|
-
if (halt?.reason !== "exception" || halt.text === "" || game.brokeOn === halt.text) {
|
|
32294
|
+
if (halt?.reason !== "exception" || halt.text === "" || game.brokeOn === halt.text || game.errorReport !== undefined) {
|
|
32050
32295
|
return;
|
|
32051
32296
|
}
|
|
32052
32297
|
game.brokeOn = halt.text;
|
|
@@ -32244,18 +32489,93 @@ class GodotServer {
|
|
|
32244
32489
|
this.activeProcess = picked;
|
|
32245
32490
|
}
|
|
32246
32491
|
announcedPidOf(run, announced) {
|
|
32247
|
-
if (
|
|
32492
|
+
if (run.announcedPid !== undefined) {
|
|
32493
|
+
return run.announcedPid;
|
|
32494
|
+
}
|
|
32495
|
+
const ours = this.announcedOfTheRunsProject(run, announced ?? runtimesAnnounced().running);
|
|
32496
|
+
if (run.pid !== null && ours.some((one) => one.pid === run.pid)) {
|
|
32497
|
+
run.announcedPid = run.pid;
|
|
32248
32498
|
return run.announcedPid;
|
|
32249
32499
|
}
|
|
32250
32500
|
const before = run.announcedBefore ?? new Set;
|
|
32251
|
-
const
|
|
32252
|
-
const candidates = fresh.filter((one) => this.playedByOurEditor(one, run.projectPath));
|
|
32501
|
+
const candidates = ours.filter((one) => !before.has(one.pid) && this.isTheGameOf(run, one, run.projectPath));
|
|
32253
32502
|
const theOne = candidates.length === 1 ? candidates[0] : undefined;
|
|
32254
32503
|
if (theOne !== undefined) {
|
|
32255
32504
|
run.announcedPid = theOne.pid;
|
|
32256
32505
|
}
|
|
32257
32506
|
return run.announcedPid;
|
|
32258
32507
|
}
|
|
32508
|
+
ownGameAmong(running, projectPath) {
|
|
32509
|
+
const run = this.currentRun();
|
|
32510
|
+
if (run === null || !stillRunning(run)) {
|
|
32511
|
+
return;
|
|
32512
|
+
}
|
|
32513
|
+
if (projectPath !== undefined && run.projectPath !== null && !isSameDirectory(run.projectPath, projectPath)) {
|
|
32514
|
+
return;
|
|
32515
|
+
}
|
|
32516
|
+
const its = this.announcedPidOf(run, running);
|
|
32517
|
+
return its !== undefined && running.some((one) => one.pid === its) ? its : undefined;
|
|
32518
|
+
}
|
|
32519
|
+
async ownGameThroughTheTree(running, projectPath) {
|
|
32520
|
+
const run = this.currentRun();
|
|
32521
|
+
if (run === null || !stillRunning(run) || run.announcedPid !== undefined) {
|
|
32522
|
+
return;
|
|
32523
|
+
}
|
|
32524
|
+
if (projectPath !== undefined && run.projectPath !== null && !isSameDirectory(run.projectPath, projectPath)) {
|
|
32525
|
+
return;
|
|
32526
|
+
}
|
|
32527
|
+
const its = await this.tiedThroughTheTree(run, running);
|
|
32528
|
+
return its !== undefined && running.some((one) => one.pid === its) ? its : undefined;
|
|
32529
|
+
}
|
|
32530
|
+
async tiedThroughTheTree(run, announced) {
|
|
32531
|
+
if (run.announcedPid !== undefined) {
|
|
32532
|
+
return run.announcedPid;
|
|
32533
|
+
}
|
|
32534
|
+
const ours = this.announcedOfTheRunsProject(run, announced);
|
|
32535
|
+
if (ours.length === 0) {
|
|
32536
|
+
return;
|
|
32537
|
+
}
|
|
32538
|
+
const tree = await processTree();
|
|
32539
|
+
if (tree === undefined) {
|
|
32540
|
+
return;
|
|
32541
|
+
}
|
|
32542
|
+
return this.tieThroughTheTree(run, tree, new Set(ours.map((one) => one.pid)));
|
|
32543
|
+
}
|
|
32544
|
+
async gameProcessOf(run) {
|
|
32545
|
+
if (run.gamePid !== undefined) {
|
|
32546
|
+
return run.gamePid;
|
|
32547
|
+
}
|
|
32548
|
+
const announced = runtimesAnnounced().running;
|
|
32549
|
+
const tied = await this.tiedThroughTheTree(run, announced);
|
|
32550
|
+
if (tied !== undefined) {
|
|
32551
|
+
return tied;
|
|
32552
|
+
}
|
|
32553
|
+
const tree = await processTree();
|
|
32554
|
+
if (tree === undefined) {
|
|
32555
|
+
return;
|
|
32556
|
+
}
|
|
32557
|
+
const found = this.gameByCommandLineIn(run, tree);
|
|
32558
|
+
if (found !== undefined) {
|
|
32559
|
+
run.gamePid = found;
|
|
32560
|
+
}
|
|
32561
|
+
return found;
|
|
32562
|
+
}
|
|
32563
|
+
tieThroughTheTree(run, tree, games) {
|
|
32564
|
+
const root = run.pid ?? (run.throughEditor ? this.godotBridge.getStatus().editorPid : undefined);
|
|
32565
|
+
if (root === undefined) {
|
|
32566
|
+
return;
|
|
32567
|
+
}
|
|
32568
|
+
if (games.has(root)) {
|
|
32569
|
+
run.announcedPid = root;
|
|
32570
|
+
return root;
|
|
32571
|
+
}
|
|
32572
|
+
const nearest = descendantsIn(tree, root).filter((one) => games.has(one) && !gameBetween(tree, one, root, games));
|
|
32573
|
+
const theOne = nearest.length === 1 ? nearest[0] : undefined;
|
|
32574
|
+
if (theOne !== undefined) {
|
|
32575
|
+
run.announcedPid = theOne;
|
|
32576
|
+
}
|
|
32577
|
+
return theOne;
|
|
32578
|
+
}
|
|
32259
32579
|
playedByOurEditor(endpoint, projectPath) {
|
|
32260
32580
|
const editor = this.godotBridge.getStatus().editorPid;
|
|
32261
32581
|
if (editor === undefined) {
|
|
@@ -32332,6 +32652,13 @@ class GodotServer {
|
|
|
32332
32652
|
announcedForOurProject(announced) {
|
|
32333
32653
|
return this.allAnnouncedForOurProject(announced)[0] ?? null;
|
|
32334
32654
|
}
|
|
32655
|
+
announcedOfTheRunsProject(run, announced) {
|
|
32656
|
+
const project = run.projectPath;
|
|
32657
|
+
if (project === null) {
|
|
32658
|
+
return this.allAnnouncedForOurProject(announced);
|
|
32659
|
+
}
|
|
32660
|
+
return announced.filter((one) => one.project.path !== "" && isSameDirectory(project, one.project.path));
|
|
32661
|
+
}
|
|
32335
32662
|
allAnnouncedForOurProject(announced) {
|
|
32336
32663
|
const status = this.godotBridge.getStatus();
|
|
32337
32664
|
const mine = this.ownProject ?? (status.connected ? status.projectPath ?? null : null);
|
|
@@ -32446,7 +32773,8 @@ class GodotServer {
|
|
|
32446
32773
|
});
|
|
32447
32774
|
const hold = await this.holdOf(run, going);
|
|
32448
32775
|
const wanted = readBoolean(args, "cpu") ?? false;
|
|
32449
|
-
const
|
|
32776
|
+
const announced = this.announcedPidOf(run);
|
|
32777
|
+
const processToAsk = wanted ? announced ?? (stillRunning(run) ? await this.gameProcessOf(run) : undefined) ?? run.pid ?? null : null;
|
|
32450
32778
|
const cpuSeconds = wanted && processToAsk !== null && stillRunning(run) ? await cpuSecondsOf(processToAsk) : undefined;
|
|
32451
32779
|
const notes = [];
|
|
32452
32780
|
if (wanted && cpuSeconds === undefined && stillRunning(run)) {
|
|
@@ -32477,7 +32805,7 @@ class GodotServer {
|
|
|
32477
32805
|
running: going,
|
|
32478
32806
|
exitCode: run.exitCode,
|
|
32479
32807
|
through: run.throughEditor ? "editor" : "gdharness",
|
|
32480
|
-
pid: run.pid ??
|
|
32808
|
+
pid: run.pid ?? announced ?? run.announcedPid ?? null,
|
|
32481
32809
|
errors: run.log.count("error"),
|
|
32482
32810
|
warnings: run.log.count("warning"),
|
|
32483
32811
|
clean: run.consoleLost === true ? undefined : run.log.count("error") === 0,
|
|
@@ -32495,7 +32823,7 @@ class GodotServer {
|
|
|
32495
32823
|
entries: forAnswer(selected.entries)
|
|
32496
32824
|
});
|
|
32497
32825
|
}
|
|
32498
|
-
async handleStopProject() {
|
|
32826
|
+
async handleStopProject(args) {
|
|
32499
32827
|
await this.pickUpWhatTheEditorIsPlaying();
|
|
32500
32828
|
const stopped = this.currentRun();
|
|
32501
32829
|
if (!stopped) {
|
|
@@ -32504,21 +32832,98 @@ class GodotServer {
|
|
|
32504
32832
|
this.drainEditorOutput(stopped);
|
|
32505
32833
|
this.drainTranscript(stopped);
|
|
32506
32834
|
const wasRunning = await this.runStillGoing(stopped);
|
|
32835
|
+
const endedPid = stopped.pid ?? this.announcedPidOf(stopped) ?? null;
|
|
32836
|
+
const children = readBoolean(args, "andChildren") === true && wasRunning ? await this.whatTheRunStarted(stopped) : null;
|
|
32507
32837
|
this.logDebug("Stopping the running game");
|
|
32508
32838
|
await this.endActiveGame("editor_run stop");
|
|
32839
|
+
const ended = children === null ? null : endChildrenAmong(children);
|
|
32509
32840
|
return this.jsonTextResponse({
|
|
32510
32841
|
stopped: true,
|
|
32511
32842
|
through: stopped.throughEditor ? "editor" : "gdharness",
|
|
32512
|
-
endedPid
|
|
32843
|
+
endedPid,
|
|
32844
|
+
...ended === null ? {} : {
|
|
32845
|
+
endedChildren: ended.ended.map((one) => one.pid),
|
|
32846
|
+
identifiedBy: ended.ended.length === 0 ? undefined : Object.fromEntries(ended.ended.map((one) => [String(one.pid), one.identifiedBy])),
|
|
32847
|
+
childrenLeft: ended.left.length === 0 ? undefined : ended.left,
|
|
32848
|
+
childrenUnknown: ended.unknown === false ? undefined : true
|
|
32849
|
+
},
|
|
32513
32850
|
exitedBeforeStop: stopped.exitCode !== null || !wasRunning,
|
|
32514
32851
|
exitCode: stopped.exitCode,
|
|
32515
32852
|
errors: stopped.log.count("error"),
|
|
32516
32853
|
warnings: stopped.log.count("warning"),
|
|
32517
32854
|
clean: stopped.log.count("error") === 0,
|
|
32518
|
-
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.
|
|
32855
|
+
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." : "The process named under endedPid was ended."} ${aboutTheChildren(ended, stopped.throughEditor)}`,
|
|
32519
32856
|
entries: forAnswer(stopped.log.select({ severity: "warning", sinceLastCall: false, limit: 200 }).entries)
|
|
32520
32857
|
});
|
|
32521
32858
|
}
|
|
32859
|
+
async whatTheRunStarted(run) {
|
|
32860
|
+
const tree = await processTree();
|
|
32861
|
+
if (tree === undefined) {
|
|
32862
|
+
return { unknown: "platform" };
|
|
32863
|
+
}
|
|
32864
|
+
const announced = this.announcedOfTheRunsProject(run, runtimesAnnounced().running);
|
|
32865
|
+
const games = new Set(announced.map((one) => one.pid));
|
|
32866
|
+
const own = this.announcedPidOf(run, announced) ?? this.tieThroughTheTree(run, tree, games) ?? run.gamePid ?? this.gameByCommandLineIn(run, tree);
|
|
32867
|
+
if (own !== undefined && run.announcedPid === undefined) {
|
|
32868
|
+
run.gamePid = own;
|
|
32869
|
+
}
|
|
32870
|
+
const roots = [run.pid, own].filter((one) => one !== null && one !== undefined);
|
|
32871
|
+
if (roots.length === 0) {
|
|
32872
|
+
return { unknown: "no process" };
|
|
32873
|
+
}
|
|
32874
|
+
const under = new Set(roots.flatMap((root) => descendantsIn(tree, root)));
|
|
32875
|
+
for (const root of roots) {
|
|
32876
|
+
under.delete(root);
|
|
32877
|
+
}
|
|
32878
|
+
const started = [...under].sort((a, b) => a - b);
|
|
32879
|
+
const found = [];
|
|
32880
|
+
const others = [];
|
|
32881
|
+
for (const pid of started) {
|
|
32882
|
+
if (games.has(pid)) {
|
|
32883
|
+
found.push({ pid, how: "announced", identifiedBy: "announced as a game of this project" });
|
|
32884
|
+
continue;
|
|
32885
|
+
}
|
|
32886
|
+
const engines = [...roots, ...roots.flatMap((root) => ancestorsIn(tree, pid, root))];
|
|
32887
|
+
const read = this.theProjectsEngine(run, tree, pid, engines);
|
|
32888
|
+
if (read !== null) {
|
|
32889
|
+
found.push({
|
|
32890
|
+
pid,
|
|
32891
|
+
how: "command line",
|
|
32892
|
+
identifiedBy: `${read.executable} run with --path ${read.projectPath ?? ""}, this project's engine on this project`
|
|
32893
|
+
});
|
|
32894
|
+
continue;
|
|
32895
|
+
}
|
|
32896
|
+
others.push(pid);
|
|
32897
|
+
}
|
|
32898
|
+
return { unknown: false, games: found, others };
|
|
32899
|
+
}
|
|
32900
|
+
theProjectsEngine(run, tree, pid, engines) {
|
|
32901
|
+
const project = run.projectPath;
|
|
32902
|
+
if (project === null) {
|
|
32903
|
+
return null;
|
|
32904
|
+
}
|
|
32905
|
+
const read = readCommandLine(tree.get(pid)?.command ?? "");
|
|
32906
|
+
if (read.executable === "" || read.editor || read.projectPath === null || !isSameDirectory(read.projectPath, project)) {
|
|
32907
|
+
return null;
|
|
32908
|
+
}
|
|
32909
|
+
const names = engines.flatMap((one) => engineNamesOf(readCommandLine(tree.get(one)?.command ?? "").executable));
|
|
32910
|
+
const sameName = (one, other) => one !== "" && (process.platform === "win32" ? one.toLowerCase() === other.toLowerCase() : one === other);
|
|
32911
|
+
return names.some((name) => sameName(name, read.executable)) ? read : null;
|
|
32912
|
+
}
|
|
32913
|
+
gameByCommandLineIn(run, tree) {
|
|
32914
|
+
const root = run.pid ?? (run.throughEditor ? this.godotBridge.getStatus().editorPid : undefined);
|
|
32915
|
+
if (root === undefined || run.projectPath === null) {
|
|
32916
|
+
return;
|
|
32917
|
+
}
|
|
32918
|
+
const rootRead = readCommandLine(tree.get(root)?.command ?? "");
|
|
32919
|
+
const rootIsTheGame = !rootRead.editor && !CONSOLE_WRAPPER.test(rootRead.executable) && rootRead.projectPath !== null && isSameDirectory(rootRead.projectPath, run.projectPath);
|
|
32920
|
+
if (rootIsTheGame) {
|
|
32921
|
+
return;
|
|
32922
|
+
}
|
|
32923
|
+
const isEngine = (pid) => this.theProjectsEngine(run, tree, pid, [root]) !== null;
|
|
32924
|
+
const nearest = descendantsIn(tree, root).filter((pid) => isEngine(pid) && !ancestorsIn(tree, pid, root).some((one) => one !== root && isEngine(one)));
|
|
32925
|
+
return nearest.length === 1 ? nearest[0] : undefined;
|
|
32926
|
+
}
|
|
32522
32927
|
async handleRescanFilesystem(args) {
|
|
32523
32928
|
const timeoutMs = readPositiveNumber(args, "timeoutMs") ?? 30000;
|
|
32524
32929
|
const started = Date.now();
|
|
@@ -32713,10 +33118,15 @@ class GodotServer {
|
|
|
32713
33118
|
async handleRuntimeCommand(command, args, timeoutMs = this.runtimeTimeoutMs()) {
|
|
32714
33119
|
const { op: _op, projectPath, pid, ...params } = asParams(args);
|
|
32715
33120
|
const announced = runtimesAnnounced();
|
|
32716
|
-
const
|
|
33121
|
+
const wanted = typeof projectPath === "string" ? projectPath : this.ownProject ?? undefined;
|
|
33122
|
+
const own = typeof pid === "number" ? undefined : this.ownGameAmong(announced.running, wanted) ?? await this.ownGameThroughTheTree(announced.running, wanted);
|
|
33123
|
+
const choice = chooseRuntime(announced.running, wanted, announced.unspoken, typeof pid === "number" ? pid : own);
|
|
32717
33124
|
if ("problem" in choice) {
|
|
32718
33125
|
return this.createErrorResponse(choice.problem);
|
|
32719
33126
|
}
|
|
33127
|
+
const ofProject = announced.running.filter((one) => wanted === undefined || isSameDirectory(one.project.path, wanted));
|
|
33128
|
+
const chosenAmongSeveral = own !== undefined && ofProject.length > 1;
|
|
33129
|
+
const answeredBy = chosenAmongSeveral ? { answeredBy: own } : {};
|
|
32720
33130
|
const held = this.knownHoldOn(choice.endpoint, announced.running);
|
|
32721
33131
|
if (held !== null) {
|
|
32722
33132
|
return this.createErrorResponse(`The game (pid ${choice.endpoint.pid}) is held by the editor's debugger, ${describeHalt(held)}, and answers no runtime call while it is.`, [
|
|
@@ -32735,7 +33145,7 @@ class GodotServer {
|
|
|
32735
33145
|
}
|
|
32736
33146
|
const { id: _id, ...payload } = reply.payload;
|
|
32737
33147
|
if (!expectsScreenshot) {
|
|
32738
|
-
return this.jsonTextResponse(payload);
|
|
33148
|
+
return this.jsonTextResponse({ ...payload, ...answeredBy });
|
|
32739
33149
|
}
|
|
32740
33150
|
const returnedPath = readString(payload, "path");
|
|
32741
33151
|
if (readString(payload, "type") !== "screenshot_file" || !returnedPath || !screenshotPath) {
|
|
@@ -32747,7 +33157,10 @@ class GodotServer {
|
|
|
32747
33157
|
const dimensions = `${readNumber(payload, "width") ?? 0}x${readNumber(payload, "height") ?? 0} ${readString(payload, "format") ?? "unknown"}`;
|
|
32748
33158
|
return {
|
|
32749
33159
|
content: [
|
|
32750
|
-
{
|
|
33160
|
+
{
|
|
33161
|
+
type: "text",
|
|
33162
|
+
text: `Screenshot captured: ${dimensions}${chosenAmongSeveral ? ` from pid ${own}, the game this server holds` : ""}`
|
|
33163
|
+
},
|
|
32751
33164
|
{ type: "image", data: readFileSync13(screenshotPath).toString("base64"), mimeType: "image/png" }
|
|
32752
33165
|
]
|
|
32753
33166
|
};
|