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/cli.js
CHANGED
|
@@ -7283,7 +7283,7 @@ class GameLog {
|
|
|
7283
7283
|
}
|
|
7284
7284
|
var HEADLINE, COLOUR_CODE, STREAMS, ADMITTED;
|
|
7285
7285
|
var init_game_log = __esm(() => {
|
|
7286
|
-
HEADLINE = /^(USER )?(SCRIPT ERROR|ERROR|WARNING):\s?(.*)$/;
|
|
7286
|
+
HEADLINE = /^(USER )?(SCRIPT ERROR|SHADER ERROR|ERROR|WARNING):\s?(.*)$/;
|
|
7287
7287
|
COLOUR_CODE = new RegExp(`${String.fromCharCode(27)}\\[[0-9;]*m`, "g");
|
|
7288
7288
|
STREAMS = ["stdout", "stderr", "transcript"];
|
|
7289
7289
|
ADMITTED = {
|
|
@@ -15393,7 +15393,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
15393
15393
|
};
|
|
15394
15394
|
RUNNING_PID = {
|
|
15395
15395
|
type: "number",
|
|
15396
|
-
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.
|
|
15396
|
+
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."
|
|
15397
15397
|
};
|
|
15398
15398
|
SCENE_PATH = {
|
|
15399
15399
|
type: "string",
|
|
@@ -16011,6 +16011,11 @@ var init_tool_definitions = __esm(() => {
|
|
|
16011
16011
|
type: "number",
|
|
16012
16012
|
ops: ["check", "wait"],
|
|
16013
16013
|
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."
|
|
16014
|
+
},
|
|
16015
|
+
andChildren: {
|
|
16016
|
+
type: "boolean",
|
|
16017
|
+
ops: ["stop"],
|
|
16018
|
+
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."
|
|
16014
16019
|
}
|
|
16015
16020
|
},
|
|
16016
16021
|
requires: [],
|
|
@@ -16020,7 +16025,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
16020
16025
|
requires: ["projectPath"]
|
|
16021
16026
|
},
|
|
16022
16027
|
stop: {
|
|
16023
|
-
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",
|
|
16028
|
+
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",
|
|
16024
16029
|
requires: []
|
|
16025
16030
|
},
|
|
16026
16031
|
check: {
|
|
@@ -16033,7 +16038,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
16033
16038
|
},
|
|
16034
16039
|
{
|
|
16035
16040
|
name: "editor_output",
|
|
16036
|
-
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.",
|
|
16041
|
+
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.",
|
|
16037
16042
|
parameters: {
|
|
16038
16043
|
severity: {
|
|
16039
16044
|
type: "string",
|
|
@@ -36413,23 +36418,159 @@ var init_lsp_client = __esm(() => {
|
|
|
36413
36418
|
init_ports();
|
|
36414
36419
|
});
|
|
36415
36420
|
|
|
36416
|
-
// src/process-
|
|
36421
|
+
// src/process-children.ts
|
|
36417
36422
|
import { execFile as execFile5 } from "node:child_process";
|
|
36423
|
+
import { basename as basename2 } from "node:path";
|
|
36418
36424
|
import { promisify as promisify5 } from "node:util";
|
|
36425
|
+
async function processTree() {
|
|
36426
|
+
try {
|
|
36427
|
+
const printed = process.platform === "win32" ? await askWindows() : await askPosix();
|
|
36428
|
+
return parseProcessTable(printed);
|
|
36429
|
+
} catch {
|
|
36430
|
+
return;
|
|
36431
|
+
}
|
|
36432
|
+
}
|
|
36433
|
+
function parseProcessTable(printed) {
|
|
36434
|
+
const tree = new Map;
|
|
36435
|
+
for (const line of printed.split(/\r?\n/)) {
|
|
36436
|
+
const fields = /^\s*(\d+)\s+(\d+)(?:\s+(.*))?$/.exec(line);
|
|
36437
|
+
if (fields === null) {
|
|
36438
|
+
continue;
|
|
36439
|
+
}
|
|
36440
|
+
const pid = Number(fields[1]);
|
|
36441
|
+
const parent = Number(fields[2]);
|
|
36442
|
+
if (pid > 0) {
|
|
36443
|
+
tree.set(pid, { parent, command: (fields[3] ?? "").trim() });
|
|
36444
|
+
}
|
|
36445
|
+
}
|
|
36446
|
+
return tree;
|
|
36447
|
+
}
|
|
36448
|
+
function childrenIn(tree, pid) {
|
|
36449
|
+
const children = [];
|
|
36450
|
+
for (const [child, listed] of tree) {
|
|
36451
|
+
if (listed.parent === pid) {
|
|
36452
|
+
children.push(child);
|
|
36453
|
+
}
|
|
36454
|
+
}
|
|
36455
|
+
return children;
|
|
36456
|
+
}
|
|
36457
|
+
function descendantsIn(tree, pid) {
|
|
36458
|
+
const found = [];
|
|
36459
|
+
const queue = [pid];
|
|
36460
|
+
const seen = new Set([pid]);
|
|
36461
|
+
while (queue.length > 0) {
|
|
36462
|
+
const next = queue.shift();
|
|
36463
|
+
if (next === undefined) {
|
|
36464
|
+
break;
|
|
36465
|
+
}
|
|
36466
|
+
for (const child of childrenIn(tree, next)) {
|
|
36467
|
+
if (!seen.has(child)) {
|
|
36468
|
+
seen.add(child);
|
|
36469
|
+
found.push(child);
|
|
36470
|
+
queue.push(child);
|
|
36471
|
+
}
|
|
36472
|
+
}
|
|
36473
|
+
}
|
|
36474
|
+
return found;
|
|
36475
|
+
}
|
|
36476
|
+
function ancestorsIn(tree, pid, root) {
|
|
36477
|
+
const chain = [];
|
|
36478
|
+
const seen = new Set([pid]);
|
|
36479
|
+
let parent = tree.get(pid)?.parent;
|
|
36480
|
+
while (parent !== undefined && !seen.has(parent)) {
|
|
36481
|
+
chain.push(parent);
|
|
36482
|
+
if (parent === root) {
|
|
36483
|
+
break;
|
|
36484
|
+
}
|
|
36485
|
+
seen.add(parent);
|
|
36486
|
+
parent = tree.get(parent)?.parent;
|
|
36487
|
+
}
|
|
36488
|
+
return chain;
|
|
36489
|
+
}
|
|
36490
|
+
function readCommandLine(command) {
|
|
36491
|
+
const words = commandWords(command);
|
|
36492
|
+
const executable = words[0] === undefined ? "" : basename2(words[0].replaceAll("\\", "/"));
|
|
36493
|
+
const at = words.indexOf("--path");
|
|
36494
|
+
let projectPath = null;
|
|
36495
|
+
if (at !== -1 && at + 1 < words.length) {
|
|
36496
|
+
const taken = [];
|
|
36497
|
+
for (const word of words.slice(at + 1)) {
|
|
36498
|
+
if (taken.length > 0 && (word.startsWith("-") || /^[a-z]+:\/\//.test(word))) {
|
|
36499
|
+
break;
|
|
36500
|
+
}
|
|
36501
|
+
taken.push(word);
|
|
36502
|
+
}
|
|
36503
|
+
projectPath = taken.join(" ");
|
|
36504
|
+
}
|
|
36505
|
+
return {
|
|
36506
|
+
executable,
|
|
36507
|
+
projectPath,
|
|
36508
|
+
editor: words.some((word) => word === "-e" || word === "--editor")
|
|
36509
|
+
};
|
|
36510
|
+
}
|
|
36511
|
+
function commandWords(command) {
|
|
36512
|
+
const words = [];
|
|
36513
|
+
let word = "";
|
|
36514
|
+
let quoted = false;
|
|
36515
|
+
let started = false;
|
|
36516
|
+
for (const char of command) {
|
|
36517
|
+
if (char === '"') {
|
|
36518
|
+
quoted = !quoted;
|
|
36519
|
+
started = true;
|
|
36520
|
+
} else if (!quoted && /\s/.test(char)) {
|
|
36521
|
+
if (started) {
|
|
36522
|
+
words.push(word);
|
|
36523
|
+
word = "";
|
|
36524
|
+
started = false;
|
|
36525
|
+
}
|
|
36526
|
+
} else {
|
|
36527
|
+
word += char;
|
|
36528
|
+
started = true;
|
|
36529
|
+
}
|
|
36530
|
+
}
|
|
36531
|
+
if (started) {
|
|
36532
|
+
words.push(word);
|
|
36533
|
+
}
|
|
36534
|
+
return words;
|
|
36535
|
+
}
|
|
36536
|
+
async function askWindows() {
|
|
36537
|
+
const { stdout } = await run5("powershell.exe", [
|
|
36538
|
+
"-NoProfile",
|
|
36539
|
+
"-NonInteractive",
|
|
36540
|
+
"-Command",
|
|
36541
|
+
'Get-CimInstance Win32_Process | ForEach-Object { "$($_.ProcessId) $($_.ParentProcessId) $($_.CommandLine)" }'
|
|
36542
|
+
], { timeout: ASK_TIMEOUT_MS, windowsHide: true, maxBuffer: 16 * 1024 * 1024 });
|
|
36543
|
+
return stdout;
|
|
36544
|
+
}
|
|
36545
|
+
async function askPosix() {
|
|
36546
|
+
const { stdout } = await run5("ps", ["-e", "-o", "pid=,ppid=,args="], {
|
|
36547
|
+
timeout: ASK_TIMEOUT_MS,
|
|
36548
|
+
maxBuffer: 16 * 1024 * 1024
|
|
36549
|
+
});
|
|
36550
|
+
return stdout;
|
|
36551
|
+
}
|
|
36552
|
+
var run5, ASK_TIMEOUT_MS = 15000;
|
|
36553
|
+
var init_process_children = __esm(() => {
|
|
36554
|
+
run5 = promisify5(execFile5);
|
|
36555
|
+
});
|
|
36556
|
+
|
|
36557
|
+
// src/process-time.ts
|
|
36558
|
+
import { execFile as execFile6 } from "node:child_process";
|
|
36559
|
+
import { promisify as promisify6 } from "node:util";
|
|
36419
36560
|
async function cpuSecondsOf(pid) {
|
|
36420
36561
|
try {
|
|
36421
|
-
return process.platform === "win32" ? await
|
|
36562
|
+
return process.platform === "win32" ? await askWindows2(pid) : await askPosix2(pid);
|
|
36422
36563
|
} catch {
|
|
36423
36564
|
return;
|
|
36424
36565
|
}
|
|
36425
36566
|
}
|
|
36426
|
-
async function
|
|
36427
|
-
const { stdout } = await
|
|
36567
|
+
async function askWindows2(pid) {
|
|
36568
|
+
const { stdout } = await run6("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", `(Get-Process -Id ${pid}).CPU`], { timeout: ASK_TIMEOUT_MS2, windowsHide: true });
|
|
36428
36569
|
const seconds = Number(stdout.trim().replace(",", "."));
|
|
36429
36570
|
return Number.isFinite(seconds) ? seconds : undefined;
|
|
36430
36571
|
}
|
|
36431
|
-
async function
|
|
36432
|
-
const { stdout } = await
|
|
36572
|
+
async function askPosix2(pid) {
|
|
36573
|
+
const { stdout } = await run6("ps", ["-o", "time=", "-p", String(pid)], { timeout: ASK_TIMEOUT_MS2 });
|
|
36433
36574
|
return secondsFromClock(stdout.trim());
|
|
36434
36575
|
}
|
|
36435
36576
|
function secondsFromClock(printed) {
|
|
@@ -36447,9 +36588,9 @@ function secondsFromClock(printed) {
|
|
|
36447
36588
|
const extra = days === null ? 0 : Number(days) * 86400;
|
|
36448
36589
|
return Number.isFinite(extra) ? seconds + extra : undefined;
|
|
36449
36590
|
}
|
|
36450
|
-
var
|
|
36591
|
+
var run6, ASK_TIMEOUT_MS2 = 6000;
|
|
36451
36592
|
var init_process_time = __esm(() => {
|
|
36452
|
-
|
|
36593
|
+
run6 = promisify6(execFile6);
|
|
36453
36594
|
});
|
|
36454
36595
|
|
|
36455
36596
|
// src/project-scan.ts
|
|
@@ -36626,7 +36767,7 @@ var init_restart_note = __esm(() => {
|
|
|
36626
36767
|
});
|
|
36627
36768
|
|
|
36628
36769
|
// src/runtime-client.ts
|
|
36629
|
-
import { existsSync as existsSync13, readdirSync as readdirSync5, readFileSync as readFileSync15, unlinkSync } from "node:fs";
|
|
36770
|
+
import { existsSync as existsSync13, readdirSync as readdirSync5, readFileSync as readFileSync15, statSync as statSync3, unlinkSync } from "node:fs";
|
|
36630
36771
|
import { createConnection as createConnection3 } from "node:net";
|
|
36631
36772
|
import { tmpdir as tmpdir4 } from "node:os";
|
|
36632
36773
|
import { join as join17, resolve as resolve4 } from "node:path";
|
|
@@ -36647,6 +36788,15 @@ function runtimeDirectories(variables = process.env) {
|
|
|
36647
36788
|
}
|
|
36648
36789
|
return [...new Set(candidates.map((path) => resolve4(path)))];
|
|
36649
36790
|
}
|
|
36791
|
+
function errorReportOf(pid, directories = runtimeDirectories()) {
|
|
36792
|
+
for (const directory of directories) {
|
|
36793
|
+
const path = join17(directory, `runtime-${pid}.log`);
|
|
36794
|
+
if (existsSync13(path)) {
|
|
36795
|
+
return path;
|
|
36796
|
+
}
|
|
36797
|
+
}
|
|
36798
|
+
return null;
|
|
36799
|
+
}
|
|
36650
36800
|
function processAlive(pid) {
|
|
36651
36801
|
try {
|
|
36652
36802
|
process.kill(pid, 0);
|
|
@@ -36735,6 +36885,11 @@ function announcedIn(directory) {
|
|
|
36735
36885
|
}
|
|
36736
36886
|
const found = [];
|
|
36737
36887
|
for (const entry of readdirSync5(directory)) {
|
|
36888
|
+
const report = ERROR_REPORT_PATTERN.exec(entry);
|
|
36889
|
+
if (report) {
|
|
36890
|
+
sweepErrorReport(join17(directory, entry), Number.parseInt(report[1] ?? "", 10));
|
|
36891
|
+
continue;
|
|
36892
|
+
}
|
|
36738
36893
|
const match = ANNOUNCEMENT_PATTERN.exec(entry);
|
|
36739
36894
|
if (!match) {
|
|
36740
36895
|
continue;
|
|
@@ -36756,6 +36911,16 @@ function announcedIn(directory) {
|
|
|
36756
36911
|
}
|
|
36757
36912
|
return found;
|
|
36758
36913
|
}
|
|
36914
|
+
function sweepErrorReport(path, pid, now = Date.now()) {
|
|
36915
|
+
if (processAlive(pid)) {
|
|
36916
|
+
return;
|
|
36917
|
+
}
|
|
36918
|
+
try {
|
|
36919
|
+
if (now - statSync3(path).mtimeMs >= ERROR_REPORT_KEEP_MS) {
|
|
36920
|
+
unlinkSync(path);
|
|
36921
|
+
}
|
|
36922
|
+
} catch {}
|
|
36923
|
+
}
|
|
36759
36924
|
async function announcedSince(projectPath, before, waiting = {}) {
|
|
36760
36925
|
const wanted = resolve4(projectPath);
|
|
36761
36926
|
const directories = waiting.directories ?? runtimeDirectories();
|
|
@@ -36926,16 +37091,18 @@ function runtimeRequest(endpoint, command, params, timeoutMs) {
|
|
|
36926
37091
|
});
|
|
36927
37092
|
});
|
|
36928
37093
|
}
|
|
36929
|
-
var RUNTIME_PROTOCOL = 2, ANNOUNCEMENT_PATTERN, GONE_WINDOW_MS = 120000, GONE_KEPT = 32, gone, LOOK_EVERY_MS = 50, ANNOUNCE_BUDGET_MS = 5000, nextRequestId = 1;
|
|
37094
|
+
var RUNTIME_PROTOCOL = 2, ANNOUNCEMENT_PATTERN, ERROR_REPORT_PATTERN, ERROR_REPORT_KEEP_MS, GONE_WINDOW_MS = 120000, GONE_KEPT = 32, gone, LOOK_EVERY_MS = 50, ANNOUNCE_BUDGET_MS = 5000, nextRequestId = 1;
|
|
36930
37095
|
var init_runtime_client = __esm(() => {
|
|
36931
37096
|
ANNOUNCEMENT_PATTERN = /^runtime-(\d+)\.json$/;
|
|
37097
|
+
ERROR_REPORT_PATTERN = /^runtime-(\d+)\.log$/;
|
|
37098
|
+
ERROR_REPORT_KEEP_MS = 60 * 60 * 1000;
|
|
36932
37099
|
gone = [];
|
|
36933
37100
|
});
|
|
36934
37101
|
|
|
36935
37102
|
// src/run-record.ts
|
|
36936
37103
|
import { execFileSync } from "node:child_process";
|
|
36937
|
-
import { mkdirSync as mkdirSync9, openSync, readdirSync as readdirSync6, readFileSync as readFileSync16, rmSync as rmSync9, statSync as
|
|
36938
|
-
import { basename as
|
|
37104
|
+
import { mkdirSync as mkdirSync9, openSync, readdirSync as readdirSync6, readFileSync as readFileSync16, rmSync as rmSync9, statSync as statSync4, writeFileSync as writeFileSync11 } from "node:fs";
|
|
37105
|
+
import { basename as basename3, join as join18 } from "node:path";
|
|
36939
37106
|
function runsDirectory() {
|
|
36940
37107
|
return join18(runtimeDirectory(), "runs");
|
|
36941
37108
|
}
|
|
@@ -37151,7 +37318,7 @@ function judgeRun(record, running, needed) {
|
|
|
37151
37318
|
if (running.startedAt !== undefined && running.startedAt - record.startedAt > SAME_RUN_WINDOW_MS) {
|
|
37152
37319
|
return false;
|
|
37153
37320
|
}
|
|
37154
|
-
const engine = record.command === undefined ? null :
|
|
37321
|
+
const engine = record.command === undefined ? null : basename3(record.command);
|
|
37155
37322
|
const said = process.platform === "win32" ? running.text.toLowerCase() : running.text;
|
|
37156
37323
|
const wanted = engine === null || process.platform !== "win32" ? engine : engine.toLowerCase();
|
|
37157
37324
|
if (wanted !== null && !said.includes(wanted)) {
|
|
@@ -37182,7 +37349,7 @@ function clearRunRecord(ours = () => true) {
|
|
|
37182
37349
|
}
|
|
37183
37350
|
function ageMs(path, now) {
|
|
37184
37351
|
try {
|
|
37185
|
-
return now -
|
|
37352
|
+
return now - statSync4(path).mtimeMs;
|
|
37186
37353
|
} catch {
|
|
37187
37354
|
return 0;
|
|
37188
37355
|
}
|
|
@@ -37216,7 +37383,7 @@ var init_run_record = __esm(() => {
|
|
|
37216
37383
|
});
|
|
37217
37384
|
|
|
37218
37385
|
// src/server.ts
|
|
37219
|
-
import { execFile as
|
|
37386
|
+
import { execFile as execFile7, spawn } from "node:child_process";
|
|
37220
37387
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
37221
37388
|
import {
|
|
37222
37389
|
appendFileSync,
|
|
@@ -37231,10 +37398,10 @@ import {
|
|
|
37231
37398
|
readSync
|
|
37232
37399
|
} from "node:fs";
|
|
37233
37400
|
import { tmpdir as tmpdir5 } from "node:os";
|
|
37234
|
-
import { basename as
|
|
37401
|
+
import { basename as basename4, dirname as dirname11, join as join19, normalize as normalize3, relative as relative3 } from "node:path";
|
|
37235
37402
|
import { setTimeout as delay3 } from "node:timers/promises";
|
|
37236
37403
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
37237
|
-
import { promisify as
|
|
37404
|
+
import { promisify as promisify7 } from "node:util";
|
|
37238
37405
|
function patienceForFrames(frames, atLeast) {
|
|
37239
37406
|
return Math.max(atLeast, frames / SLOWEST_FRAME_RATE * 1000 + atLeast);
|
|
37240
37407
|
}
|
|
@@ -37285,6 +37452,45 @@ function endedPreviousRun(ended) {
|
|
|
37285
37452
|
}
|
|
37286
37453
|
return ended.pid ?? true;
|
|
37287
37454
|
}
|
|
37455
|
+
function engineNamesOf(executable) {
|
|
37456
|
+
return CONSOLE_WRAPPER.test(executable) ? [executable, executable.replace(CONSOLE_WRAPPER, "$1")] : [executable];
|
|
37457
|
+
}
|
|
37458
|
+
function gameBetween(tree, pid, root, games) {
|
|
37459
|
+
return ancestorsIn(tree, pid, root).some((one) => one !== root && games.has(one));
|
|
37460
|
+
}
|
|
37461
|
+
function endChildrenAmong(started) {
|
|
37462
|
+
if (started.unknown !== false) {
|
|
37463
|
+
return { ended: [], left: [], unknown: started.unknown };
|
|
37464
|
+
}
|
|
37465
|
+
for (const game of started.games) {
|
|
37466
|
+
try {
|
|
37467
|
+
process.kill(game.pid);
|
|
37468
|
+
} catch {}
|
|
37469
|
+
}
|
|
37470
|
+
return { ended: started.games, left: [...started.others], unknown: false };
|
|
37471
|
+
}
|
|
37472
|
+
function aboutTheChildren(ended, throughEditor) {
|
|
37473
|
+
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.";
|
|
37474
|
+
if (ended === null) {
|
|
37475
|
+
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}`;
|
|
37476
|
+
}
|
|
37477
|
+
if (ended.unknown === "platform") {
|
|
37478
|
+
return "andChildren was asked for and this platform would not list what the game had started, so nothing else was ended.";
|
|
37479
|
+
}
|
|
37480
|
+
if (ended.unknown === "no process") {
|
|
37481
|
+
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.";
|
|
37482
|
+
}
|
|
37483
|
+
const counted = (n) => `${n} ${n === 1 ? "process" : "processes"}`;
|
|
37484
|
+
const announced = ended.ended.filter((one) => one.how === "announced").length;
|
|
37485
|
+
const byCommandLine = ended.ended.length - announced;
|
|
37486
|
+
const how = [
|
|
37487
|
+
announced === 0 ? "" : `${announced} announced as ${announced === 1 ? "a game" : "games"} of this project`,
|
|
37488
|
+
byCommandLine === 0 ? "" : `${byCommandLine} this project's engine run with --path on it, by ${byCommandLine === 1 ? "its" : "their"} command line`
|
|
37489
|
+
].filter((part) => part !== "").join(" and ");
|
|
37490
|
+
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}.`;
|
|
37491
|
+
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.`;
|
|
37492
|
+
return `${endedToo}${leftToo}`;
|
|
37493
|
+
}
|
|
37288
37494
|
function endedToStartThis(ended) {
|
|
37289
37495
|
if (ended === null) {
|
|
37290
37496
|
return "";
|
|
@@ -37984,7 +38190,7 @@ class GodotServer {
|
|
|
37984
38190
|
return op === "restart" ? await this.handleRestartEditor() : await this.handleLaunchEditor(args);
|
|
37985
38191
|
case "editor_run":
|
|
37986
38192
|
if (op === "stop") {
|
|
37987
|
-
return await this.handleStopProject();
|
|
38193
|
+
return await this.handleStopProject(args);
|
|
37988
38194
|
}
|
|
37989
38195
|
if (op === "wait") {
|
|
37990
38196
|
return await this.handleWaitForRun(args);
|
|
@@ -38348,7 +38554,7 @@ class GodotServer {
|
|
|
38348
38554
|
const name = application["config/name"];
|
|
38349
38555
|
const mainScene = application["run/main_scene"];
|
|
38350
38556
|
const info = {
|
|
38351
|
-
name: typeof name === "string" && name !== "" ? name :
|
|
38557
|
+
name: typeof name === "string" && name !== "" ? name : basename4(project.value.path),
|
|
38352
38558
|
path: project.value.path,
|
|
38353
38559
|
mainScene: typeof mainScene === "string" && mainScene !== "" ? mainScene : null,
|
|
38354
38560
|
godotVersion: await this.godotVersion(engine.value),
|
|
@@ -38427,7 +38633,7 @@ class GodotServer {
|
|
|
38427
38633
|
const log = new GameLog;
|
|
38428
38634
|
let exitCode = 0;
|
|
38429
38635
|
try {
|
|
38430
|
-
const { stdout, stderr } = await
|
|
38636
|
+
const { stdout, stderr } = await run7(engine.value, exportArgs, { timeout: 300000 });
|
|
38431
38637
|
log.append("stdout", stdout);
|
|
38432
38638
|
log.append("stderr", stderr);
|
|
38433
38639
|
} catch (error) {
|
|
@@ -38624,7 +38830,7 @@ class GodotServer {
|
|
|
38624
38830
|
}
|
|
38625
38831
|
async godotVersion(godotPath) {
|
|
38626
38832
|
try {
|
|
38627
|
-
const { stdout } = await
|
|
38833
|
+
const { stdout } = await run7(godotPath, ["--version"], { timeout: 1e4 });
|
|
38628
38834
|
this.lastGodotVersion = stdout.trim();
|
|
38629
38835
|
return this.lastGodotVersion;
|
|
38630
38836
|
} catch (error) {
|
|
@@ -39160,6 +39366,7 @@ class GodotServer {
|
|
|
39160
39366
|
});
|
|
39161
39367
|
this.logDebug(`Running Godot project: ${engine.value} ${cmdArgs.join(" ")}`);
|
|
39162
39368
|
const started = this.spawnKeptGame(engine.value, cmdArgs, project.value.path);
|
|
39369
|
+
started.announcedBefore = alreadyPlaying;
|
|
39163
39370
|
this.activeProcess = started;
|
|
39164
39371
|
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." : "";
|
|
39165
39372
|
const endedForThis = endedToStartThis(ended);
|
|
@@ -39209,7 +39416,7 @@ class GodotServer {
|
|
|
39209
39416
|
}
|
|
39210
39417
|
});
|
|
39211
39418
|
const going = this.currentRun();
|
|
39212
|
-
if (endpoint !== null && going
|
|
39419
|
+
if (endpoint !== null && going !== null && going.announcedPid === undefined) {
|
|
39213
39420
|
going.announcedPid = endpoint.pid;
|
|
39214
39421
|
}
|
|
39215
39422
|
return runtimeVerdict(endpoint, {
|
|
@@ -39280,7 +39487,11 @@ class GodotServer {
|
|
|
39280
39487
|
});
|
|
39281
39488
|
}
|
|
39282
39489
|
drainEditorOutput(game) {
|
|
39283
|
-
if (!game.throughEditor
|
|
39490
|
+
if (!game.throughEditor) {
|
|
39491
|
+
return;
|
|
39492
|
+
}
|
|
39493
|
+
this.drainErrorReport(game);
|
|
39494
|
+
if (!this.dapClient) {
|
|
39284
39495
|
return;
|
|
39285
39496
|
}
|
|
39286
39497
|
if (game.transcript === null) {
|
|
@@ -39295,9 +39506,44 @@ class GodotServer {
|
|
|
39295
39506
|
}
|
|
39296
39507
|
this.recordWhatItBrokeOn(game);
|
|
39297
39508
|
}
|
|
39509
|
+
drainErrorReport(game) {
|
|
39510
|
+
if (game.errorReport === undefined) {
|
|
39511
|
+
const its = this.announcedPidOf(game);
|
|
39512
|
+
const found = its === undefined ? null : errorReportOf(its);
|
|
39513
|
+
if (found === null) {
|
|
39514
|
+
return;
|
|
39515
|
+
}
|
|
39516
|
+
game.errorReport = found;
|
|
39517
|
+
game.errorReportOffset = 0;
|
|
39518
|
+
}
|
|
39519
|
+
let handle;
|
|
39520
|
+
try {
|
|
39521
|
+
handle = openSync2(game.errorReport, "r");
|
|
39522
|
+
} catch {
|
|
39523
|
+
return;
|
|
39524
|
+
}
|
|
39525
|
+
try {
|
|
39526
|
+
const from = game.errorReportOffset ?? 0;
|
|
39527
|
+
const size = fstatSync(handle).size;
|
|
39528
|
+
if (size <= from) {
|
|
39529
|
+
return;
|
|
39530
|
+
}
|
|
39531
|
+
const buffer = Buffer.alloc(size - from);
|
|
39532
|
+
const read = readSync(handle, buffer, 0, buffer.length, from);
|
|
39533
|
+
game.errorReportOffset = from + read;
|
|
39534
|
+
const reported = buffer.subarray(0, read);
|
|
39535
|
+
if (game.transcript !== null) {
|
|
39536
|
+
appendFileSync(game.transcript, reported);
|
|
39537
|
+
} else {
|
|
39538
|
+
game.log.append("stderr", reported);
|
|
39539
|
+
}
|
|
39540
|
+
} finally {
|
|
39541
|
+
closeSync(handle);
|
|
39542
|
+
}
|
|
39543
|
+
}
|
|
39298
39544
|
recordWhatItBrokeOn(game) {
|
|
39299
39545
|
const halt = this.dapClient?.whereItStopped() ?? null;
|
|
39300
|
-
if (halt?.reason !== "exception" || halt.text === "" || game.brokeOn === halt.text) {
|
|
39546
|
+
if (halt?.reason !== "exception" || halt.text === "" || game.brokeOn === halt.text || game.errorReport !== undefined) {
|
|
39301
39547
|
return;
|
|
39302
39548
|
}
|
|
39303
39549
|
game.brokeOn = halt.text;
|
|
@@ -39495,18 +39741,93 @@ class GodotServer {
|
|
|
39495
39741
|
this.activeProcess = picked;
|
|
39496
39742
|
}
|
|
39497
39743
|
announcedPidOf(run, announced) {
|
|
39498
|
-
if (
|
|
39744
|
+
if (run.announcedPid !== undefined) {
|
|
39745
|
+
return run.announcedPid;
|
|
39746
|
+
}
|
|
39747
|
+
const ours = this.announcedOfTheRunsProject(run, announced ?? runtimesAnnounced().running);
|
|
39748
|
+
if (run.pid !== null && ours.some((one) => one.pid === run.pid)) {
|
|
39749
|
+
run.announcedPid = run.pid;
|
|
39499
39750
|
return run.announcedPid;
|
|
39500
39751
|
}
|
|
39501
39752
|
const before = run.announcedBefore ?? new Set;
|
|
39502
|
-
const
|
|
39503
|
-
const candidates = fresh.filter((one) => this.playedByOurEditor(one, run.projectPath));
|
|
39753
|
+
const candidates = ours.filter((one) => !before.has(one.pid) && this.isTheGameOf(run, one, run.projectPath));
|
|
39504
39754
|
const theOne = candidates.length === 1 ? candidates[0] : undefined;
|
|
39505
39755
|
if (theOne !== undefined) {
|
|
39506
39756
|
run.announcedPid = theOne.pid;
|
|
39507
39757
|
}
|
|
39508
39758
|
return run.announcedPid;
|
|
39509
39759
|
}
|
|
39760
|
+
ownGameAmong(running, projectPath) {
|
|
39761
|
+
const run = this.currentRun();
|
|
39762
|
+
if (run === null || !stillRunning(run)) {
|
|
39763
|
+
return;
|
|
39764
|
+
}
|
|
39765
|
+
if (projectPath !== undefined && run.projectPath !== null && !isSameDirectory(run.projectPath, projectPath)) {
|
|
39766
|
+
return;
|
|
39767
|
+
}
|
|
39768
|
+
const its = this.announcedPidOf(run, running);
|
|
39769
|
+
return its !== undefined && running.some((one) => one.pid === its) ? its : undefined;
|
|
39770
|
+
}
|
|
39771
|
+
async ownGameThroughTheTree(running, projectPath) {
|
|
39772
|
+
const run = this.currentRun();
|
|
39773
|
+
if (run === null || !stillRunning(run) || run.announcedPid !== undefined) {
|
|
39774
|
+
return;
|
|
39775
|
+
}
|
|
39776
|
+
if (projectPath !== undefined && run.projectPath !== null && !isSameDirectory(run.projectPath, projectPath)) {
|
|
39777
|
+
return;
|
|
39778
|
+
}
|
|
39779
|
+
const its = await this.tiedThroughTheTree(run, running);
|
|
39780
|
+
return its !== undefined && running.some((one) => one.pid === its) ? its : undefined;
|
|
39781
|
+
}
|
|
39782
|
+
async tiedThroughTheTree(run, announced) {
|
|
39783
|
+
if (run.announcedPid !== undefined) {
|
|
39784
|
+
return run.announcedPid;
|
|
39785
|
+
}
|
|
39786
|
+
const ours = this.announcedOfTheRunsProject(run, announced);
|
|
39787
|
+
if (ours.length === 0) {
|
|
39788
|
+
return;
|
|
39789
|
+
}
|
|
39790
|
+
const tree = await processTree();
|
|
39791
|
+
if (tree === undefined) {
|
|
39792
|
+
return;
|
|
39793
|
+
}
|
|
39794
|
+
return this.tieThroughTheTree(run, tree, new Set(ours.map((one) => one.pid)));
|
|
39795
|
+
}
|
|
39796
|
+
async gameProcessOf(run) {
|
|
39797
|
+
if (run.gamePid !== undefined) {
|
|
39798
|
+
return run.gamePid;
|
|
39799
|
+
}
|
|
39800
|
+
const announced = runtimesAnnounced().running;
|
|
39801
|
+
const tied = await this.tiedThroughTheTree(run, announced);
|
|
39802
|
+
if (tied !== undefined) {
|
|
39803
|
+
return tied;
|
|
39804
|
+
}
|
|
39805
|
+
const tree = await processTree();
|
|
39806
|
+
if (tree === undefined) {
|
|
39807
|
+
return;
|
|
39808
|
+
}
|
|
39809
|
+
const found = this.gameByCommandLineIn(run, tree);
|
|
39810
|
+
if (found !== undefined) {
|
|
39811
|
+
run.gamePid = found;
|
|
39812
|
+
}
|
|
39813
|
+
return found;
|
|
39814
|
+
}
|
|
39815
|
+
tieThroughTheTree(run, tree, games) {
|
|
39816
|
+
const root = run.pid ?? (run.throughEditor ? this.godotBridge.getStatus().editorPid : undefined);
|
|
39817
|
+
if (root === undefined) {
|
|
39818
|
+
return;
|
|
39819
|
+
}
|
|
39820
|
+
if (games.has(root)) {
|
|
39821
|
+
run.announcedPid = root;
|
|
39822
|
+
return root;
|
|
39823
|
+
}
|
|
39824
|
+
const nearest = descendantsIn(tree, root).filter((one) => games.has(one) && !gameBetween(tree, one, root, games));
|
|
39825
|
+
const theOne = nearest.length === 1 ? nearest[0] : undefined;
|
|
39826
|
+
if (theOne !== undefined) {
|
|
39827
|
+
run.announcedPid = theOne;
|
|
39828
|
+
}
|
|
39829
|
+
return theOne;
|
|
39830
|
+
}
|
|
39510
39831
|
playedByOurEditor(endpoint, projectPath) {
|
|
39511
39832
|
const editor = this.godotBridge.getStatus().editorPid;
|
|
39512
39833
|
if (editor === undefined) {
|
|
@@ -39583,6 +39904,13 @@ class GodotServer {
|
|
|
39583
39904
|
announcedForOurProject(announced) {
|
|
39584
39905
|
return this.allAnnouncedForOurProject(announced)[0] ?? null;
|
|
39585
39906
|
}
|
|
39907
|
+
announcedOfTheRunsProject(run, announced) {
|
|
39908
|
+
const project = run.projectPath;
|
|
39909
|
+
if (project === null) {
|
|
39910
|
+
return this.allAnnouncedForOurProject(announced);
|
|
39911
|
+
}
|
|
39912
|
+
return announced.filter((one) => one.project.path !== "" && isSameDirectory(project, one.project.path));
|
|
39913
|
+
}
|
|
39586
39914
|
allAnnouncedForOurProject(announced) {
|
|
39587
39915
|
const status = this.godotBridge.getStatus();
|
|
39588
39916
|
const mine = this.ownProject ?? (status.connected ? status.projectPath ?? null : null);
|
|
@@ -39697,7 +40025,8 @@ class GodotServer {
|
|
|
39697
40025
|
});
|
|
39698
40026
|
const hold = await this.holdOf(run, going);
|
|
39699
40027
|
const wanted = readBoolean(args, "cpu") ?? false;
|
|
39700
|
-
const
|
|
40028
|
+
const announced = this.announcedPidOf(run);
|
|
40029
|
+
const processToAsk = wanted ? announced ?? (stillRunning(run) ? await this.gameProcessOf(run) : undefined) ?? run.pid ?? null : null;
|
|
39701
40030
|
const cpuSeconds = wanted && processToAsk !== null && stillRunning(run) ? await cpuSecondsOf(processToAsk) : undefined;
|
|
39702
40031
|
const notes = [];
|
|
39703
40032
|
if (wanted && cpuSeconds === undefined && stillRunning(run)) {
|
|
@@ -39728,7 +40057,7 @@ class GodotServer {
|
|
|
39728
40057
|
running: going,
|
|
39729
40058
|
exitCode: run.exitCode,
|
|
39730
40059
|
through: run.throughEditor ? "editor" : "gdharness",
|
|
39731
|
-
pid: run.pid ??
|
|
40060
|
+
pid: run.pid ?? announced ?? run.announcedPid ?? null,
|
|
39732
40061
|
errors: run.log.count("error"),
|
|
39733
40062
|
warnings: run.log.count("warning"),
|
|
39734
40063
|
clean: run.consoleLost === true ? undefined : run.log.count("error") === 0,
|
|
@@ -39746,7 +40075,7 @@ class GodotServer {
|
|
|
39746
40075
|
entries: forAnswer(selected.entries)
|
|
39747
40076
|
});
|
|
39748
40077
|
}
|
|
39749
|
-
async handleStopProject() {
|
|
40078
|
+
async handleStopProject(args) {
|
|
39750
40079
|
await this.pickUpWhatTheEditorIsPlaying();
|
|
39751
40080
|
const stopped = this.currentRun();
|
|
39752
40081
|
if (!stopped) {
|
|
@@ -39755,21 +40084,98 @@ class GodotServer {
|
|
|
39755
40084
|
this.drainEditorOutput(stopped);
|
|
39756
40085
|
this.drainTranscript(stopped);
|
|
39757
40086
|
const wasRunning = await this.runStillGoing(stopped);
|
|
40087
|
+
const endedPid = stopped.pid ?? this.announcedPidOf(stopped) ?? null;
|
|
40088
|
+
const children = readBoolean(args, "andChildren") === true && wasRunning ? await this.whatTheRunStarted(stopped) : null;
|
|
39758
40089
|
this.logDebug("Stopping the running game");
|
|
39759
40090
|
await this.endActiveGame("editor_run stop");
|
|
40091
|
+
const ended = children === null ? null : endChildrenAmong(children);
|
|
39760
40092
|
return this.jsonTextResponse({
|
|
39761
40093
|
stopped: true,
|
|
39762
40094
|
through: stopped.throughEditor ? "editor" : "gdharness",
|
|
39763
|
-
endedPid
|
|
40095
|
+
endedPid,
|
|
40096
|
+
...ended === null ? {} : {
|
|
40097
|
+
endedChildren: ended.ended.map((one) => one.pid),
|
|
40098
|
+
identifiedBy: ended.ended.length === 0 ? undefined : Object.fromEntries(ended.ended.map((one) => [String(one.pid), one.identifiedBy])),
|
|
40099
|
+
childrenLeft: ended.left.length === 0 ? undefined : ended.left,
|
|
40100
|
+
childrenUnknown: ended.unknown === false ? undefined : true
|
|
40101
|
+
},
|
|
39764
40102
|
exitedBeforeStop: stopped.exitCode !== null || !wasRunning,
|
|
39765
40103
|
exitCode: stopped.exitCode,
|
|
39766
40104
|
errors: stopped.log.count("error"),
|
|
39767
40105
|
warnings: stopped.log.count("warning"),
|
|
39768
40106
|
clean: stopped.log.count("error") === 0,
|
|
39769
|
-
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.
|
|
40107
|
+
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)}`,
|
|
39770
40108
|
entries: forAnswer(stopped.log.select({ severity: "warning", sinceLastCall: false, limit: 200 }).entries)
|
|
39771
40109
|
});
|
|
39772
40110
|
}
|
|
40111
|
+
async whatTheRunStarted(run) {
|
|
40112
|
+
const tree = await processTree();
|
|
40113
|
+
if (tree === undefined) {
|
|
40114
|
+
return { unknown: "platform" };
|
|
40115
|
+
}
|
|
40116
|
+
const announced = this.announcedOfTheRunsProject(run, runtimesAnnounced().running);
|
|
40117
|
+
const games = new Set(announced.map((one) => one.pid));
|
|
40118
|
+
const own = this.announcedPidOf(run, announced) ?? this.tieThroughTheTree(run, tree, games) ?? run.gamePid ?? this.gameByCommandLineIn(run, tree);
|
|
40119
|
+
if (own !== undefined && run.announcedPid === undefined) {
|
|
40120
|
+
run.gamePid = own;
|
|
40121
|
+
}
|
|
40122
|
+
const roots = [run.pid, own].filter((one) => one !== null && one !== undefined);
|
|
40123
|
+
if (roots.length === 0) {
|
|
40124
|
+
return { unknown: "no process" };
|
|
40125
|
+
}
|
|
40126
|
+
const under = new Set(roots.flatMap((root) => descendantsIn(tree, root)));
|
|
40127
|
+
for (const root of roots) {
|
|
40128
|
+
under.delete(root);
|
|
40129
|
+
}
|
|
40130
|
+
const started = [...under].sort((a, b) => a - b);
|
|
40131
|
+
const found = [];
|
|
40132
|
+
const others = [];
|
|
40133
|
+
for (const pid of started) {
|
|
40134
|
+
if (games.has(pid)) {
|
|
40135
|
+
found.push({ pid, how: "announced", identifiedBy: "announced as a game of this project" });
|
|
40136
|
+
continue;
|
|
40137
|
+
}
|
|
40138
|
+
const engines = [...roots, ...roots.flatMap((root) => ancestorsIn(tree, pid, root))];
|
|
40139
|
+
const read = this.theProjectsEngine(run, tree, pid, engines);
|
|
40140
|
+
if (read !== null) {
|
|
40141
|
+
found.push({
|
|
40142
|
+
pid,
|
|
40143
|
+
how: "command line",
|
|
40144
|
+
identifiedBy: `${read.executable} run with --path ${read.projectPath ?? ""}, this project's engine on this project`
|
|
40145
|
+
});
|
|
40146
|
+
continue;
|
|
40147
|
+
}
|
|
40148
|
+
others.push(pid);
|
|
40149
|
+
}
|
|
40150
|
+
return { unknown: false, games: found, others };
|
|
40151
|
+
}
|
|
40152
|
+
theProjectsEngine(run, tree, pid, engines) {
|
|
40153
|
+
const project = run.projectPath;
|
|
40154
|
+
if (project === null) {
|
|
40155
|
+
return null;
|
|
40156
|
+
}
|
|
40157
|
+
const read = readCommandLine(tree.get(pid)?.command ?? "");
|
|
40158
|
+
if (read.executable === "" || read.editor || read.projectPath === null || !isSameDirectory(read.projectPath, project)) {
|
|
40159
|
+
return null;
|
|
40160
|
+
}
|
|
40161
|
+
const names = engines.flatMap((one) => engineNamesOf(readCommandLine(tree.get(one)?.command ?? "").executable));
|
|
40162
|
+
const sameName = (one, other) => one !== "" && (process.platform === "win32" ? one.toLowerCase() === other.toLowerCase() : one === other);
|
|
40163
|
+
return names.some((name) => sameName(name, read.executable)) ? read : null;
|
|
40164
|
+
}
|
|
40165
|
+
gameByCommandLineIn(run, tree) {
|
|
40166
|
+
const root = run.pid ?? (run.throughEditor ? this.godotBridge.getStatus().editorPid : undefined);
|
|
40167
|
+
if (root === undefined || run.projectPath === null) {
|
|
40168
|
+
return;
|
|
40169
|
+
}
|
|
40170
|
+
const rootRead = readCommandLine(tree.get(root)?.command ?? "");
|
|
40171
|
+
const rootIsTheGame = !rootRead.editor && !CONSOLE_WRAPPER.test(rootRead.executable) && rootRead.projectPath !== null && isSameDirectory(rootRead.projectPath, run.projectPath);
|
|
40172
|
+
if (rootIsTheGame) {
|
|
40173
|
+
return;
|
|
40174
|
+
}
|
|
40175
|
+
const isEngine = (pid) => this.theProjectsEngine(run, tree, pid, [root]) !== null;
|
|
40176
|
+
const nearest = descendantsIn(tree, root).filter((pid) => isEngine(pid) && !ancestorsIn(tree, pid, root).some((one) => one !== root && isEngine(one)));
|
|
40177
|
+
return nearest.length === 1 ? nearest[0] : undefined;
|
|
40178
|
+
}
|
|
39773
40179
|
async handleRescanFilesystem(args) {
|
|
39774
40180
|
const timeoutMs = readPositiveNumber(args, "timeoutMs") ?? 30000;
|
|
39775
40181
|
const started = Date.now();
|
|
@@ -39964,10 +40370,15 @@ class GodotServer {
|
|
|
39964
40370
|
async handleRuntimeCommand(command, args, timeoutMs = this.runtimeTimeoutMs()) {
|
|
39965
40371
|
const { op: _op, projectPath, pid, ...params } = asParams(args);
|
|
39966
40372
|
const announced = runtimesAnnounced();
|
|
39967
|
-
const
|
|
40373
|
+
const wanted = typeof projectPath === "string" ? projectPath : this.ownProject ?? undefined;
|
|
40374
|
+
const own = typeof pid === "number" ? undefined : this.ownGameAmong(announced.running, wanted) ?? await this.ownGameThroughTheTree(announced.running, wanted);
|
|
40375
|
+
const choice = chooseRuntime(announced.running, wanted, announced.unspoken, typeof pid === "number" ? pid : own);
|
|
39968
40376
|
if ("problem" in choice) {
|
|
39969
40377
|
return this.createErrorResponse(choice.problem);
|
|
39970
40378
|
}
|
|
40379
|
+
const ofProject = announced.running.filter((one) => wanted === undefined || isSameDirectory(one.project.path, wanted));
|
|
40380
|
+
const chosenAmongSeveral = own !== undefined && ofProject.length > 1;
|
|
40381
|
+
const answeredBy = chosenAmongSeveral ? { answeredBy: own } : {};
|
|
39971
40382
|
const held = this.knownHoldOn(choice.endpoint, announced.running);
|
|
39972
40383
|
if (held !== null) {
|
|
39973
40384
|
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.`, [
|
|
@@ -39986,7 +40397,7 @@ class GodotServer {
|
|
|
39986
40397
|
}
|
|
39987
40398
|
const { id: _id, ...payload } = reply.payload;
|
|
39988
40399
|
if (!expectsScreenshot) {
|
|
39989
|
-
return this.jsonTextResponse(payload);
|
|
40400
|
+
return this.jsonTextResponse({ ...payload, ...answeredBy });
|
|
39990
40401
|
}
|
|
39991
40402
|
const returnedPath = readString(payload, "path");
|
|
39992
40403
|
if (readString(payload, "type") !== "screenshot_file" || !returnedPath || !screenshotPath) {
|
|
@@ -39998,7 +40409,10 @@ class GodotServer {
|
|
|
39998
40409
|
const dimensions = `${readNumber(payload, "width") ?? 0}x${readNumber(payload, "height") ?? 0} ${readString(payload, "format") ?? "unknown"}`;
|
|
39999
40410
|
return {
|
|
40000
40411
|
content: [
|
|
40001
|
-
{
|
|
40412
|
+
{
|
|
40413
|
+
type: "text",
|
|
40414
|
+
text: `Screenshot captured: ${dimensions}${chosenAmongSeveral ? ` from pid ${own}, the game this server holds` : ""}`
|
|
40415
|
+
},
|
|
40002
40416
|
{ type: "image", data: readFileSync17(screenshotPath).toString("base64"), mimeType: "image/png" }
|
|
40003
40417
|
]
|
|
40004
40418
|
};
|
|
@@ -40065,7 +40479,7 @@ async function runGodotServer() {
|
|
|
40065
40479
|
const server = new GodotServer;
|
|
40066
40480
|
await server.run();
|
|
40067
40481
|
}
|
|
40068
|
-
var UPDATE_NOTICE_EVERY = 500, FEEDBACK_NOTICE_EVERY = 250,
|
|
40482
|
+
var UPDATE_NOTICE_EVERY = 500, FEEDBACK_NOTICE_EVERY = 250, run7, __dirname2, EDITOR_RESTART_TIMEOUT_MS = 90000, HOLD_PING_MS = 2000, SLOWEST_FRAME_RATE = 20, CONSOLE_WRAPPER, VALUE_NAMES, BRIDGE_RETRY_MS = 2000, SUCCESSOR_CHECK_MS = 1e4, CACHE_WRITE_MS = 2000, PLAYING_STATUS_MS = 1000, SCAN_START_MS = 2000, WAIT_FOR_RUN_MS = 600000, RUN_POLL_MS = 250, PATH_SOLUTIONS, PROJECT_FILE_ARGUMENTS, DEBUG_STATE_CALLS, PROJECT_INFO_SECTIONS2, RUNNER_DIRECTORY = "addons/gdUnit4/";
|
|
40069
40483
|
var init_server3 = __esm(() => {
|
|
40070
40484
|
init_mcp();
|
|
40071
40485
|
init_stdio2();
|
|
@@ -40086,6 +40500,7 @@ var init_server3 = __esm(() => {
|
|
|
40086
40500
|
init_lsp_client();
|
|
40087
40501
|
init_paths();
|
|
40088
40502
|
init_ports();
|
|
40503
|
+
init_process_children();
|
|
40089
40504
|
init_process_time();
|
|
40090
40505
|
init_project_scan();
|
|
40091
40506
|
init_resources();
|
|
@@ -40097,8 +40512,9 @@ var init_server3 = __esm(() => {
|
|
|
40097
40512
|
init_setup();
|
|
40098
40513
|
init_tool_definitions();
|
|
40099
40514
|
init_update_check();
|
|
40100
|
-
|
|
40515
|
+
run7 = promisify7(execFile7);
|
|
40101
40516
|
__dirname2 = dirname11(fileURLToPath4(import.meta.url));
|
|
40517
|
+
CONSOLE_WRAPPER = /_console(\.exe)?$/i;
|
|
40102
40518
|
VALUE_NAMES = {
|
|
40103
40519
|
string: "a string",
|
|
40104
40520
|
number: "a number",
|
|
@@ -41608,8 +42024,10 @@ that is running, and the project on disk. ${TOOL_SPECS.length} tools, named \`do
|
|
|
41608
42024
|
|
|
41609
42025
|
- ${projectPathSentence()}. The \`runtime_*\` and \`debug_*\` ones pick between running
|
|
41610
42026
|
games instead, by \`projectPath\`, or by \`pid\` when several run from one project, as a bench
|
|
41611
|
-
and its workers do
|
|
41612
|
-
|
|
42027
|
+
and its workers do: without \`pid\` the game this server started or plays is the one asked, and
|
|
42028
|
+
the answer says so under \`answeredBy\`; the others answer about the editor this server is
|
|
42029
|
+
connected to. There is no ambient project, and an argument a tool does not declare is refused
|
|
42030
|
+
rather than ignored.
|
|
41613
42031
|
- \`editor_status\` says whether an editor is connected and whether its addon matches the server.
|
|
41614
42032
|
\`addonIsStale\` means they differ, and \`staleNote\` says which half is behind: an editor that
|
|
41615
42033
|
needs restarting, or a server that needs reconnecting in your harness. Every answer that came
|
|
@@ -41621,7 +42039,9 @@ that is running, and the project on disk. ${TOOL_SPECS.length} tools, named \`do
|
|
|
41621
42039
|
false is a runtime that is not coming and true is a game still on its way up.
|
|
41622
42040
|
- Read \`editor_output\` after every run. It returns the engine's errors and warnings as entries
|
|
41623
42041
|
with their backtraces, and a \`clean\` verdict, so a run that printed an error is one call away
|
|
41624
|
-
from being known. For a
|
|
42042
|
+
from being known. For a run the editor plays, the errors come from the game through the runtime
|
|
42043
|
+
addon, so a played game without the addon is judged on its prints alone and \`clean\` there says
|
|
42044
|
+
less. For a long run it also names a transcript file: read that for output and this
|
|
41625
42045
|
answer for state. Never decide a run has ended by watching the file stop growing, or a process
|
|
41626
42046
|
list, or a timeout; a bench between prints looks exactly like a bench that died to everything else.
|
|
41627
42047
|
\`endedBy\` says whether gdharness ended it, and \`running\` is asked of the operating system
|
|
@@ -41654,6 +42074,7 @@ that is running, and the project on disk. ${TOOL_SPECS.length} tools, named \`do
|
|
|
41654
42074
|
| Where a 3D node is on screen | \`runtime_inspect\` \`rect\`, rather than unprojecting by hand |
|
|
41655
42075
|
| Wait for something in a running game | \`runtime_wait\`, never a sleep; \`until\` with \`says\` for a panel that rebuilds its own labels |
|
|
41656
42076
|
| Wait for a run to finish | \`editor_run wait\`, never a sleep or a shell loop on the pid: it answers as \`editor_output\` does, once the run is over |
|
|
42077
|
+
| End a bench and the workers it opened | \`editor_run stop\` with \`andChildren\`, never a process listing by scene name: it ends every process under the run's, to any depth, that is a game of the project, announced as one or the project's engine run with \`--path\` on it, names them under \`endedChildren\` with what identified each, and leaves and names any other |
|
|
41657
42078
|
| A picture, for a person who asked to see one | \`runtime_capture\` |
|
|
41658
42079
|
|
|
41659
42080
|
These need the runtime autoload, which \`gdharness setup\` registers. \`runtime_capture\` needs a
|