gdharness 1.0.30 → 1.0.31
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 +321 -85
- package/build/index.js +322 -87
- package/build/keeper.js +74 -24
- package/package.json +1 -1
package/build/cli.js
CHANGED
|
@@ -204,7 +204,9 @@ function ancestorsIn(tree, pid, root) {
|
|
|
204
204
|
return chain;
|
|
205
205
|
}
|
|
206
206
|
function readCommandLine(command) {
|
|
207
|
-
const
|
|
207
|
+
const all = commandWords(command);
|
|
208
|
+
const separator = all.findIndex((word) => word === "--" || word === "++");
|
|
209
|
+
const words = separator === -1 ? all : all.slice(0, separator);
|
|
208
210
|
const executable = words[0] === undefined ? "" : basename(words[0].replaceAll("\\", "/"));
|
|
209
211
|
const at = words.indexOf("--path");
|
|
210
212
|
let projectPath = null;
|
|
@@ -216,7 +218,7 @@ function readCommandLine(command) {
|
|
|
216
218
|
}
|
|
217
219
|
taken.push(word);
|
|
218
220
|
}
|
|
219
|
-
projectPath = taken.join(" ");
|
|
221
|
+
projectPath = taken.join(" ").replaceAll("%20", " ");
|
|
220
222
|
}
|
|
221
223
|
return {
|
|
222
224
|
executable,
|
|
@@ -16356,6 +16358,12 @@ var init_tool_definitions = __esm(() => {
|
|
|
16356
16358
|
type: "boolean",
|
|
16357
16359
|
ops: ["stop"],
|
|
16358
16360
|
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."
|
|
16361
|
+
},
|
|
16362
|
+
pid: {
|
|
16363
|
+
type: "integer",
|
|
16364
|
+
minimum: 1,
|
|
16365
|
+
ops: ["stop"],
|
|
16366
|
+
description: "stop: a game of this project that no server here is holding, by its process id as editor_status lists it under runtimes: one a server before a reconnect started and left no note for, or one started some other way. It is ended only when it announced itself for this project and the operating system describes it as this project's engine run, --path on the project and no editor flag, started before it announced; otherwise nothing is signalled and the answer says which of those failed. The run this server holds is ended the ordinary way whether or not its number is given."
|
|
16359
16367
|
}
|
|
16360
16368
|
},
|
|
16361
16369
|
requires: [],
|
|
@@ -16365,7 +16373,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
16365
16373
|
requires: ["projectPath"]
|
|
16366
16374
|
},
|
|
16367
16375
|
stop: {
|
|
16368
|
-
summary: "end the run and answer with what it printed last, naming the process ended under endedPid: for a run the editor plays, the number its game announced under, read at the stop rather than remembered, and the operating system hands a freed number to the next process, so it can equal the game before it. A game that started processes of its own keeps them unless andChildren says to end those too",
|
|
16376
|
+
summary: "end the run and answer with what it printed last, naming the process ended under endedPid: for a run the editor plays, the number its game announced under, read at the stop rather than remembered, and the operating system hands a freed number to the next process, so it can equal the game before it. A game that started processes of its own keeps them unless andChildren says to end those too. stopped is false, with the number under notSignalled, when that number no longer answers as the process the run was started as and nothing was signalled",
|
|
16369
16377
|
requires: []
|
|
16370
16378
|
},
|
|
16371
16379
|
check: {
|
|
@@ -31703,8 +31711,13 @@ function parseAnnouncement(file, pid) {
|
|
|
31703
31711
|
name: readString(project, "name") ?? "",
|
|
31704
31712
|
path: readString(project, "path") ?? ""
|
|
31705
31713
|
};
|
|
31714
|
+
let announcedAt;
|
|
31715
|
+
try {
|
|
31716
|
+
announcedAt = statSync5(file).mtimeMs;
|
|
31717
|
+
} catch {}
|
|
31718
|
+
const when = announcedAt === undefined ? {} : { announcedAt };
|
|
31706
31719
|
if (protocol !== RUNTIME_PROTOCOL) {
|
|
31707
|
-
return { kind: "unspoken", unspoken: { pid, protocol: protocol ?? 0, project: named } };
|
|
31720
|
+
return { kind: "unspoken", unspoken: { pid, protocol: protocol ?? 0, project: named, ...when } };
|
|
31708
31721
|
}
|
|
31709
31722
|
const editorPid = readNumber(fields, "editor_pid");
|
|
31710
31723
|
return {
|
|
@@ -31715,7 +31728,8 @@ function parseAnnouncement(file, pid) {
|
|
|
31715
31728
|
address,
|
|
31716
31729
|
project: named,
|
|
31717
31730
|
file,
|
|
31718
|
-
...editorPid !== undefined && Number.isInteger(editorPid) && editorPid > 0 ? { editorPid } : {}
|
|
31731
|
+
...editorPid !== undefined && Number.isInteger(editorPid) && editorPid > 0 ? { editorPid } : {},
|
|
31732
|
+
...when
|
|
31719
31733
|
}
|
|
31720
31734
|
};
|
|
31721
31735
|
}
|
|
@@ -37733,8 +37747,25 @@ var init_lsp_client = __esm(() => {
|
|
|
37733
37747
|
// src/outside.ts
|
|
37734
37748
|
import { spawn } from "node:child_process";
|
|
37735
37749
|
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
37736
|
-
function
|
|
37737
|
-
|
|
37750
|
+
function changesFrom(env, base) {
|
|
37751
|
+
const changes = {};
|
|
37752
|
+
if (env === undefined) {
|
|
37753
|
+
return changes;
|
|
37754
|
+
}
|
|
37755
|
+
for (const [name, value] of Object.entries(env)) {
|
|
37756
|
+
if (value !== undefined && base[name] !== value) {
|
|
37757
|
+
changes[name] = value;
|
|
37758
|
+
}
|
|
37759
|
+
}
|
|
37760
|
+
for (const name of Object.keys(base)) {
|
|
37761
|
+
if (env[name] === undefined) {
|
|
37762
|
+
changes[name] = null;
|
|
37763
|
+
}
|
|
37764
|
+
}
|
|
37765
|
+
return changes;
|
|
37766
|
+
}
|
|
37767
|
+
function sendSpec(child, spec) {
|
|
37768
|
+
child.stdin?.end(JSON.stringify(spec), "utf8");
|
|
37738
37769
|
}
|
|
37739
37770
|
function readLaunched(printed) {
|
|
37740
37771
|
const line = printed.split(`
|
|
@@ -37747,10 +37778,16 @@ function readLaunched(printed) {
|
|
|
37747
37778
|
}
|
|
37748
37779
|
async function launchOutsideTheTree(spec, runtime = process.execPath, keeper = KEEPER_SCRIPT) {
|
|
37749
37780
|
return await new Promise((resolve) => {
|
|
37750
|
-
const launcher = spawn(runtime, [keeper, "launch"
|
|
37751
|
-
stdio: ["
|
|
37781
|
+
const launcher = spawn(runtime, [keeper, "launch"], {
|
|
37782
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
37752
37783
|
windowsHide: true
|
|
37753
37784
|
});
|
|
37785
|
+
sendSpec(launcher, {
|
|
37786
|
+
command: spec.command,
|
|
37787
|
+
args: spec.args,
|
|
37788
|
+
envChanges: changesFrom(spec.env, process.env),
|
|
37789
|
+
...spec.run === undefined ? {} : { run: spec.run }
|
|
37790
|
+
});
|
|
37754
37791
|
let printed = "";
|
|
37755
37792
|
let complained = "";
|
|
37756
37793
|
launcher.stdout.on("data", (chunk) => {
|
|
@@ -37987,30 +38024,61 @@ var init_restart_note = __esm(() => {
|
|
|
37987
38024
|
|
|
37988
38025
|
// src/run-record.ts
|
|
37989
38026
|
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
37990
|
-
import {
|
|
37991
|
-
import {
|
|
38027
|
+
import { createHash as createHash5 } from "node:crypto";
|
|
38028
|
+
import {
|
|
38029
|
+
mkdirSync as mkdirSync13,
|
|
38030
|
+
openSync as openSync2,
|
|
38031
|
+
readdirSync as readdirSync8,
|
|
38032
|
+
readFileSync as readFileSync20,
|
|
38033
|
+
realpathSync as realpathSync3,
|
|
38034
|
+
rmSync as rmSync11,
|
|
38035
|
+
statSync as statSync7,
|
|
38036
|
+
writeFileSync as writeFileSync15
|
|
38037
|
+
} from "node:fs";
|
|
38038
|
+
import { basename as basename3, dirname as dirname16, join as join22, resolve as resolve5 } from "node:path";
|
|
37992
38039
|
function runsDirectory() {
|
|
37993
38040
|
return join22(runtimeDirectory(), "runs");
|
|
37994
38041
|
}
|
|
37995
|
-
function
|
|
37996
|
-
|
|
38042
|
+
function realAsFarAsItExists(path) {
|
|
38043
|
+
const missing = [];
|
|
38044
|
+
let at = path;
|
|
38045
|
+
for (;; ) {
|
|
38046
|
+
try {
|
|
38047
|
+
return join22(realpathSync3.native(at), ...missing);
|
|
38048
|
+
} catch {
|
|
38049
|
+
const parent = dirname16(at);
|
|
38050
|
+
if (parent === at) {
|
|
38051
|
+
return path;
|
|
38052
|
+
}
|
|
38053
|
+
missing.unshift(basename3(at));
|
|
38054
|
+
at = parent;
|
|
38055
|
+
}
|
|
38056
|
+
}
|
|
38057
|
+
}
|
|
38058
|
+
function projectKey(projectPath) {
|
|
38059
|
+
const real = realAsFarAsItExists(resolve5(projectPath)).replace(/[\\/]+$/, "");
|
|
38060
|
+
const folded = process.platform === "win32" || process.platform === "darwin" ? real.toLowerCase() : real;
|
|
38061
|
+
return createHash5("sha256").update(folded.replaceAll("\\", "/")).digest("hex").slice(0, 16);
|
|
38062
|
+
}
|
|
38063
|
+
function runRecordPath(projectPath, directory = runtimeDirectory()) {
|
|
38064
|
+
return join22(directory, "runs", `note-${projectKey(projectPath)}.json`);
|
|
37997
38065
|
}
|
|
37998
38066
|
function openTranscript(startedAt) {
|
|
37999
38067
|
mkdirSync13(runsDirectory(), { recursive: true });
|
|
38000
38068
|
const path = join22(runsDirectory(), `run-${startedAt}.log`);
|
|
38001
38069
|
return { path, fd: openSync2(path, "a") };
|
|
38002
38070
|
}
|
|
38003
|
-
function editorNotePath() {
|
|
38004
|
-
return join22(
|
|
38071
|
+
function editorNotePath(projectPath, directory = runtimeDirectory()) {
|
|
38072
|
+
return join22(directory, "runs", `editor-${projectKey(projectPath)}.json`);
|
|
38005
38073
|
}
|
|
38006
38074
|
function writeEditorRunNote(note) {
|
|
38007
38075
|
mkdirSync13(runsDirectory(), { recursive: true });
|
|
38008
|
-
writeFileSync15(editorNotePath(), JSON.stringify(note, null, 2), "utf8");
|
|
38076
|
+
writeFileSync15(editorNotePath(note.projectPath), JSON.stringify(note, null, 2), "utf8");
|
|
38009
38077
|
}
|
|
38010
|
-
function readEditorRunNote() {
|
|
38078
|
+
function readEditorRunNote(projectPath) {
|
|
38011
38079
|
for (const directory of runtimeDirectories()) {
|
|
38012
|
-
const note = editorNoteAt(join22(directory, "runs", "editor-run.json"));
|
|
38013
|
-
if (note !== null) {
|
|
38080
|
+
const note = editorNoteAt(editorNotePath(projectPath, directory)) ?? editorNoteAt(join22(directory, "runs", "editor-run.json"));
|
|
38081
|
+
if (note !== null && isSameDirectory(note.projectPath, projectPath)) {
|
|
38014
38082
|
return note;
|
|
38015
38083
|
}
|
|
38016
38084
|
}
|
|
@@ -38035,15 +38103,39 @@ function editorNoteAt(path) {
|
|
|
38035
38103
|
}
|
|
38036
38104
|
return { projectPath, transcript, startedAt };
|
|
38037
38105
|
}
|
|
38038
|
-
function readRunRecord() {
|
|
38039
|
-
for (const
|
|
38040
|
-
const
|
|
38041
|
-
if (
|
|
38042
|
-
return
|
|
38106
|
+
function readRunRecord(projectPath) {
|
|
38107
|
+
for (const directory of runtimeDirectories()) {
|
|
38108
|
+
const own = recordAt(runRecordPath(projectPath, directory));
|
|
38109
|
+
if (own !== null) {
|
|
38110
|
+
return own;
|
|
38111
|
+
}
|
|
38112
|
+
const shared = recordAt(join22(directory, "runs", SHARED_NOTE));
|
|
38113
|
+
if (shared !== null && shared.projectPath !== "" && isSameDirectory(shared.projectPath, projectPath)) {
|
|
38114
|
+
return shared;
|
|
38043
38115
|
}
|
|
38044
38116
|
}
|
|
38045
38117
|
return null;
|
|
38046
38118
|
}
|
|
38119
|
+
function everyRunRecord() {
|
|
38120
|
+
const found = [];
|
|
38121
|
+
for (const directory of runtimeDirectories()) {
|
|
38122
|
+
let entries;
|
|
38123
|
+
try {
|
|
38124
|
+
entries = readdirSync8(join22(directory, "runs"));
|
|
38125
|
+
} catch {
|
|
38126
|
+
continue;
|
|
38127
|
+
}
|
|
38128
|
+
for (const entry of entries) {
|
|
38129
|
+
if (entry === SHARED_NOTE || entry.startsWith("note-") && entry.endsWith(".json")) {
|
|
38130
|
+
const record = recordAt(join22(directory, "runs", entry));
|
|
38131
|
+
if (record !== null) {
|
|
38132
|
+
found.push(record);
|
|
38133
|
+
}
|
|
38134
|
+
}
|
|
38135
|
+
}
|
|
38136
|
+
}
|
|
38137
|
+
return found.sort((one, other) => other.startedAt - one.startedAt);
|
|
38138
|
+
}
|
|
38047
38139
|
function recordAt(path) {
|
|
38048
38140
|
let parsed;
|
|
38049
38141
|
try {
|
|
@@ -38201,7 +38293,7 @@ function judgeRun(record, running, needed) {
|
|
|
38201
38293
|
if (wanted !== null && !said.includes(wanted)) {
|
|
38202
38294
|
return false;
|
|
38203
38295
|
}
|
|
38204
|
-
if (running.kind === "commandLine" &&
|
|
38296
|
+
if (running.kind === "commandLine" && readCommandLine(running.text).editor) {
|
|
38205
38297
|
return false;
|
|
38206
38298
|
}
|
|
38207
38299
|
if (record.projectPath === "") {
|
|
@@ -38213,15 +38305,32 @@ function judgeRun(record, running, needed) {
|
|
|
38213
38305
|
const project = process.platform === "win32" ? record.projectPath.toLowerCase() : record.projectPath;
|
|
38214
38306
|
return said.includes(project);
|
|
38215
38307
|
}
|
|
38216
|
-
function
|
|
38217
|
-
|
|
38218
|
-
|
|
38219
|
-
|
|
38220
|
-
|
|
38308
|
+
function stillTheAnnouncedGame(pid, projectPath, announcedAt) {
|
|
38309
|
+
return judgeAnnouncedGame(runningAs(pid), projectPath, announcedAt);
|
|
38310
|
+
}
|
|
38311
|
+
function judgeAnnouncedGame(running, projectPath, announcedAt) {
|
|
38312
|
+
if (running?.kind !== "commandLine" || running.startedAt === undefined) {
|
|
38313
|
+
return null;
|
|
38314
|
+
}
|
|
38315
|
+
if (running.startedAt - announcedAt > STARTED_AFTER_ANNOUNCING_MS) {
|
|
38316
|
+
return null;
|
|
38317
|
+
}
|
|
38318
|
+
const read = readCommandLine(running.text);
|
|
38319
|
+
if (read.editor || read.projectPath === null || !isSameDirectory(read.projectPath, projectPath)) {
|
|
38320
|
+
return null;
|
|
38321
|
+
}
|
|
38322
|
+
return read;
|
|
38323
|
+
}
|
|
38324
|
+
function clearRunRecord(projectPath) {
|
|
38325
|
+
for (const directory of runtimeDirectories()) {
|
|
38326
|
+
const shared = join22(directory, "runs", SHARED_NOTE);
|
|
38327
|
+
const sharedRecord = recordAt(shared);
|
|
38328
|
+
const ours = sharedRecord !== null && sharedRecord.projectPath !== "" && isSameDirectory(sharedRecord.projectPath, projectPath);
|
|
38329
|
+
for (const path of [runRecordPath(projectPath, directory), ...ours ? [shared] : []]) {
|
|
38330
|
+
try {
|
|
38331
|
+
rmSync11(path, { force: true });
|
|
38332
|
+
} catch {}
|
|
38221
38333
|
}
|
|
38222
|
-
try {
|
|
38223
|
-
rmSync11(path);
|
|
38224
|
-
} catch {}
|
|
38225
38334
|
}
|
|
38226
38335
|
}
|
|
38227
38336
|
function ageMs(path, now) {
|
|
@@ -38233,7 +38342,7 @@ function ageMs(path, now) {
|
|
|
38233
38342
|
}
|
|
38234
38343
|
function sweepTranscripts(keepMs = 24 * 60 * 60 * 1000, now = Date.now()) {
|
|
38235
38344
|
const directory = runsDirectory();
|
|
38236
|
-
const
|
|
38345
|
+
const kept = new Set(everyRunRecord().map((record) => record.transcript));
|
|
38237
38346
|
let entries;
|
|
38238
38347
|
try {
|
|
38239
38348
|
entries = readdirSync8(directory);
|
|
@@ -38245,7 +38354,7 @@ function sweepTranscripts(keepMs = 24 * 60 * 60 * 1000, now = Date.now()) {
|
|
|
38245
38354
|
continue;
|
|
38246
38355
|
}
|
|
38247
38356
|
const path = join22(directory, entry);
|
|
38248
|
-
if (path
|
|
38357
|
+
if (kept.has(path) || ageMs(path, now) < keepMs) {
|
|
38249
38358
|
continue;
|
|
38250
38359
|
}
|
|
38251
38360
|
try {
|
|
@@ -38253,11 +38362,11 @@ function sweepTranscripts(keepMs = 24 * 60 * 60 * 1000, now = Date.now()) {
|
|
|
38253
38362
|
} catch {}
|
|
38254
38363
|
}
|
|
38255
38364
|
}
|
|
38256
|
-
var
|
|
38365
|
+
var SHARED_NOTE = "run.json", SAME_RUN_WINDOW_MS = 1e4;
|
|
38257
38366
|
var init_run_record = __esm(() => {
|
|
38367
|
+
init_paths();
|
|
38258
38368
|
init_process_children();
|
|
38259
38369
|
init_runtime_client();
|
|
38260
|
-
AN_EDITOR = /(?:^|\s)(?:-e|--editor)(?:\s|$)/;
|
|
38261
38370
|
});
|
|
38262
38371
|
|
|
38263
38372
|
// src/server.ts
|
|
@@ -38280,7 +38389,7 @@ import {
|
|
|
38280
38389
|
statSync as statSync8
|
|
38281
38390
|
} from "node:fs";
|
|
38282
38391
|
import { tmpdir as tmpdir5 } from "node:os";
|
|
38283
|
-
import { basename as basename4, dirname as
|
|
38392
|
+
import { basename as basename4, dirname as dirname17, extname as extname2, isAbsolute as isAbsolute3, join as join23, normalize as normalize3, relative as relative3 } from "node:path";
|
|
38284
38393
|
import { setTimeout as delay3 } from "node:timers/promises";
|
|
38285
38394
|
import { fileURLToPath as fileURLToPath6 } from "node:url";
|
|
38286
38395
|
import { promisify as promisify7 } from "node:util";
|
|
@@ -38294,9 +38403,9 @@ function captureDestinationRefusal(outputPath, projects) {
|
|
|
38294
38403
|
if (extname2(outputPath).toLowerCase() !== ".png") {
|
|
38295
38404
|
return `outputPath ${outputPath} does not end in .png, which is what a capture is.`;
|
|
38296
38405
|
}
|
|
38297
|
-
const directory = realPathOr(
|
|
38406
|
+
const directory = realPathOr(dirname17(outputPath));
|
|
38298
38407
|
if (!existsSync15(directory)) {
|
|
38299
|
-
return `outputPath's directory ${
|
|
38408
|
+
return `outputPath's directory ${dirname17(outputPath)} does not exist; a capture creates no directories.`;
|
|
38300
38409
|
}
|
|
38301
38410
|
const where = join23(directory, basename4(outputPath));
|
|
38302
38411
|
const inside = projects.find((project) => project !== "" && isWithinRoot(realPathOr(project), where));
|
|
@@ -38401,12 +38510,41 @@ function endedWithoutACode(run) {
|
|
|
38401
38510
|
}
|
|
38402
38511
|
return `The editor stopped playing this run. ${kept} What it printed is below.`;
|
|
38403
38512
|
}
|
|
38513
|
+
function stopVerdict(stop) {
|
|
38514
|
+
if (!stop.wasRunning) {
|
|
38515
|
+
return {
|
|
38516
|
+
stopped: true,
|
|
38517
|
+
endedPid: stop.endedPid,
|
|
38518
|
+
clean: stop.errors === 0,
|
|
38519
|
+
note: "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.",
|
|
38520
|
+
withChildren: false
|
|
38521
|
+
};
|
|
38522
|
+
}
|
|
38523
|
+
if (stop.ending === "refused") {
|
|
38524
|
+
return {
|
|
38525
|
+
stopped: false,
|
|
38526
|
+
notSignalled: stop.endedPid,
|
|
38527
|
+
note: `Nothing was ended: pid ${stop.endedPid ?? "unknown"} no longer answers as the process this run was started as (its engine, its project and when it started), so it was not signalled. If that process is still the game, end it yourself, for instance with runtime_invoke calling get_tree().quit() when the game has the runtime addon; if it is not, it belongs to something else.`,
|
|
38528
|
+
withChildren: false
|
|
38529
|
+
};
|
|
38530
|
+
}
|
|
38531
|
+
return {
|
|
38532
|
+
stopped: true,
|
|
38533
|
+
endedPid: stop.endedPid,
|
|
38534
|
+
clean: stop.errors === 0,
|
|
38535
|
+
note: `${stop.throughEditor ? stop.endedPid === null ? "The editor was asked to stop the scene it is playing. Its game had not announced a runtime, so no process was named under endedPid, and its exit code stays with the editor." : "The editor was asked to stop the scene it is playing." : "The process named under endedPid was ended."}${stop.exitSignal === null ? "" : ` It was ended by ${stop.exitSignal} and so has no exit code.`}${stop.ending === "gone" ? "" : ` It had not exited ${STOP_WAIT_MS / 1000} seconds after being told to, so it may still be going: editor_status says whether it is.`}`,
|
|
38536
|
+
withChildren: true
|
|
38537
|
+
};
|
|
38538
|
+
}
|
|
38404
38539
|
function endedPreviousRun(ended) {
|
|
38405
|
-
if (ended === null) {
|
|
38540
|
+
if (ended === null || ended.ending === "refused") {
|
|
38406
38541
|
return;
|
|
38407
38542
|
}
|
|
38408
38543
|
return ended.pid ?? true;
|
|
38409
38544
|
}
|
|
38545
|
+
function previousRunLeft(ended) {
|
|
38546
|
+
return ended?.ending === "refused" && ended.pid !== null ? ended.pid : undefined;
|
|
38547
|
+
}
|
|
38410
38548
|
function engineNamesOf(executable) {
|
|
38411
38549
|
return CONSOLE_WRAPPER.test(executable) ? [executable, executable.replace(CONSOLE_WRAPPER, "$1")] : [executable];
|
|
38412
38550
|
}
|
|
@@ -38446,6 +38584,17 @@ function aboutTheChildren(ended, throughEditor) {
|
|
|
38446
38584
|
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.`;
|
|
38447
38585
|
return `${endedToo}${leftToo}`;
|
|
38448
38586
|
}
|
|
38587
|
+
function childrenFields(ended) {
|
|
38588
|
+
if (ended === null) {
|
|
38589
|
+
return {};
|
|
38590
|
+
}
|
|
38591
|
+
return {
|
|
38592
|
+
endedChildren: ended.ended.map((one) => one.pid),
|
|
38593
|
+
identifiedBy: ended.ended.length === 0 ? undefined : Object.fromEntries(ended.ended.map((one) => [String(one.pid), one.identifiedBy])),
|
|
38594
|
+
childrenLeft: ended.left.length === 0 ? undefined : ended.left,
|
|
38595
|
+
childrenUnknown: ended.unknown === false ? undefined : true
|
|
38596
|
+
};
|
|
38597
|
+
}
|
|
38449
38598
|
function uidsLeftNote(left) {
|
|
38450
38599
|
if (left === 0) {
|
|
38451
38600
|
return "No scene was written: this reads the project and imports it, and does not resave anything.";
|
|
@@ -38453,12 +38602,24 @@ function uidsLeftNote(left) {
|
|
|
38453
38602
|
const them = left === 1 ? "it" : "them";
|
|
38454
38603
|
return `${left === 1 ? "One file" : `${left} files`} still ${left === 1 ? "has" : "have"} no .uid, named under stillWithoutUid, which is the engine declining to import ${them} rather than this op skipping ${them}. No scene was written.`;
|
|
38455
38604
|
}
|
|
38605
|
+
function leftRunningNote(stranded) {
|
|
38606
|
+
if (stranded.length === 0) {
|
|
38607
|
+
return "";
|
|
38608
|
+
}
|
|
38609
|
+
const named = stranded.map((one) => one.protocol === null ? `pid ${one.pid}` : `pid ${one.pid} on protocol ${one.protocol}`).join(", ");
|
|
38610
|
+
const [only] = stranded;
|
|
38611
|
+
return stranded.length === 1 && only !== undefined ? ` A game of this project was already running and still is: ${named}. Starting this one did not end it, because this server did not start it. editor_run stop with pid ${only.pid} ends it and leaves this run going.` : ` ${stranded.length} games of this project were already running and still are: ${named}. Starting this one did not end them, because this server did not start them. editor_run stop with the pid of each ends it and leaves this run going.`;
|
|
38612
|
+
}
|
|
38456
38613
|
function endedToStartThis(ended) {
|
|
38457
38614
|
if (ended === null) {
|
|
38458
38615
|
return "";
|
|
38459
38616
|
}
|
|
38460
38617
|
const which = ended.pid === null ? "The game the editor was playing" : `The run that was going, pid ${ended.pid},`;
|
|
38461
|
-
|
|
38618
|
+
if (ended.ending === "refused") {
|
|
38619
|
+
return ` ${which} was not ended: that pid no longer answers as the process the run was started as, so nothing was signalled. If it is still the game, it is running beside this one and you have to end it yourself; if it is not, it belongs to something else.`;
|
|
38620
|
+
}
|
|
38621
|
+
const after = ended.ending === "lingering" ? ` It had not exited ${STOP_WAIT_MS / 1000} seconds after being told to, so it may still be running beside this one: editor_status says whether it is.` : "";
|
|
38622
|
+
return ` ${which} was ended to start this one; its output is no longer what editor_output answers about.${after}`;
|
|
38462
38623
|
}
|
|
38463
38624
|
function runtimeVerdict(endpoint, after) {
|
|
38464
38625
|
if (endpoint !== null) {
|
|
@@ -39763,11 +39924,11 @@ class GodotServer {
|
|
|
39763
39924
|
if (!engine.ok) {
|
|
39764
39925
|
return engine.response;
|
|
39765
39926
|
}
|
|
39766
|
-
const folder =
|
|
39927
|
+
const folder = dirname17(output.absolutePath);
|
|
39767
39928
|
try {
|
|
39768
39929
|
mkdirSync14(folder, { recursive: true });
|
|
39769
39930
|
} catch (error) {
|
|
39770
|
-
return this.createErrorResponse(`Cannot create ${
|
|
39931
|
+
return this.createErrorResponse(`Cannot create ${dirname17(output.relativePath)} to export into: ${errorMessage(error)}`);
|
|
39771
39932
|
}
|
|
39772
39933
|
const debug = readBoolean(args, "debug") ?? false;
|
|
39773
39934
|
const exportLogs = mkdtempSync3(join23(tmpdir5(), "gdharness-export-"));
|
|
@@ -39912,7 +40073,7 @@ class GodotServer {
|
|
|
39912
40073
|
} finally {
|
|
39913
40074
|
discard(reportsDir, userData);
|
|
39914
40075
|
try {
|
|
39915
|
-
rmdirSync(
|
|
40076
|
+
rmdirSync(dirname17(reportsDir));
|
|
39916
40077
|
} catch {}
|
|
39917
40078
|
}
|
|
39918
40079
|
const engineEntries = run.log.select({ severity: "warning", sinceLastCall: false, limit: 200 }).entries.map(aboveTheRunner);
|
|
@@ -40574,8 +40735,10 @@ class GodotServer {
|
|
|
40574
40735
|
const before = this.currentRun();
|
|
40575
40736
|
if (before !== null && await this.runStillGoing(before)) {
|
|
40576
40737
|
this.logDebug("Ending the running game before starting another");
|
|
40577
|
-
ended = {
|
|
40578
|
-
|
|
40738
|
+
ended = {
|
|
40739
|
+
pid: before.pid ?? this.announcedPidOf(before) ?? null,
|
|
40740
|
+
ending: await this.endActiveGame("editor_run start, which ends the run that was going before it starts another", true)
|
|
40741
|
+
};
|
|
40579
40742
|
}
|
|
40580
40743
|
const alreadyPlaying = new Set(discoverRuntimes().map((endpoint) => endpoint.pid));
|
|
40581
40744
|
const wereHereFirst = this.everyRuntimeOfOurs();
|
|
@@ -40606,8 +40769,7 @@ class GodotServer {
|
|
|
40606
40769
|
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." : this.godotBridge.isConnected() && editorWouldPlay && needsItsOwnProcess(asked.value) ? " The editor plays a game with its own environment, so a run given one was started " + "here: the debug_* tools answer only for a game the editor is playing." : "";
|
|
40607
40770
|
const endedForThis = endedToStartThis(ended);
|
|
40608
40771
|
const stranded = wereHereFirst.filter((one) => one.pid !== ended?.pid && alive(one.pid));
|
|
40609
|
-
const
|
|
40610
|
-
const alsoRunning = stranded.length === 0 ? "" : ` A game of this project was already running and still is: ${named}. Starting this one did not end it and nothing here can, because this server did not start it. End it yourself, or bring the halves to one version where a protocol is named.`;
|
|
40772
|
+
const alsoRunning = leftRunningNote(stranded);
|
|
40611
40773
|
return this.jsonTextResponse({
|
|
40612
40774
|
started: true,
|
|
40613
40775
|
through: "gdharness",
|
|
@@ -40619,6 +40781,7 @@ class GodotServer {
|
|
|
40619
40781
|
...scanWaitAnswer(scanned),
|
|
40620
40782
|
savesNote: asked.value.savesIn !== undefined && savesStayPut() ? SAVES_NOT_MOVED_NOTE : undefined,
|
|
40621
40783
|
endedPreviousRun: endedPreviousRun(ended),
|
|
40784
|
+
previousRunLeft: previousRunLeft(ended),
|
|
40622
40785
|
runtime: await this.runtimeUp(project.value.path, alreadyPlaying, runtimeWaitMs, given.value.length > 0),
|
|
40623
40786
|
message: `Use editor_output for what it prints and editor_run stop to end it.${spawnedInstead}${endedForThis}${alsoRunning}`
|
|
40624
40787
|
});
|
|
@@ -40751,6 +40914,7 @@ class GodotServer {
|
|
|
40751
40914
|
...scanWaitAnswer(scanned),
|
|
40752
40915
|
debugPort: readNumber(playAnswer, "debugPort"),
|
|
40753
40916
|
endedPreviousRun: endedPreviousRun(ended),
|
|
40917
|
+
previousRunLeft: previousRunLeft(ended),
|
|
40754
40918
|
breakpoints: breakpoints.applied.length === 0 ? undefined : this.breakpointsAsProjectSpells(projectPath, breakpoints.applied),
|
|
40755
40919
|
breakpointsRefused: breakpoints.refused.length === 0 ? undefined : breakpoints.refused.map((one) => ({
|
|
40756
40920
|
scriptPath: projectSpelling(projectPath, one.scriptPath),
|
|
@@ -40825,31 +40989,41 @@ class GodotServer {
|
|
|
40825
40989
|
}
|
|
40826
40990
|
async endActiveGame(reason, going) {
|
|
40827
40991
|
const running = this.activeProcess;
|
|
40828
|
-
const
|
|
40992
|
+
const project = running?.projectPath ?? this.ourProject();
|
|
40829
40993
|
const clearTheRecord = () => {
|
|
40830
|
-
|
|
40994
|
+
if (project !== null) {
|
|
40995
|
+
clearRunRecord(project);
|
|
40996
|
+
}
|
|
40831
40997
|
};
|
|
40832
40998
|
const aKeeperHoldsIt = running !== null && going && !running.throughEditor && running.pid !== null;
|
|
40833
40999
|
if (!aKeeperHoldsIt) {
|
|
40834
41000
|
clearTheRecord();
|
|
40835
41001
|
}
|
|
40836
41002
|
if (!running || !going) {
|
|
40837
|
-
return
|
|
41003
|
+
return "gone";
|
|
40838
41004
|
}
|
|
40839
41005
|
running.endedHere = reason;
|
|
40840
41006
|
const announced = this.announcedPidOf(running);
|
|
40841
41007
|
if (running.throughEditor) {
|
|
40842
41008
|
await this.handleViaBridge("stop_playing", {});
|
|
40843
|
-
return await untilGone(announced === undefined ? [] : [announced]);
|
|
41009
|
+
return await untilGone(announced === undefined ? [] : [announced]) ? "gone" : "lingering";
|
|
40844
41010
|
}
|
|
40845
41011
|
if (running.pid === null) {
|
|
40846
|
-
return
|
|
40847
|
-
}
|
|
40848
|
-
|
|
41012
|
+
return "gone";
|
|
41013
|
+
}
|
|
41014
|
+
const startedAs = {
|
|
41015
|
+
pid: running.pid,
|
|
41016
|
+
transcript: running.transcript ?? "",
|
|
41017
|
+
startedAt: running.startedAt,
|
|
41018
|
+
projectPath: running.projectPath ?? "",
|
|
41019
|
+
arguments: [],
|
|
41020
|
+
...running.command === undefined ? {} : { command: running.command }
|
|
41021
|
+
};
|
|
41022
|
+
if (!stillTheRecordedRun(startedAs)) {
|
|
40849
41023
|
clearTheRecord();
|
|
40850
41024
|
running.endedHere = null;
|
|
40851
|
-
running.log.record("warning", `This run was not ended here: pid ${running.pid} no longer answers as the run
|
|
40852
|
-
return
|
|
41025
|
+
running.log.record("warning", `This run was not ended here: pid ${running.pid} no longer answers as the process the run was started as, so nothing was signalled. If that process is still the game, end it yourself; if it is not, it belongs to something else.`);
|
|
41026
|
+
return "refused";
|
|
40853
41027
|
}
|
|
40854
41028
|
const signalled = runningAs(running.pid);
|
|
40855
41029
|
const described = signalled === null ? "nothing it would name" : `${signalled.kind}: ${signalled.text}`;
|
|
@@ -40869,7 +41043,7 @@ class GodotServer {
|
|
|
40869
41043
|
}
|
|
40870
41044
|
clearTheRecord();
|
|
40871
41045
|
running.log.record("info", landed ? `gdharness ended pid ${running.pid}, which the operating system described as ${described}.` : `gdharness signalled pid ${running.pid} and it was already gone; the operating system had described it as ${described}.`);
|
|
40872
|
-
return gone;
|
|
41046
|
+
return gone ? "gone" : "lingering";
|
|
40873
41047
|
}
|
|
40874
41048
|
spawnGame(godotPath, cmdArgs, env) {
|
|
40875
41049
|
const child = spawn2(godotPath, cmdArgs, { stdio: ["ignore", "pipe", "pipe"], ...env ? { env } : {} });
|
|
@@ -40945,6 +41119,7 @@ class GodotServer {
|
|
|
40945
41119
|
readOffset: 0,
|
|
40946
41120
|
projectPath,
|
|
40947
41121
|
startedAt,
|
|
41122
|
+
command: godotPath,
|
|
40948
41123
|
exitCode: null,
|
|
40949
41124
|
exitSignal: null,
|
|
40950
41125
|
throughEditor: false,
|
|
@@ -41002,8 +41177,7 @@ class GodotServer {
|
|
|
41002
41177
|
this.logDebug(`Picked up an editor-played run without its adapter: ${errorMessage(error)}`);
|
|
41003
41178
|
}
|
|
41004
41179
|
const project = this.godotBridge.getStatus().projectPath ?? null;
|
|
41005
|
-
const
|
|
41006
|
-
const note = left !== null && project !== null && isSameDirectory(left.projectPath, project) ? left : null;
|
|
41180
|
+
const note = project === null ? null : readEditorRunNote(project);
|
|
41007
41181
|
const picked = {
|
|
41008
41182
|
pid: null,
|
|
41009
41183
|
log: new GameLog,
|
|
@@ -41136,7 +41310,8 @@ class GodotServer {
|
|
|
41136
41310
|
return endpoint.editorPid === undefined || endpoint.editorPid !== editor;
|
|
41137
41311
|
}
|
|
41138
41312
|
adoptRecordedRun() {
|
|
41139
|
-
const
|
|
41313
|
+
const project = this.ourProject();
|
|
41314
|
+
const record = project === null ? null : readRunRecord(project);
|
|
41140
41315
|
if (record === null || !this.couldBeOurs(record.projectPath)) {
|
|
41141
41316
|
return null;
|
|
41142
41317
|
}
|
|
@@ -41147,6 +41322,7 @@ class GodotServer {
|
|
|
41147
41322
|
readOffset: 0,
|
|
41148
41323
|
projectPath: record.projectPath === "" ? null : record.projectPath,
|
|
41149
41324
|
startedAt: record.startedAt,
|
|
41325
|
+
...record.command === undefined ? {} : { command: record.command },
|
|
41150
41326
|
exitCode: null,
|
|
41151
41327
|
exitSignal: null,
|
|
41152
41328
|
throughEditor: false,
|
|
@@ -41168,18 +41344,18 @@ class GodotServer {
|
|
|
41168
41344
|
const noEditor = !status.connected ? " No editor is connected, so a game the editor is playing cannot be seen from here at all:" + " editor_status says whether one is on its way, and a run of yours that is still up is" + " reachable again once it is." : "";
|
|
41169
41345
|
const live = this.aRuntimeOfOursIsAnswering();
|
|
41170
41346
|
if (live !== null) {
|
|
41171
|
-
return `A game is running for ${live.project.path} and has announced its runtime on port ${live.port}, pid ${live.pid},` + " but this server did not start it and cannot reach it through the editor, so editor_run and" +
|
|
41347
|
+
return `A game is running for ${live.project.path} and has announced its runtime on port ${live.port}, pid ${live.pid},` + " but this server did not start it and cannot reach it through the editor, so editor_run and" + ` editor_output have nothing to answer about. The runtime_* tools reach it now, and editor_run stop with pid ${live.pid} ends it.` + " A run started here is a separate process rather than a replacement, so starting one leaves" + " this game running as well.";
|
|
41172
41348
|
}
|
|
41173
41349
|
const unreadable = this.aRuntimeOfOursIsTooFarOff();
|
|
41174
41350
|
if (unreadable !== null) {
|
|
41175
41351
|
const behind = whichHalfIsBehind([unreadable]);
|
|
41176
|
-
return `A game is running for ${unreadable.project.path}, pid ${unreadable.pid}, announced in protocol` + ` ${unreadable.protocol}, which this server does not speak: it speaks ${RUNTIME_PROTOCOL}, so ${behind}.` +
|
|
41352
|
+
return `A game is running for ${unreadable.project.path}, pid ${unreadable.pid}, announced in protocol` + ` ${unreadable.protocol}, which this server does not speak: it speaks ${RUNTIME_PROTOCOL}, so ${behind}.` + ` Until then the runtime_* tools cannot reach it, and editor_run stop with pid ${unreadable.pid} ends it.` + " A run started here is a separate process rather than a replacement, so starting one leaves" + " this game running as well.";
|
|
41177
41353
|
}
|
|
41178
|
-
const
|
|
41354
|
+
const mine = this.ourProject();
|
|
41355
|
+
const record = everyRunRecord().find((one) => mine === null || !isSameDirectory(one.projectPath, mine)) ?? null;
|
|
41179
41356
|
if (record === null) {
|
|
41180
41357
|
return `No game is running.${noEditor} Start one with editor_run.`;
|
|
41181
41358
|
}
|
|
41182
|
-
const mine = this.ownProject ?? (status.connected ? status.projectPath ?? null : null);
|
|
41183
41359
|
const whose = record.projectPath === "" ? "a project it does not name" : record.projectPath;
|
|
41184
41360
|
const ours = mine === null ? "this server was started without GDHARNESS_PROJECT and has no editor connected, so it cannot say whose that run is" : `this server serves ${mine}`;
|
|
41185
41361
|
return `No game of this server's is running.${noEditor} A run started from ${whose} is recorded on this machine, and ${ours}, so it is not this server's to answer for or to end. Start one with editor_run.`;
|
|
@@ -41222,13 +41398,16 @@ class GodotServer {
|
|
|
41222
41398
|
];
|
|
41223
41399
|
}
|
|
41224
41400
|
couldBeOurs(project) {
|
|
41225
|
-
const
|
|
41226
|
-
const mine = this.ownProject ?? (status.connected ? status.projectPath ?? null : null);
|
|
41401
|
+
const mine = this.ourProject();
|
|
41227
41402
|
if (mine === null) {
|
|
41228
41403
|
return false;
|
|
41229
41404
|
}
|
|
41230
41405
|
return project !== "" && isSameDirectory(mine, project);
|
|
41231
41406
|
}
|
|
41407
|
+
ourProject() {
|
|
41408
|
+
const status = this.godotBridge.getStatus();
|
|
41409
|
+
return this.ownProject ?? (status.connected ? status.projectPath ?? null : null);
|
|
41410
|
+
}
|
|
41232
41411
|
currentRun() {
|
|
41233
41412
|
return this.activeProcess ?? this.adoptRecordedRun();
|
|
41234
41413
|
}
|
|
@@ -41307,7 +41486,7 @@ class GodotServer {
|
|
|
41307
41486
|
async untilTheExitIsRecorded(run) {
|
|
41308
41487
|
const until = Date.now() + EXIT_REPORTED_WITHIN_MS;
|
|
41309
41488
|
for (;; ) {
|
|
41310
|
-
const record = readRunRecord();
|
|
41489
|
+
const record = run.projectPath === null ? null : readRunRecord(run.projectPath);
|
|
41311
41490
|
if (record !== null && record.pid === run.pid && (record.exitCode !== undefined || record.exitSignal !== undefined)) {
|
|
41312
41491
|
run.exitCode = record.exitCode ?? null;
|
|
41313
41492
|
run.exitSignal = record.exitSignal ?? null;
|
|
@@ -41460,6 +41639,10 @@ class GodotServer {
|
|
|
41460
41639
|
async handleStopProject(args) {
|
|
41461
41640
|
await this.pickUpWhatTheEditorIsPlaying();
|
|
41462
41641
|
const stopped = this.currentRun();
|
|
41642
|
+
const named = readNumber(args, "pid");
|
|
41643
|
+
if (named !== undefined && (stopped === null || named !== stopped.pid && named !== this.announcedPidOf(stopped))) {
|
|
41644
|
+
return await this.stopAnnouncedGame(named, readBoolean(args, "andChildren") === true);
|
|
41645
|
+
}
|
|
41463
41646
|
if (!stopped) {
|
|
41464
41647
|
return this.createErrorResponse(this.nothingOfOursIsRunning());
|
|
41465
41648
|
}
|
|
@@ -41470,35 +41653,87 @@ class GodotServer {
|
|
|
41470
41653
|
const endedPid = stopped.pid ?? announcedPid;
|
|
41471
41654
|
const children = readBoolean(args, "andChildren") === true && wasRunning ? await this.whatTheRunStarted(stopped) : null;
|
|
41472
41655
|
this.logDebug("Stopping the running game");
|
|
41473
|
-
const
|
|
41474
|
-
const
|
|
41475
|
-
|
|
41656
|
+
const ending = await this.endActiveGame("editor_run stop", wasRunning);
|
|
41657
|
+
const refused = ending === "refused";
|
|
41658
|
+
const ended = children === null || refused ? null : endChildrenAmong(children);
|
|
41659
|
+
if (wasRunning && !refused) {
|
|
41476
41660
|
for (const pid of new Set([endedPid, announcedPid])) {
|
|
41477
41661
|
if (pid !== null) {
|
|
41478
41662
|
await announcementEnded(pid);
|
|
41479
41663
|
}
|
|
41480
41664
|
}
|
|
41481
41665
|
}
|
|
41666
|
+
const verdict = stopVerdict({
|
|
41667
|
+
wasRunning,
|
|
41668
|
+
ending,
|
|
41669
|
+
endedPid,
|
|
41670
|
+
throughEditor: stopped.throughEditor,
|
|
41671
|
+
exitSignal: stopped.exitSignal,
|
|
41672
|
+
errors: stopped.log.count("error")
|
|
41673
|
+
});
|
|
41482
41674
|
return this.jsonTextResponse({
|
|
41483
|
-
stopped:
|
|
41675
|
+
stopped: verdict.stopped,
|
|
41484
41676
|
through: stopped.throughEditor ? "editor" : "gdharness",
|
|
41485
|
-
endedPid,
|
|
41486
|
-
|
|
41487
|
-
|
|
41488
|
-
identifiedBy: ended.ended.length === 0 ? undefined : Object.fromEntries(ended.ended.map((one) => [String(one.pid), one.identifiedBy])),
|
|
41489
|
-
childrenLeft: ended.left.length === 0 ? undefined : ended.left,
|
|
41490
|
-
childrenUnknown: ended.unknown === false ? undefined : true
|
|
41491
|
-
},
|
|
41677
|
+
endedPid: verdict.endedPid,
|
|
41678
|
+
notSignalled: verdict.notSignalled,
|
|
41679
|
+
...childrenFields(ended),
|
|
41492
41680
|
exitedBeforeStop: !wasRunning,
|
|
41493
41681
|
exitCode: stopped.exitCode,
|
|
41494
41682
|
exitSignal: stopped.exitSignal ?? undefined,
|
|
41495
41683
|
errors: stopped.log.count("error"),
|
|
41496
41684
|
warnings: stopped.log.count("warning"),
|
|
41497
|
-
clean:
|
|
41498
|
-
note:
|
|
41685
|
+
clean: verdict.clean,
|
|
41686
|
+
note: verdict.withChildren ? `${verdict.note} ${aboutTheChildren(ended, stopped.throughEditor)}` : verdict.note,
|
|
41499
41687
|
entries: forAnswer(stopped.log.select({ severity: "warning", sinceLastCall: false, limit: 200 }).entries)
|
|
41500
41688
|
});
|
|
41501
41689
|
}
|
|
41690
|
+
async stopAnnouncedGame(pid, andChildren) {
|
|
41691
|
+
const announced = runtimesAnnounced();
|
|
41692
|
+
const game = [
|
|
41693
|
+
...this.allAnnouncedForOurProject(announced.running),
|
|
41694
|
+
...this.allAnnouncedForOurProject(announced.unspoken)
|
|
41695
|
+
].find((one) => one.pid === pid);
|
|
41696
|
+
if (game === undefined) {
|
|
41697
|
+
return this.createErrorResponse(`pid ${pid} is not a game announced for this project, so nothing was signalled. editor_status lists this project's games under runtimes. A game that announces no runtime is ended by the server holding its run, or with andChildren by a stop of the run that started it.`);
|
|
41698
|
+
}
|
|
41699
|
+
const read = game.announcedAt === undefined ? null : stillTheAnnouncedGame(pid, game.project.path, game.announcedAt);
|
|
41700
|
+
if (read === null) {
|
|
41701
|
+
return this.createErrorResponse(`pid ${pid} announced itself as a game of this project, but the operating system does not describe it as this project's engine run started before that announcement (the engine with --path on the project and no editor flag), so nothing was signalled. If it is still the game, runtime_invoke can ask it to quit.`);
|
|
41702
|
+
}
|
|
41703
|
+
const run = {
|
|
41704
|
+
pid,
|
|
41705
|
+
log: new GameLog,
|
|
41706
|
+
transcript: null,
|
|
41707
|
+
readOffset: 0,
|
|
41708
|
+
projectPath: game.project.path,
|
|
41709
|
+
startedAt: game.announcedAt ?? Date.now(),
|
|
41710
|
+
exitCode: null,
|
|
41711
|
+
exitSignal: null,
|
|
41712
|
+
throughEditor: false,
|
|
41713
|
+
brokeOn: null,
|
|
41714
|
+
announcedPid: pid
|
|
41715
|
+
};
|
|
41716
|
+
const children = andChildren ? await this.whatTheRunStarted(run) : null;
|
|
41717
|
+
let landed = true;
|
|
41718
|
+
try {
|
|
41719
|
+
process.kill(pid);
|
|
41720
|
+
} catch {
|
|
41721
|
+
landed = false;
|
|
41722
|
+
}
|
|
41723
|
+
const gone = await untilGone([pid]);
|
|
41724
|
+
const ended = children === null ? null : endChildrenAmong(children);
|
|
41725
|
+
if (gone) {
|
|
41726
|
+
await announcementEnded(pid);
|
|
41727
|
+
}
|
|
41728
|
+
return this.jsonTextResponse({
|
|
41729
|
+
stopped: true,
|
|
41730
|
+
through: "gdharness",
|
|
41731
|
+
endedPid: pid,
|
|
41732
|
+
...childrenFields(ended),
|
|
41733
|
+
exitedBeforeStop: !landed,
|
|
41734
|
+
note: `${landed ? `pid ${pid} was ended: a game of this project that no server here was holding,` : `pid ${pid} had already gone when it was signalled: a game of this project that no server here was holding,`} identified by its announcement and by its command line, ${read.executable} run with --path ${read.projectPath ?? ""}. Nothing here read what it printed or holds its exit code, so neither is in this answer.${gone ? "" : ` It had not exited ${STOP_WAIT_MS / 1000} seconds after being told to, so it may still be going: editor_status says whether it is.`} ${aboutTheChildren(ended, false)}`
|
|
41735
|
+
});
|
|
41736
|
+
}
|
|
41502
41737
|
async whatTheRunStarted(run) {
|
|
41503
41738
|
const tree = await processTree();
|
|
41504
41739
|
if (tree === undefined) {
|
|
@@ -42093,7 +42328,7 @@ var init_server3 = __esm(() => {
|
|
|
42093
42328
|
init_tool_definitions();
|
|
42094
42329
|
init_update_check();
|
|
42095
42330
|
run7 = promisify7(execFile7);
|
|
42096
|
-
__dirname2 =
|
|
42331
|
+
__dirname2 = dirname17(fileURLToPath6(import.meta.url));
|
|
42097
42332
|
CONSOLE_WRAPPER = /_console(\.exe)?$/i;
|
|
42098
42333
|
LONGEST_BOOT_NOTED_MS = 5 * 60000;
|
|
42099
42334
|
VALUE_NAMES = {
|
|
@@ -42135,7 +42370,7 @@ var init_server3 = __esm(() => {
|
|
|
42135
42370
|
|
|
42136
42371
|
// src/cli.ts
|
|
42137
42372
|
import { existsSync as existsSync16 } from "fs";
|
|
42138
|
-
import { join as join24, resolve as
|
|
42373
|
+
import { join as join24, resolve as resolve6 } from "path";
|
|
42139
42374
|
|
|
42140
42375
|
// src/bridge-announce.ts
|
|
42141
42376
|
init_errors();
|
|
@@ -43764,6 +43999,7 @@ that is running, and the project on disk. ${TOOL_SPECS.length} tools, named \`do
|
|
|
43764
43999
|
| Whether this engine has a method or property, and what it takes | \`editor_classes info\` with \`member\`, which answers that one member and which class declares it rather than the whole class |
|
|
43765
44000
|
| 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 |
|
|
43766
44001
|
| 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 |
|
|
44002
|
+
| End a game of the project that no server is holding | \`editor_run stop\` with its \`pid\` from \`editor_status\` \`runtimes\`, never a process kill by hand: it is ended only when it announced itself for the project and its command line is the project's engine run |
|
|
43767
44003
|
| Play a game without touching the player's saves | \`editor_run start\` with \`savesIn\`, a directory for this run's \`user://\`: the editor plays with its own environment, so a run given one is started by the server, and the answer says under \`savesNote\` when the platform ignores it |
|
|
43768
44004
|
| A picture, for a person who asked to see one | \`runtime_capture\` |
|
|
43769
44005
|
|
|
@@ -43957,7 +44193,7 @@ class UsageError extends Refusal2 {
|
|
|
43957
44193
|
}
|
|
43958
44194
|
function projectArgument(at) {
|
|
43959
44195
|
const named = args.slice(at).find((value) => !value.startsWith("--"));
|
|
43960
|
-
const projectPath =
|
|
44196
|
+
const projectPath = resolve6(named ?? ".");
|
|
43961
44197
|
if (!existsSync16(join24(projectPath, "project.godot"))) {
|
|
43962
44198
|
throw new UsageError(`Not a Godot project: ${projectPath} holds no project.godot.`);
|
|
43963
44199
|
}
|