gdharness 1.3.66 → 1.3.67
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/README.md +1 -1
- package/build/cli.js +362 -135
- package/build/godot/addons/gdharness_editor/bridge_client.gd +13 -2
- package/build/godot/addons/gdharness_editor/picture_saves.gd +48 -0
- package/build/godot/addons/gdharness_editor/picture_saves.gd.uid +1 -0
- package/build/godot/addons/gdharness_editor/plugin.gd +4 -1
- package/build/godot/addons/gdharness_editor/scene_file.gd +5 -0
- package/build/godot/addons/gdharness_editor/tool_executor.gd +15 -1
- package/build/godot/addons/gdharness_editor/tools/scene_placing.gd +8 -6
- package/build/godot/addons/gdharness_editor/tools/scene_tools.gd +4 -1
- package/build/godot/addons/gdharness_editor/tools/view_tools.gd +121 -0
- package/build/godot/addons/gdharness_editor/tools/view_tools.gd.uid +1 -0
- package/build/godot/addons/gdharness_runtime/picture_saves.gd +48 -0
- package/build/godot/addons/gdharness_runtime/picture_saves.gd.uid +1 -0
- package/build/godot/addons/gdharness_runtime/runtime_capture.gd +3 -43
- package/build/godot/operations/gdscript_analysis.gd +17 -0
- package/build/godot/operations/picture_saves.gd +48 -0
- package/package.json +1 -1
package/build/cli.js
CHANGED
|
@@ -2003,9 +2003,6 @@ function nonEmpty(value) {
|
|
|
2003
2003
|
function keptSlotsOf(count) {
|
|
2004
2004
|
return Math.max(2, Math.floor(count / 4));
|
|
2005
2005
|
}
|
|
2006
|
-
function reservedSlotOf(count) {
|
|
2007
|
-
return count - Math.max(1, count - keptSlotsOf(count)) >= 2 ? count - 1 : null;
|
|
2008
|
-
}
|
|
2009
2006
|
function slotWaiters(directory) {
|
|
2010
2007
|
const marks = join6(directory, WAITING_FOLDER);
|
|
2011
2008
|
let names;
|
|
@@ -2072,8 +2069,20 @@ class SlotLine {
|
|
|
2072
2069
|
return { ...holder, ...this.game ? { game: true } : {}, taker: this.taker, since: this.since / 1000 };
|
|
2073
2070
|
}
|
|
2074
2071
|
ahead(waiters) {
|
|
2075
|
-
const
|
|
2076
|
-
|
|
2072
|
+
const now = this.clock();
|
|
2073
|
+
const young = (taker, game, since) => taker === "other" && (game || now - since < ONE_OFF_AGED_MS) ? 1 : 0;
|
|
2074
|
+
const own = {
|
|
2075
|
+
group: young(this.taker, this.game, this.since),
|
|
2076
|
+
since: this.since,
|
|
2077
|
+
mark: this.mark ?? ""
|
|
2078
|
+
};
|
|
2079
|
+
const placeOf = (one) => ({
|
|
2080
|
+
group: young(one.taker, one.game, one.since),
|
|
2081
|
+
since: one.since,
|
|
2082
|
+
mark: one.mark
|
|
2083
|
+
});
|
|
2084
|
+
const before = (a, b) => a.group - b.group || a.since - b.since || (a.mark < b.mark ? -1 : a.mark > b.mark ? 1 : 0);
|
|
2085
|
+
return waiters.filter((one) => one.mark !== this.mark && before(placeOf(one), own) < 0).toSorted((a, b) => before(placeOf(a), placeOf(b)));
|
|
2077
2086
|
}
|
|
2078
2087
|
admitted(numbers, theirs) {
|
|
2079
2088
|
const now = this.clock();
|
|
@@ -2085,7 +2094,7 @@ class SlotLine {
|
|
|
2085
2094
|
}
|
|
2086
2095
|
}
|
|
2087
2096
|
function gamesKeptOf(kept) {
|
|
2088
|
-
return Math.max(0, Math.min(Math.floor(kept / 2), kept -
|
|
2097
|
+
return Math.max(0, Math.min(Math.floor(kept / 2), kept - 1));
|
|
2089
2098
|
}
|
|
2090
2099
|
function slotsDrawnOn(count, taker, queue) {
|
|
2091
2100
|
const gated = Math.max(1, count - keptSlotsOf(count));
|
|
@@ -2094,14 +2103,35 @@ function slotsDrawnOn(count, taker, queue) {
|
|
|
2094
2103
|
}
|
|
2095
2104
|
const every = Array.from({ length: count }, (_, slot) => count - 1 - slot);
|
|
2096
2105
|
const kept = every.slice(0, count - gated);
|
|
2106
|
+
if (taker === "tier") {
|
|
2107
|
+
return { numbers: [...every.slice(count - gated), ...kept] };
|
|
2108
|
+
}
|
|
2097
2109
|
if (taker === "timing" || queue === "none" || kept.length === 0) {
|
|
2098
2110
|
return { numbers: every };
|
|
2099
2111
|
}
|
|
2100
|
-
if (
|
|
2101
|
-
|
|
2102
|
-
return { numbers: [...kept].reverse().filter((slot) => slot !== reserve), limit: "tier" };
|
|
2112
|
+
if (queue === "spare") {
|
|
2113
|
+
return { numbers: [...kept, ...every.slice(count - gated)] };
|
|
2103
2114
|
}
|
|
2104
|
-
return { numbers: kept
|
|
2115
|
+
return { numbers: kept, limit: "kept" };
|
|
2116
|
+
}
|
|
2117
|
+
function spareSaid(content, now) {
|
|
2118
|
+
let said;
|
|
2119
|
+
try {
|
|
2120
|
+
said = JSON.parse(content ?? "");
|
|
2121
|
+
} catch {
|
|
2122
|
+
return 0;
|
|
2123
|
+
}
|
|
2124
|
+
if (!isRecord(said)) {
|
|
2125
|
+
return 0;
|
|
2126
|
+
}
|
|
2127
|
+
const { spare, written } = said;
|
|
2128
|
+
if (typeof spare !== "number" || !Number.isInteger(spare) || typeof written !== "number") {
|
|
2129
|
+
return 0;
|
|
2130
|
+
}
|
|
2131
|
+
return now - written > SPARE_STALE_MS ? 0 : Math.max(0, spare);
|
|
2132
|
+
}
|
|
2133
|
+
function forAGame(queue, game) {
|
|
2134
|
+
return game && queue === "spare" ? "queued" : queue;
|
|
2105
2135
|
}
|
|
2106
2136
|
function inAGate(env = process4.env) {
|
|
2107
2137
|
return nonEmpty(env[SHARE_VARIABLE]) !== undefined && nonEmpty(env[SHARE_COUNT_VARIABLE]) !== undefined;
|
|
@@ -2294,10 +2324,8 @@ class EngineSlots {
|
|
|
2294
2324
|
const aged = taker === "other" && !game && waitedMs >= ONE_OFF_AGED_MS;
|
|
2295
2325
|
let queue;
|
|
2296
2326
|
const queued = async () => queue ??= await this.gateQueue();
|
|
2297
|
-
this.drawn = slotsDrawnOn(this.count, taker, taker === "
|
|
2327
|
+
this.drawn = slotsDrawnOn(this.count, taker, taker === "other" && !aged ? forAGame(await queued(), game) : "none");
|
|
2298
2328
|
if (game && this.drawn.limit === "kept") {
|
|
2299
|
-
const reserve = reservedSlotOf(this.count);
|
|
2300
|
-
this.drawn = { ...this.drawn, numbers: this.drawn.numbers.filter((slot) => slot !== reserve) };
|
|
2301
2329
|
const kept = this.count - Math.max(1, this.count - keptSlotsOf(this.count));
|
|
2302
2330
|
const games = await this.heldByGames(Array.from({ length: kept }, (_, at) => this.count - kept + at));
|
|
2303
2331
|
if (games.length >= gamesKeptOf(kept)) {
|
|
@@ -2353,12 +2381,11 @@ class EngineSlots {
|
|
|
2353
2381
|
return new Map;
|
|
2354
2382
|
}
|
|
2355
2383
|
const now = Date.now();
|
|
2356
|
-
const reserve = reservedSlotOf(this.count);
|
|
2357
2384
|
const drawn = [];
|
|
2358
2385
|
for (const one of ahead) {
|
|
2359
2386
|
const aged = one.taker === "other" && !one.game && now - one.since >= ONE_OFF_AGED_MS;
|
|
2360
|
-
const theirs = slotsDrawnOn(this.count, one.taker, one.taker === "
|
|
2361
|
-
drawn.push(
|
|
2387
|
+
const theirs = slotsDrawnOn(this.count, one.taker, one.taker === "other" && !aged ? forAGame(await queued(), one.game) : "none");
|
|
2388
|
+
drawn.push(theirs.numbers);
|
|
2362
2389
|
}
|
|
2363
2390
|
return new Map([...slotsClaimed(drawn, open)].map(([slot, at]) => [slot, ahead[at]?.mark ?? ""]));
|
|
2364
2391
|
}
|
|
@@ -2433,7 +2460,12 @@ class EngineSlots {
|
|
|
2433
2460
|
live = "queued";
|
|
2434
2461
|
}
|
|
2435
2462
|
}
|
|
2436
|
-
return live;
|
|
2463
|
+
return live === "queued" && await this.spare() > 0 ? "spare" : live;
|
|
2464
|
+
}
|
|
2465
|
+
async spare() {
|
|
2466
|
+
const path = join6(this.directory, SPARE_FILE);
|
|
2467
|
+
const said = spareSaid(readWhole(path), Date.now());
|
|
2468
|
+
return said > 0 && !await this.free(path) ? said : 0;
|
|
2437
2469
|
}
|
|
2438
2470
|
holders() {
|
|
2439
2471
|
const found = [];
|
|
@@ -2654,7 +2686,7 @@ function slotWaitSaid(waitedMs, count, directory, holders = [], drawn = { number
|
|
|
2654
2686
|
const named = holdersNamed(taking, holders);
|
|
2655
2687
|
const waiting = `${so}: ${slots}`;
|
|
2656
2688
|
const back = `the engine starts when ${taking === 1 ? "it" : "one"} is given back`;
|
|
2657
|
-
const starts = drawn.limit === "kept" || drawn.limit === "
|
|
2689
|
+
const starts = drawn.limit === "kept" || drawn.limit === "games" ? `${back} or no gate is queued` : back;
|
|
2658
2690
|
const said = named.length === 0 ? `${waiting}, by a gate or another tool, and ${starts}.` : taking === 1 ? `${waiting}, by ${named.join(", ")}, and ${starts}.` : `${waiting}: ${listed(named)}. ${starts.charAt(0).toUpperCase()}${starts.slice(1)}.`;
|
|
2659
2691
|
return `${said}${leftSaid(drawn)}`;
|
|
2660
2692
|
}
|
|
@@ -2674,9 +2706,6 @@ function slotsSaid(taking, count, directory, limit) {
|
|
|
2674
2706
|
if (limit === "gate") {
|
|
2675
2707
|
return `${taking === 1 ? "the one engine slot" : `the ${String(taking)} engine slots`} of the ${String(count)} ${where} a gate's engines draw on`;
|
|
2676
2708
|
}
|
|
2677
|
-
if (limit === "tier") {
|
|
2678
|
-
return `${taking === 1 ? "the one engine slot" : `the ${String(taking)} engine slots`} of the ${String(count)} ${where} a gate's own steps take while a gate is queued`;
|
|
2679
|
-
}
|
|
2680
2709
|
if (limit === "games") {
|
|
2681
2710
|
return `${taking === 1 ? "the one engine slot" : `the ${String(taking)} engine slots`} of the ${String(count)} ${where} that games editor_run started may hold while a gate is queued, the rest kept for short calls,`;
|
|
2682
2711
|
}
|
|
@@ -2743,7 +2772,7 @@ async function inASlot(body) {
|
|
|
2743
2772
|
held.release();
|
|
2744
2773
|
}
|
|
2745
2774
|
}
|
|
2746
|
-
var HELD_VARIABLE = "GODOT_ENGINE_HELD", SLOTS_VARIABLE = "GODOT_ENGINE_SLOTS", SLOT_COUNT_VARIABLE = "GODOT_ENGINE_SLOT_COUNT", PROCESSORS_A_SLOT = 2, START_TOLERANCE_MS = 2000, UNWRITTEN_MS = 1e4, SLOT_LOOK_MS = 1000, SLOT_SETTLE_MS = 50, WAIT_SAID_MS = 20000, COMMAND_WAIT_SAID_MS = 2000, START_READ_MS = 1e4, WAITING_FOLDER = "waiting", QUEUED_FOLDER = "queued", SHARE_VARIABLE = "GODOT_ENGINE_SHARE", SHARE_COUNT_VARIABLE = "GODOT_ENGINE_SHARE_COUNT",
|
|
2775
|
+
var HELD_VARIABLE = "GODOT_ENGINE_HELD", SLOTS_VARIABLE = "GODOT_ENGINE_SLOTS", SLOT_COUNT_VARIABLE = "GODOT_ENGINE_SLOT_COUNT", PROCESSORS_A_SLOT = 2, START_TOLERANCE_MS = 2000, UNWRITTEN_MS = 1e4, SLOT_LOOK_MS = 1000, SLOT_SETTLE_MS = 50, WAIT_SAID_MS = 20000, COMMAND_WAIT_SAID_MS = 2000, START_READ_MS = 1e4, WAITING_FOLDER = "waiting", QUEUED_FOLDER = "queued", SHARE_VARIABLE = "GODOT_ENGINE_SHARE", SHARE_COUNT_VARIABLE = "GODOT_ENGINE_SHARE_COUNT", ONE_OFF_AGED_MS = 120000, SLOT_YIELD_MS = 5000, MEMORY_TAKER = "memory", TIMED_VARIABLE = "GODOT_ENGINE_TIMED", TIER_VARIABLE = "GODOT_ENGINE_TIER", SEATED_SUFFIX = ".seated", SPARE_FILE = "spare.json", SPARE_STALE_MS = 1e4, waitsMarked = 0, ENGINE_PID_LOOK_MS = 100, ours, saidTo, takenBy;
|
|
2747
2776
|
var init_engine_slots = __esm(() => {
|
|
2748
2777
|
init_alive();
|
|
2749
2778
|
init_cache_home();
|
|
@@ -13081,13 +13110,17 @@ function crashIn(lines) {
|
|
|
13081
13110
|
const at = trimmed.findIndex((line) => CRASH_HEADER.test(line));
|
|
13082
13111
|
const said = trimmed.some((line) => line.includes(QUEUE_OVERFLOW_LINE));
|
|
13083
13112
|
if (at === -1) {
|
|
13084
|
-
return said ? { signal:
|
|
13113
|
+
return said ? { reported: false, signal: undefined, overflowed: true } : null;
|
|
13085
13114
|
}
|
|
13086
13115
|
const report = trimmed.slice(at);
|
|
13087
13116
|
const build = report.map((line) => CRASH_BUILD.exec(line)?.[1]).find((commit) => commit !== undefined);
|
|
13088
13117
|
const frames = build === undefined ? undefined : QUEUE_OVERFLOW_FRAMES.get(build);
|
|
13089
13118
|
const framed = frames?.every((frame) => report.some((line) => line.includes(`(${frame})`))) === true;
|
|
13090
|
-
return { signal: CRASH_HEADER.exec(trimmed[at] ?? "")?.[1]
|
|
13119
|
+
return { reported: true, signal: CRASH_HEADER.exec(trimmed[at] ?? "")?.[1], overflowed: said || framed };
|
|
13120
|
+
}
|
|
13121
|
+
function crashSaid(crashed, during) {
|
|
13122
|
+
const signal = crashed.signal === undefined ? "" : `, its crash handler reporting signal ${crashed.signal}`;
|
|
13123
|
+
return `the engine crashed${during}${signal}`;
|
|
13091
13124
|
}
|
|
13092
13125
|
function whileRunning(running) {
|
|
13093
13126
|
if (running === null) {
|
|
@@ -13115,10 +13148,10 @@ function judgeEngine(printed, handed, end) {
|
|
|
13115
13148
|
faults.push(end.ended);
|
|
13116
13149
|
} else if (end.code !== 0 && !explained) {
|
|
13117
13150
|
const free = end.freeBytes === undefined ? "" : `, with ${(end.freeBytes / 1024 ** 3).toFixed(1)} GB of memory free as it ended`;
|
|
13118
|
-
faults.push(crashed
|
|
13151
|
+
faults.push(crashed?.reported === true ? `${crashSaid(crashed, whileRunning(printed.running))}${free}` : end.code === null ? `the engine was ended by ${end.signal ?? "a signal"}${free}` : `the engine exited ${exitNamed(end.code)}${free}`);
|
|
13119
13152
|
}
|
|
13120
13153
|
if (crashed?.overflowed === true) {
|
|
13121
|
-
faults.push(`the engine's call queue overflowed${whileRunning(printed.running)}: something queued deferred calls without end within one frame, most often a layout that changes the size it is laid out from in a deferred handler, so the frame never ends${crashed.
|
|
13154
|
+
faults.push(`the engine's call queue overflowed${whileRunning(printed.running)}: something queued deferred calls without end within one frame, most often a layout that changes the size it is laid out from in a deferred handler, so the frame never ends${crashed.reported ? ", and the engine crashed reporting the overflow" : ""}`);
|
|
13122
13155
|
}
|
|
13123
13156
|
if (printed.lines.every((line) => line.trim() === "")) {
|
|
13124
13157
|
faults.push("the engine printed nothing at all");
|
|
@@ -13250,7 +13283,7 @@ function judgeCompile(printed, end) {
|
|
|
13250
13283
|
if (end.ended !== null) {
|
|
13251
13284
|
faults.push(end.ended);
|
|
13252
13285
|
} else if (end.code !== 0) {
|
|
13253
|
-
faults.push(crashed
|
|
13286
|
+
faults.push(crashed?.reported === true ? crashSaid(crashed, "") : end.code === null ? `the engine was ended by ${end.signal ?? "a signal"}` : `the engine exited ${exitNamed(end.code)}`);
|
|
13254
13287
|
}
|
|
13255
13288
|
if (!printed.lines.some((line) => line.trim() === COMPILED_SAID)) {
|
|
13256
13289
|
faults.push("it never said it had compiled them");
|
|
@@ -13390,7 +13423,7 @@ var init_suite_judge = __esm(() => {
|
|
|
13390
13423
|
[105, "gdUnit4's code for scripts that did not compile"]
|
|
13391
13424
|
]);
|
|
13392
13425
|
GODOT_EXITS = new Set([1, 255]);
|
|
13393
|
-
CRASH_HEADER = /^(?:CrashHandlerException|handle_crash): Program crashed with signal (\d+)
|
|
13426
|
+
CRASH_HEADER = /^(?:CrashHandlerException|handle_crash): Program crashed(?: with signal (\d+))?$/;
|
|
13394
13427
|
CRASH_BUILD = /^Engine version: .*\(([0-9a-f]{40})\)$/;
|
|
13395
13428
|
QUEUE_OVERFLOW_FRAMES = new Map([
|
|
13396
13429
|
["ed1daf0bf001b61586d9930840f2f1394092c079", ["main+3e1082a", "main+3e0f4aa"]]
|
|
@@ -13969,11 +14002,14 @@ function runArguments(options) {
|
|
|
13969
14002
|
}
|
|
13970
14003
|
return args;
|
|
13971
14004
|
}
|
|
13972
|
-
function editorArguments(projectPath, ports, logFile, hidden, silent) {
|
|
14005
|
+
function editorArguments(projectPath, ports, logFile, hidden, silent, platform = process.platform) {
|
|
14006
|
+
const headless = hidden && platform !== "win32";
|
|
14007
|
+
const windowed = hidden && !headless;
|
|
13973
14008
|
return [
|
|
13974
14009
|
"-e",
|
|
13975
|
-
...
|
|
13976
|
-
...
|
|
14010
|
+
...headless ? ["--headless"] : [],
|
|
14011
|
+
...windowed ? hiddenWindowFlags([], false) : [],
|
|
14012
|
+
...(silent || hidden) && !headless ? ["--audio-driver", "Dummy"] : [],
|
|
13977
14013
|
"--path",
|
|
13978
14014
|
projectPath,
|
|
13979
14015
|
"--lsp-port",
|
|
@@ -13981,12 +14017,13 @@ function editorArguments(projectPath, ports, logFile, hidden, silent) {
|
|
|
13981
14017
|
"--dap-port",
|
|
13982
14018
|
String(ports.dap),
|
|
13983
14019
|
"--log-file",
|
|
13984
|
-
logFile
|
|
14020
|
+
logFile,
|
|
14021
|
+
...windowed ? ["--", HIDDEN_EDITOR_MARK] : []
|
|
13985
14022
|
];
|
|
13986
14023
|
}
|
|
13987
14024
|
function startedAs(commandLine) {
|
|
13988
14025
|
const words = commandLineWords(commandLine);
|
|
13989
|
-
const hidden = words.includes("--headless");
|
|
14026
|
+
const hidden = words.includes("--headless") || words.includes(HIDDEN_EDITOR_MARK);
|
|
13990
14027
|
const driver = words.indexOf("--audio-driver");
|
|
13991
14028
|
return { hidden, silent: hidden || driver >= 0 && words[driver + 1] === "Dummy" };
|
|
13992
14029
|
}
|
|
@@ -14035,7 +14072,7 @@ function environmentFor2(options, variables = process.env) {
|
|
|
14035
14072
|
const base = options.savesIn === undefined ? { ...variables } : userDataIn(options.savesIn, variables);
|
|
14036
14073
|
return { ...base, ...options.env, GDHARNESS_RUNTIME_DIR: options.runtimeDirectory };
|
|
14037
14074
|
}
|
|
14038
|
-
var HIDDEN_WINDOW_FPS = 60, ENGINE_FLAGS, VALUE_KIND, SET_BY_ARGUMENT, OPENED_BY_A_SERVER = "GDHARNESS_OPENED_BY_A_SERVER", EDITOR_PID_VARIABLE = "GDHARNESS_EDITOR_PID", RESERVED_VARIABLE_PREFIX = "GDHARNESS_", SAVES_NOT_MOVED_NOTE = "On macOS the engine reads user:// off HOME and ignores the variables that move it elsewhere, measured on Godot 4.7.2, so this run wrote its saves where the player keeps theirs. A suite that saves a game has written into the same folder as the copy being played, and one that tidies up after itself may have removed real saves.";
|
|
14075
|
+
var HIDDEN_WINDOW_FPS = 60, ENGINE_FLAGS, VALUE_KIND, SET_BY_ARGUMENT, HIDDEN_EDITOR_MARK = "--gdharness-hidden", OPENED_BY_A_SERVER = "GDHARNESS_OPENED_BY_A_SERVER", EDITOR_PID_VARIABLE = "GDHARNESS_EDITOR_PID", RESERVED_VARIABLE_PREFIX = "GDHARNESS_", SAVES_NOT_MOVED_NOTE = "On macOS the engine reads user:// off HOME and ignores the variables that move it elsewhere, measured on Godot 4.7.2, so this run wrote its saves where the player keeps theirs. A suite that saves a game has written into the same folder as the copy being played, and one that tidies up after itself may have removed real saves.";
|
|
14039
14076
|
var init_launch = __esm(() => {
|
|
14040
14077
|
ENGINE_FLAGS = Object.freeze({
|
|
14041
14078
|
"--verbose": "none",
|
|
@@ -14452,7 +14489,7 @@ function answersTo(entry, severity, contains) {
|
|
|
14452
14489
|
var HEADLINE, CRASH, GDUNIT_CASE, CRASH_FILLER, COLOUR_CODE, STREAMS, ADMITTED, LOCATION2, FRAME, LONGEST_CYCLE = 64;
|
|
14453
14490
|
var init_game_log = __esm(() => {
|
|
14454
14491
|
HEADLINE = /^(USER )?(SCRIPT ERROR|SHADER ERROR|ERROR|WARNING):\s?(.*)$/;
|
|
14455
|
-
CRASH = /^(?:CrashHandlerException|handle_crash): Program crashed with signal \d
|
|
14492
|
+
CRASH = /^(?:CrashHandlerException|handle_crash): Program crashed(?: with signal \d+)?\s*$/;
|
|
14456
14493
|
GDUNIT_CASE = /^\s+res:\/\/.+?\.(?:gd|cs) > .+? (?:STARTED|PASSED|FAILED|ERROR|SKIPPED|FLAKY)(?:\s|$)/;
|
|
14457
14494
|
CRASH_FILLER = /^(?:=+|-- END OF .* BACKTRACE --|\[\d+\] .* - no debug info in .*)$/;
|
|
14458
14495
|
COLOUR_CODE = new RegExp(`${String.fromCharCode(27)}\\[[0-9;]*m`, "g");
|
|
@@ -15475,6 +15512,7 @@ var init_scratch = __esm(() => {
|
|
|
15475
15512
|
KINDS = [
|
|
15476
15513
|
"answers",
|
|
15477
15514
|
"desktop",
|
|
15515
|
+
"editor-capture",
|
|
15478
15516
|
"export",
|
|
15479
15517
|
"import",
|
|
15480
15518
|
"params",
|
|
@@ -19461,19 +19499,19 @@ var init_tool_definitions = __esm(() => {
|
|
|
19461
19499
|
{
|
|
19462
19500
|
name: "editor_launch",
|
|
19463
19501
|
title: "Open, restart or close the editor, or bind its debug adapter again",
|
|
19464
|
-
description: "Opens the Godot editor on a project, or restarts or closes the one already connected, or binds its debug adapter again. close saves open scenes, quits the editor and waits for its process to go, which is the only way to end a hidden one short of killing it. An editor this server opened that has not connected yet has nothing of this server's to save, so close stops its process instead. open answers once the process has started, and the editor dials in after its import. A call needing the editor made before then waits for the one this server opened, for as long as a restart waits, and is refused naming it if it has not arrived. open refuses while an editor of the project is already running and has not reached this server, naming its pid, rather than putting a second on the project. The editor is an ordinary visible one on the desktop in use, and on Windows it is shown without being activated, so opening or reopening it does not take the keyboard from whatever the user is typing in. hidden: true opens it
|
|
19502
|
+
description: "Opens the Godot editor on a project, or restarts or closes the one already connected, or binds its debug adapter again. close saves open scenes, quits the editor and waits for its process to go, which is the only way to end a hidden one short of killing it. An editor this server opened that has not connected yet has nothing of this server's to save, so close stops its process instead. open answers once the process has started, and the editor dials in after its import. A call needing the editor made before then waits for the one this server opened, for as long as a restart waits, and is refused naming it if it has not arrived. open refuses while an editor of the project is already running and has not reached this server, naming its pid, rather than putting a second on the project. The editor is an ordinary visible one on the desktop in use, and on Windows it is shown without being activated, so opening or reopening it does not take the keyboard from whatever the user is typing in. hidden: true opens it out of sight instead. On Windows it gets a desktop of its own, shared by the games it plays, and still draws its views for editor_capture. On Linux and macOS it is headless, so editor_capture refuses it. A hidden editor serves the bridge, the language server and the debug adapter as a visible one does. A game a visible editor plays opens as a window on the desktop in use. An editor goes on serving the addon it read at startup, so restart is what puts a gdharness upgrade into effect, and nothing else here restarts one. It saves open scenes on the way out. An editor a server opened is started again by the server, hidden or visible and silent or not as it was started unless hidden or silent says otherwise, once the old one has gone: it waits 90 seconds for that, or GDHARNESS_EDITOR_RESTART_TIMEOUT_MS, and an old editor still running after it is not replaced, since the new one would find its ports held. One opened by hand restarts itself, which only one with a window can do, because the engine hands back none of the arguments it was started with. Godot tries each server port once, as the editor starts, so a restart binds a debug adapter that came back without its port again, in the editor that came back. rebind does the same for the connected editor without restarting it, whoever opened it. A debug session open on the adapter ends with it and is opened again by the next debug call. editor_status says which editor is connected and whether it is holding an old addon. Opening a project is a save and so is closing one, which is the part that is not obvious from outside: the editor imports the project on open and writes project.godot back, and Godot writes only what differs from its own defaults, so a key named deliberately at its default value is dropped either way. A restart of an editor this server opened is a quit and then a launch, and this server can be ended between them, usually by the reconnect the restart was being taken for: the editor is gone and nothing replaces it. That is written down before the quit, so the next server says so in editor_status rather than waiting for an editor that is not coming, and open finishes it. An open is refused when the editor it would start could never reach this server. Those are a project other than the one this server serves (its editor dials that project's own server), a project without the gdharness editor addon installed and enabled, and a project whose editor a restart is already bringing back.",
|
|
19465
19503
|
answer: "close answers settingsDropped, naming what its save took out of project.godot and left out when it took nothing, and settingsNote when the file could not be read to tell. An editor stopped before it connected is answered with beforeConnecting. close and restart name under savedScenes the open scenes that had unsaved changes and were saved on the way out, an empty list when none had. A scene that would not save refuses the close or restart and leaves the editor running. Both answer tookMs, how long the editor took to go or to come back. restart answers with the version that came back, and hidden, whether it came back with no window, and says under settingsDropped which keys its save dropped, because it waits for the editor to return. An open cannot, since it answers as soon as the process exists and the save happens during the import minutes later, so editor_status carries that reading once the editor has connected. Nothing else will say a key went until something depends on one. A restart answers lspListening and dapListening once the editor's servers are up. When either came back without its port, serversSaid gives the lines the editor's log panel has about its servers, the one place Godot says why a port would not bind, and when it was the debug adapter, which is bound again in the editor that came back, debugAdapterRebound says whether that took. Neither is there when both came up. Godot clears that panel whenever a game is played, unless its run/output/always_clear_output_on_play setting is off, so after a play the lines from the editor's own start are gone. rebind answers rebound, with lspListening, dapListening and serversSaid, the lines that rebind made the panel print. A restart that a server's end interrupted shows as restartInterrupted in editor_status, and the open that finishes it says so under finishesRestart.",
|
|
19466
19504
|
parameters: {
|
|
19467
19505
|
projectPath: { ...PROJECT_PATH, ops: ["open"] },
|
|
19468
19506
|
hidden: {
|
|
19469
19507
|
type: "boolean",
|
|
19470
19508
|
ops: ["open", "restart"],
|
|
19471
|
-
description: "Open the editor
|
|
19509
|
+
description: "Open the editor out of sight, with the games it plays: on a desktop of its own on Windows, and headless elsewhere. Default false on open: one visible editor, shown without taking the focus. On restart it applies only to an editor a server opened. The default there is how the replaced editor was started, read from its process. One whose start cannot be read is left running and refused until hidden is passed. One opened by hand comes back as it was, so hidden is refused there."
|
|
19472
19510
|
},
|
|
19473
19511
|
silent: {
|
|
19474
19512
|
type: "boolean",
|
|
19475
19513
|
ops: ["open", "restart"],
|
|
19476
|
-
description: "Open the editor with no sound out of the machine, started with --audio-driver Dummy, so a tool script, a preview or a scene playing in its viewport is heard by nobody. Default true on open, and on restart how the editor being replaced was started: pass false to hear it. The answer's arguments are what the editor was started with. The editor hands no audio driver to a game it plays, so editor_run's own silent decides that game's sound. A hidden editor is
|
|
19514
|
+
description: "Open the editor with no sound out of the machine, started with --audio-driver Dummy, so a tool script, a preview or a scene playing in its viewport is heard by nobody. Default true on open, and on restart how the editor being replaced was started: pass false to hear it. The answer's arguments are what the editor was started with. The editor hands no audio driver to a game it plays, so editor_run's own silent decides that game's sound. A hidden editor is silent whatever silent says. A headless editor has no sound to turn off, so its arguments show no --audio-driver. On restart only for an editor a server opened. One opened by hand comes back as it was."
|
|
19477
19515
|
}
|
|
19478
19516
|
},
|
|
19479
19517
|
requires: [],
|
|
@@ -19714,7 +19752,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
19714
19752
|
title: "Editor status",
|
|
19715
19753
|
effect: "read",
|
|
19716
19754
|
description: "Whether the editor addon is connected, which Godot answers, whether the editor is playing something, and which games with the runtime addon are reachable, of this project and of every other on the machine. Read connected with mayYetConnect: the editor dials this server and backs off between tries, so for the first half-minute after a server starts false means not reached yet as often as no editor. mayYetConnect true is worth waiting out, false is final, and editorCannotConnect, restartInterrupted, restartUnderway, closedHere and editorsNotYetConnected say why. An editor that has connected and not yet greeted reports nothing about itself, and every other call made in that window waits up to ten seconds for it to settle. playingInEditor null means unanswered rather than nothing playing: processActive and runtimes do not depend on the editor answering. game.run names the run editor_output and editor_run stop answer for, and stays named after it ends. addonIsStale, on a connected editor and on each running game (absent with neither), says it runs addon code other than what this server ships, and staleNote says which half moves: the project, by a gdharness upgrade it spells out to run in a shell, the editor by a restart, or this server by a reconnect. A false editor.lspListening or editor.dapListening comes with serversNote: editor_launch rebind binds the debug adapter again in place, and editor_launch restart the language server. editor.scan says whether the editor is scanning, which editor_run waits out before starting a game. A game announced in a protocol this server does not speak is listed under unreadable, during an upgrade that moves the protocol, until the halves match.",
|
|
19717
|
-
answer: "projectIs, when present, is the gdharness version of the addons in the project, which differs from this server's (project_info says which of the two to move), and configIs, when present, the version the project's MCP config starts, which differs from this server's, so the next connection runs that one. server names this server: its pid, its version, and lifecycleLog, the file every gdharness server of the user appends a line to when it starts, when its client first names itself, when it replaces another server of its project, and when it ends, with why it ended (the client closing its end as stdin:end, a signal by name, superseded with the successor's pid, an uncaught exception or rejection with its stack, or the exit code) and the calls it was answering then. The parent's command line follows on its own line a few seconds after the start. A server killed without warning writes no ended line, and on Windows every kill is that, so a started line with no ended line after it is a server something killed. " + "runtimeConnected counts the games of the project this server serves on this release's runtime addon, and runtimes lists every game on the machine with ofThisProject saying which is which, since a game of another project is reached only by naming its projectPath, and userDir, the directory its user:// is, where its saves and settings are read. playingInEditor is null when the editor was not asked or did not answer, which is not the same as a no: with no editor connected there is nobody to ask, and an editor that does not serve the question is given a moment and then left. A game can be playing while this is null, so read it as unanswered rather than as nothing playing, and use processActive and runtimes, which come from elsewhere and do not depend on the editor answering. Its play says which play the editor is on: play.play counts the plays that editor has started since it opened, whoever started them, and play.editorPid is the editor, so two reads with different pairs are two plays even when both say playing. game.run names the run processActive is about, the one editor_output and editor_run stop answer for: its pid, its project, and adopted true when this server read it back from another server rather than starting it. It stays named after the run ends, beside processActive false, so editor_output can still read what that run printed. It is null when there is no run at all. startedAt says when it began, and endedAt when it ended, wherever something saw the end: a run adopted from a note another server wrote can have ended hours before this server started. editor.lspListening and editor.dapListening say whether anything accepts connections on the ports the editor names, lspPort and dapPort: Godot binds each once, as the editor starts, and reports a failure only in the log panel of the editor itself, so a false comes with serversNote: editor_launch rebind binds the debug adapter again in place, and editor_launch restart the language server. editor.scan says whether the editor is scanning or importing and how many milliseconds ago its last scan finished (finishedMsAgo), or is null when it did not answer: the editor rewrites the class cache just after a scan, and editor_run waits that out before starting a game, saying so under waitedForEditorScanMs. editor.openScenes lists the scenes open in the editor's tabs, leaving out a tab of a scene never saved, editor.editedScene the one it is showing (null when none is, or when that scene was never saved), and editor.unsavedScenes those with changes not yet written to disk, where an empty entry is a scene never saved. All three come with scan and are absent when it is null. Each game under runtimes is pinged with a short wait, so one held at a breakpoint is listed as unreachable rather than making this call wait out the runtime timeout. For the run this server is reporting on, heldAt says whether that is what it is, with the same three answers editor_output gives. A game the editor played announces the editor under editorPid, and so does whatever that game started for itself. A game some other server started announces none, which is how the two are told apart when both are running from one project. Each game also says which runtime addon it loaded, under addonVersion (null when it cannot say). addonIsStale is true when that code is not what this server ships, and staleNote says whether the project needs the gdharness upgrade command, spelled out to run in a shell, the game a restart, or this server a reconnect. A second checkout of a project, such as a git worktree, carries addons of its own and is where this shows. A runtime_* call to such a game is refused with the staleNote and not sent. When connected is false, mayYetConnect says whether that is final: the editor dials this server rather than the other way round and backs off between tries, so for the first half-minute after a server starts, false means \"not reached yet\" as often as it means \"no editor\". True is worth waiting out. False is an editor that is not there. False beside editorCannotConnect is a project without the gdharness editor addon installed or enabled, where no editor can ever dial in, however young the server or whichever editor is open. Its remedies say how to install or enable it. False beside restartInterrupted is one that an editor_launch restart on a previous server asked to quit, where the launch half never ran because that server was ended in between, usually by the reconnect the restart was taken for. editor_launch open finishes it. True beside restartUnderway is a restart still in progress, its server alive and within the wait for the old editor to go: the editor is coming, and opening another would put two on one project. listeningSince is when this server took the bridge port, which is what that window is measured from and what a caller wanting its own window should read. An editor this server opened counts for as long as its process lives, however far past that window the import runs, and awaitingLaunchedEditor names its pid so the two reasons for true are told apart. Any editor of this project running and not yet connected, whoever opened it, makes it true as well and is named under editorsNotYetConnected with its pid and start time, so a restart a reconnect interrupted after its launch is not read as nothing being there. settingsDropped appears here once when the editor this server opened saved project.godot on its way in and took a key out of it, which is the same reading editor_launch restart makes in its own answer. A game that has announced itself in a protocol this server does not speak is listed under unreadable rather than left out, with the protocol it speaks and whether the server or the addon is the older half. This happens during an upgrade that moves the runtime protocol, which most do not: installing replaces the addon on disk as soon as the pin moves, while a server that is already running stays on the version it started with, so the two speak different protocols until the server is reconnected. Those games are real and running, but no tool here can reach them until the halves match, so runtimes stays empty and runtimeConnected stays false. An upgrade that leaves the protocol alone opens no such window and this list stays empty through it. addonIsStale is decided by the code of the editor addons rather than their version, so an editor started before an upgrade that left that code alone reports the older addonVersion, is not stale, and addonNote says so: it needs no restart. A stale editor is asked nothing but to quit: every other call to it is refused with staleNote, which names editor_launch restart, the upgrade command or a reconnect. port is the one the editor bridge is on. When that is not the one this server was configured with, portWanted names the configured one and portNote says why it moved and, when it was the server this one replaced, which pid kept it. A server replacing another of its project that holds the configured port waits for it to stand down and takes the port over, which takes a few seconds. bridgeHandover says so while it waits, and it moves only if that server has not let go within the wait. An editor that has connected and not yet greeted, which lasts a frame and longer while it imports, reports nothing about itself: greeting says so, addonIsStale is absent rather than guessed, and a restart asked then waits for the greeting to learn who opened it. startedByAnEditor is an editor another editor started, by Godot's own restart or the project manager, which has none of the arguments a server gave the one before it. startedByAnEditorNote says what that costs. An engine run that loads the editor addon to do one job and quit, such as a gate's godot --headless --import, an export or a script, is not taken as the editor: a process that greets without a server's mark has its command line read first, and from the socket opening until its greeting and that read are done, identifying is true and connected false. One started with such an option is turned away, and turnedAway names the last one, its pid and the option. Every other call made while an editor is arriving, before its greeting or while it is looked up, waits up to ten seconds for it to settle rather than acting on connected as it reads in that window. serverVersion is the gdharness version of this server. bridgeAvailable says the editor bridge is listening. When it failed to start, startupError gives the error, and retryingBridge is true while this server tries again. announcedAt is the file where this server told editors of its project which port to dial. connectedAt is when the editor connected, and lastPongAt when it last answered this server's keep-alive ping. addonDigest is a hash of the editor addon's code, which addonIsStale compares with what this server ships. debugPort is the port of the editor's remote debugger, the one games it plays connect to. openedByAServer says a gdharness server started this editor, syncsBreakpoints that it keeps its breakpoints when a debug session opens, which Godot otherwise clears, and hidden that it runs headless with no window anybody can see. pendingRequests counts the requests sent to the editor and not yet answered, and queuedResources the files with writes waiting behind another write to the same file.",
|
|
19755
|
+
answer: "projectIs, when present, is the gdharness version of the addons in the project, which differs from this server's (project_info says which of the two to move), and configIs, when present, the version the project's MCP config starts, which differs from this server's, so the next connection runs that one. server names this server: its pid, its version, and lifecycleLog, the file every gdharness server of the user appends a line to when it starts, when its client first names itself, when it replaces another server of its project, and when it ends, with why it ended (the client closing its end as stdin:end, a signal by name, superseded with the successor's pid, an uncaught exception or rejection with its stack, or the exit code) and the calls it was answering then. The parent's command line follows on its own line a few seconds after the start. A server killed without warning writes no ended line, and on Windows every kill is that, so a started line with no ended line after it is a server something killed. " + "runtimeConnected counts the games of the project this server serves on this release's runtime addon, and runtimes lists every game on the machine with ofThisProject saying which is which, since a game of another project is reached only by naming its projectPath, and userDir, the directory its user:// is, where its saves and settings are read. playingInEditor is null when the editor was not asked or did not answer, which is not the same as a no: with no editor connected there is nobody to ask, and an editor that does not serve the question is given a moment and then left. A game can be playing while this is null, so read it as unanswered rather than as nothing playing, and use processActive and runtimes, which come from elsewhere and do not depend on the editor answering. Its play says which play the editor is on: play.play counts the plays that editor has started since it opened, whoever started them, and play.editorPid is the editor, so two reads with different pairs are two plays even when both say playing. game.run names the run processActive is about, the one editor_output and editor_run stop answer for: its pid, its project, and adopted true when this server read it back from another server rather than starting it. It stays named after the run ends, beside processActive false, so editor_output can still read what that run printed. It is null when there is no run at all. startedAt says when it began, and endedAt when it ended, wherever something saw the end: a run adopted from a note another server wrote can have ended hours before this server started. editor.lspListening and editor.dapListening say whether anything accepts connections on the ports the editor names, lspPort and dapPort: Godot binds each once, as the editor starts, and reports a failure only in the log panel of the editor itself, so a false comes with serversNote: editor_launch rebind binds the debug adapter again in place, and editor_launch restart the language server. editor.scan says whether the editor is scanning or importing and how many milliseconds ago its last scan finished (finishedMsAgo), or is null when it did not answer: the editor rewrites the class cache just after a scan, and editor_run waits that out before starting a game, saying so under waitedForEditorScanMs. editor.openScenes lists the scenes open in the editor's tabs, leaving out a tab of a scene never saved, editor.editedScene the one it is showing (null when none is, or when that scene was never saved), and editor.unsavedScenes those with changes not yet written to disk, where an empty entry is a scene never saved. All three come with scan and are absent when it is null. Each game under runtimes is pinged with a short wait, so one held at a breakpoint is listed as unreachable rather than making this call wait out the runtime timeout. For the run this server is reporting on, heldAt says whether that is what it is, with the same three answers editor_output gives. A game the editor played announces the editor under editorPid, and so does whatever that game started for itself. A game some other server started announces none, which is how the two are told apart when both are running from one project. Each game also says which runtime addon it loaded, under addonVersion (null when it cannot say). addonIsStale is true when that code is not what this server ships, and staleNote says whether the project needs the gdharness upgrade command, spelled out to run in a shell, the game a restart, or this server a reconnect. A second checkout of a project, such as a git worktree, carries addons of its own and is where this shows. A runtime_* call to such a game is refused with the staleNote and not sent. When connected is false, mayYetConnect says whether that is final: the editor dials this server rather than the other way round and backs off between tries, so for the first half-minute after a server starts, false means \"not reached yet\" as often as it means \"no editor\". True is worth waiting out. False is an editor that is not there. False beside editorCannotConnect is a project without the gdharness editor addon installed or enabled, where no editor can ever dial in, however young the server or whichever editor is open. Its remedies say how to install or enable it. False beside restartInterrupted is one that an editor_launch restart on a previous server asked to quit, where the launch half never ran because that server was ended in between, usually by the reconnect the restart was taken for. editor_launch open finishes it. True beside restartUnderway is a restart still in progress, its server alive and within the wait for the old editor to go: the editor is coming, and opening another would put two on one project. listeningSince is when this server took the bridge port, which is what that window is measured from and what a caller wanting its own window should read. An editor this server opened counts for as long as its process lives, however far past that window the import runs, and awaitingLaunchedEditor names its pid so the two reasons for true are told apart. Any editor of this project running and not yet connected, whoever opened it, makes it true as well and is named under editorsNotYetConnected with its pid and start time, so a restart a reconnect interrupted after its launch is not read as nothing being there. settingsDropped appears here once when the editor this server opened saved project.godot on its way in and took a key out of it, which is the same reading editor_launch restart makes in its own answer. A game that has announced itself in a protocol this server does not speak is listed under unreadable rather than left out, with the protocol it speaks and whether the server or the addon is the older half. This happens during an upgrade that moves the runtime protocol, which most do not: installing replaces the addon on disk as soon as the pin moves, while a server that is already running stays on the version it started with, so the two speak different protocols until the server is reconnected. Those games are real and running, but no tool here can reach them until the halves match, so runtimes stays empty and runtimeConnected stays false. An upgrade that leaves the protocol alone opens no such window and this list stays empty through it. addonIsStale is decided by the code of the editor addons rather than their version, so an editor started before an upgrade that left that code alone reports the older addonVersion, is not stale, and addonNote says so: it needs no restart. A stale editor is asked nothing but to quit: every other call to it is refused with staleNote, which names editor_launch restart, the upgrade command or a reconnect. port is the one the editor bridge is on. When that is not the one this server was configured with, portWanted names the configured one and portNote says why it moved and, when it was the server this one replaced, which pid kept it. A server replacing another of its project that holds the configured port waits for it to stand down and takes the port over, which takes a few seconds. bridgeHandover says so while it waits, and it moves only if that server has not let go within the wait. An editor that has connected and not yet greeted, which lasts a frame and longer while it imports, reports nothing about itself: greeting says so, addonIsStale is absent rather than guessed, and a restart asked then waits for the greeting to learn who opened it. startedByAnEditor is an editor another editor started, by Godot's own restart or the project manager, which has none of the arguments a server gave the one before it. startedByAnEditorNote says what that costs. An engine run that loads the editor addon to do one job and quit, such as a gate's godot --headless --import, an export or a script, is not taken as the editor: a process that greets without a server's mark has its command line read first, and from the socket opening until its greeting and that read are done, identifying is true and connected false. One started with such an option is turned away, and turnedAway names the last one, its pid and the option. Every other call made while an editor is arriving, before its greeting or while it is looked up, waits up to ten seconds for it to settle rather than acting on connected as it reads in that window. serverVersion is the gdharness version of this server. bridgeAvailable says the editor bridge is listening. When it failed to start, startupError gives the error, and retryingBridge is true while this server tries again. announcedAt is the file where this server told editors of its project which port to dial. connectedAt is when the editor connected, and lastPongAt when it last answered this server's keep-alive ping. addonDigest is a hash of the editor addon's code, which addonIsStale compares with what this server ships. debugPort is the port of the editor's remote debugger, the one games it plays connect to. openedByAServer says a gdharness server started this editor, syncsBreakpoints that it keeps its breakpoints when a debug session opens, which Godot otherwise clears, and hidden that it runs with no window anybody can see. pendingRequests counts the requests sent to the editor and not yet answered, and queuedResources the files with writes waiting behind another write to the same file.",
|
|
19718
19756
|
parameters: {},
|
|
19719
19757
|
requires: []
|
|
19720
19758
|
},
|
|
@@ -19744,6 +19782,41 @@ var init_tool_definitions = __esm(() => {
|
|
|
19744
19782
|
},
|
|
19745
19783
|
requires: ["projectPath"]
|
|
19746
19784
|
},
|
|
19785
|
+
{
|
|
19786
|
+
name: "editor_capture",
|
|
19787
|
+
title: "Capture the editor's view of a scene",
|
|
19788
|
+
effect: "read",
|
|
19789
|
+
description: "A picture of a scene as the editor's 2D or 3D view draws it, including what its @tool scripts draw under Engine.is_editor_hint(). No game and no headless engine runs that branch, so runtime_capture cannot show it. The editor opens the scene, shows its 2D or 3D view for a few frames and captures it. It then returns to the scene and view it showed before, and closes the scene again if it was not open. The view is the editor's own, at its zoom and scroll for that scene, and in 3D with its grid and gizmos. A scene open with unsaved changes is captured as the editor holds it. Other calls to the editor wait for the capture. Needs a connected editor that draws: a visible one, or a hidden one on Windows. A hidden editor on Linux or macOS is headless and is refused.",
|
|
19790
|
+
answer: "The picture, with its size in pixels and which view drew it. When width or height scaled the picture, it also gives the size the view drew at. A scene with unsaved changes in the editor says so.",
|
|
19791
|
+
parameters: {
|
|
19792
|
+
projectPath: PROJECT_PATH,
|
|
19793
|
+
scenePath: SCENE_PATH,
|
|
19794
|
+
view: {
|
|
19795
|
+
type: "string",
|
|
19796
|
+
enum: ["2d", "3d"],
|
|
19797
|
+
description: "Which of the editor's views draws the scene. Default 3d for a scene whose root is a Node3D, and 2d otherwise."
|
|
19798
|
+
},
|
|
19799
|
+
width: {
|
|
19800
|
+
type: "integer",
|
|
19801
|
+
minimum: 1,
|
|
19802
|
+
description: "Scale the picture to this width. Given alone, the height keeps the picture's proportions."
|
|
19803
|
+
},
|
|
19804
|
+
height: {
|
|
19805
|
+
type: "integer",
|
|
19806
|
+
minimum: 1,
|
|
19807
|
+
description: "Scale the picture to this height. Given alone, the width keeps the picture's proportions."
|
|
19808
|
+
},
|
|
19809
|
+
outputPath: {
|
|
19810
|
+
type: "string",
|
|
19811
|
+
description: "Also save the picture as a lossless PNG at this absolute path. The answer says where. Refused unless it ends in .png, its directory exists, it is outside the project, and no file is there already."
|
|
19812
|
+
},
|
|
19813
|
+
inline: {
|
|
19814
|
+
type: "boolean",
|
|
19815
|
+
description: "Send the picture back in the answer. Default true, as a JPEG when that is smaller than the PNG and the picture has no transparency. false sends none and needs outputPath."
|
|
19816
|
+
}
|
|
19817
|
+
},
|
|
19818
|
+
requires: ["projectPath", "scenePath"]
|
|
19819
|
+
},
|
|
19747
19820
|
{
|
|
19748
19821
|
name: "editor_classes",
|
|
19749
19822
|
title: "Engine classes",
|
|
@@ -51102,27 +51175,109 @@ var init_test_selection = __esm(() => {
|
|
|
51102
51175
|
CASE_AFTER_SCRIPT = /^(.*\.gd):(\w+)$/;
|
|
51103
51176
|
});
|
|
51104
51177
|
|
|
51178
|
+
// src/write-turns.ts
|
|
51179
|
+
import { createHash as createHash8 } from "node:crypto";
|
|
51180
|
+
import { closeSync as closeSync6, mkdirSync as mkdirSync23, openSync as openSync7, readFileSync as readFileSync41, rmSync as rmSync21, statSync as statSync20, writeSync as writeSync5 } from "node:fs";
|
|
51181
|
+
import { join as join45, relative as relative10 } from "node:path";
|
|
51182
|
+
import process18 from "node:process";
|
|
51183
|
+
import { setTimeout as delay8 } from "node:timers/promises";
|
|
51184
|
+
function writeLockOf(projectPath, file) {
|
|
51185
|
+
const named = relative10(projectPath, file).split("\\").join("/");
|
|
51186
|
+
const key = process18.platform === "win32" ? named.toLowerCase() : named;
|
|
51187
|
+
const hash = createHash8("sha256").update(key).digest("hex").slice(0, 16);
|
|
51188
|
+
return join45(projectPath, ".godot", WRITES_FOLDER, `${hash}.lock`);
|
|
51189
|
+
}
|
|
51190
|
+
async function inTurnFor(projectPath, file, write) {
|
|
51191
|
+
const lock = writeLockOf(projectPath, file);
|
|
51192
|
+
mkdirSync23(join45(projectPath, ".godot", WRITES_FOLDER), { recursive: true });
|
|
51193
|
+
const holder = JSON.stringify({ pid: process18.pid, started: await ownStart(), file });
|
|
51194
|
+
const starts = new Map;
|
|
51195
|
+
while (!made(lock, holder) && !await takenOver(lock, holder, starts)) {
|
|
51196
|
+
await delay8(TURN_LOOK_MS);
|
|
51197
|
+
}
|
|
51198
|
+
try {
|
|
51199
|
+
return await write();
|
|
51200
|
+
} finally {
|
|
51201
|
+
removeWhole(lock);
|
|
51202
|
+
}
|
|
51203
|
+
}
|
|
51204
|
+
function made(path, content) {
|
|
51205
|
+
let file;
|
|
51206
|
+
try {
|
|
51207
|
+
file = openSync7(path, "wx");
|
|
51208
|
+
} catch {
|
|
51209
|
+
return false;
|
|
51210
|
+
}
|
|
51211
|
+
try {
|
|
51212
|
+
writeSync5(file, content);
|
|
51213
|
+
} finally {
|
|
51214
|
+
closeSync6(file);
|
|
51215
|
+
}
|
|
51216
|
+
return true;
|
|
51217
|
+
}
|
|
51218
|
+
async function takenOver(lock, holder, starts) {
|
|
51219
|
+
if (!await gone2(lock, starts)) {
|
|
51220
|
+
return false;
|
|
51221
|
+
}
|
|
51222
|
+
const steal = `${lock}.steal`;
|
|
51223
|
+
if (!made(steal, holder)) {
|
|
51224
|
+
try {
|
|
51225
|
+
if (Date.now() - statSync20(steal).mtimeMs > UNWRITTEN_MS) {
|
|
51226
|
+
rmSync21(steal, { force: true });
|
|
51227
|
+
}
|
|
51228
|
+
} catch {}
|
|
51229
|
+
return false;
|
|
51230
|
+
}
|
|
51231
|
+
try {
|
|
51232
|
+
return await gone2(lock, starts) && removeWhole(lock) && made(lock, holder);
|
|
51233
|
+
} finally {
|
|
51234
|
+
rmSync21(steal, { force: true });
|
|
51235
|
+
}
|
|
51236
|
+
}
|
|
51237
|
+
async function gone2(lock, starts) {
|
|
51238
|
+
let content;
|
|
51239
|
+
let writtenAt;
|
|
51240
|
+
try {
|
|
51241
|
+
content = readFileSync41(lock, "utf8");
|
|
51242
|
+
writtenAt = statSync20(lock).mtimeMs;
|
|
51243
|
+
} catch {
|
|
51244
|
+
return false;
|
|
51245
|
+
}
|
|
51246
|
+
const pid = holderOf(content)?.pid;
|
|
51247
|
+
if (pid !== undefined && !starts.has(pid) && alive(pid)) {
|
|
51248
|
+
starts.set(pid, (await startTimesOf([pid])).get(pid));
|
|
51249
|
+
}
|
|
51250
|
+
return lockIsFree(content, writtenAt, Date.now(), alive, (asked) => starts.get(asked));
|
|
51251
|
+
}
|
|
51252
|
+
var WRITES_FOLDER = "gdharness-writes", TURN_LOOK_MS = 100;
|
|
51253
|
+
var init_write_turns = __esm(() => {
|
|
51254
|
+
init_alive();
|
|
51255
|
+
init_engine_slots();
|
|
51256
|
+
init_process_children();
|
|
51257
|
+
init_whole_file();
|
|
51258
|
+
});
|
|
51259
|
+
|
|
51105
51260
|
// src/server.ts
|
|
51106
51261
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
51107
51262
|
import {
|
|
51108
51263
|
appendFileSync as appendFileSync2,
|
|
51109
|
-
closeSync as
|
|
51264
|
+
closeSync as closeSync7,
|
|
51110
51265
|
constants,
|
|
51111
51266
|
copyFileSync as copyFileSync2,
|
|
51112
51267
|
existsSync as existsSync36,
|
|
51113
51268
|
fstatSync as fstatSync3,
|
|
51114
|
-
mkdirSync as
|
|
51115
|
-
openSync as
|
|
51269
|
+
mkdirSync as mkdirSync24,
|
|
51270
|
+
openSync as openSync8,
|
|
51116
51271
|
readdirSync as readdirSync21,
|
|
51117
|
-
readFileSync as
|
|
51272
|
+
readFileSync as readFileSync42,
|
|
51118
51273
|
readSync as readSync4,
|
|
51119
51274
|
realpathSync as realpathSync7,
|
|
51120
51275
|
rmdirSync as rmdirSync2,
|
|
51121
|
-
statSync as
|
|
51276
|
+
statSync as statSync21,
|
|
51122
51277
|
writeFileSync as writeFileSync24
|
|
51123
51278
|
} from "node:fs";
|
|
51124
|
-
import { basename as basename14, dirname as dirname25, extname as extname6, isAbsolute as isAbsolute9, join as
|
|
51125
|
-
import { setTimeout as
|
|
51279
|
+
import { basename as basename14, dirname as dirname25, extname as extname6, isAbsolute as isAbsolute9, join as join46, normalize as normalize2, relative as relative11 } from "node:path";
|
|
51280
|
+
import { setTimeout as delay9 } from "node:timers/promises";
|
|
51126
51281
|
import { fileURLToPath as fileURLToPath6 } from "node:url";
|
|
51127
51282
|
function editorRestartTimeoutMs() {
|
|
51128
51283
|
return waitFromEnv("GDHARNESS_EDITOR_RESTART_TIMEOUT_MS", 90000);
|
|
@@ -51135,9 +51290,9 @@ function greetingWaitMs() {
|
|
|
51135
51290
|
return waitFromEnv("GDHARNESS_GREETING_WAIT_MS", 1e4);
|
|
51136
51291
|
}
|
|
51137
51292
|
function pictureToSend(pngPath, jpegPath) {
|
|
51138
|
-
const png =
|
|
51293
|
+
const png = readFileSync42(pngPath);
|
|
51139
51294
|
const beside = pngPath.replace(/\.png$/, ".jpg");
|
|
51140
|
-
const jpeg = jpegPath !== undefined && normalize2(jpegPath) === normalize2(beside) && existsSync36(beside) ?
|
|
51295
|
+
const jpeg = jpegPath !== undefined && normalize2(jpegPath) === normalize2(beside) && existsSync36(beside) ? readFileSync42(beside) : null;
|
|
51141
51296
|
const sent = jpeg !== null && jpeg.length < png.length ? jpeg : png;
|
|
51142
51297
|
return {
|
|
51143
51298
|
data: sent.toString("base64"),
|
|
@@ -51181,7 +51336,7 @@ function writtenFileRefusal(path, rule) {
|
|
|
51181
51336
|
if (!existsSync36(directory)) {
|
|
51182
51337
|
return `${argument}'s directory ${dirname25(path)} does not exist. ${whatOpening} creates no directories.`;
|
|
51183
51338
|
}
|
|
51184
|
-
const where =
|
|
51339
|
+
const where = join46(directory, basename14(path));
|
|
51185
51340
|
const inside = rule.projects.find((project) => project !== "" && isWithinRoot(realAsFarAsItExists(project), where));
|
|
51186
51341
|
if (inside !== undefined) {
|
|
51187
51342
|
return `${argument} ${path} is inside the project at ${inside}. ${whatOpening} is saved outside it, so it never becomes part of the game.`;
|
|
@@ -51277,7 +51432,7 @@ function keptGameRequest(tool, op, args) {
|
|
|
51277
51432
|
}
|
|
51278
51433
|
}
|
|
51279
51434
|
function projectSpelling(projectPath, absolutePath) {
|
|
51280
|
-
return `res://${
|
|
51435
|
+
return `res://${relative11(realAsFarAsItExists(projectPath), realAsFarAsItExists(absolutePath)).replaceAll("\\", "/")}`;
|
|
51281
51436
|
}
|
|
51282
51437
|
function whichGame(args) {
|
|
51283
51438
|
return { projectPath: args["projectPath"], pid: args["pid"] };
|
|
@@ -51408,7 +51563,7 @@ async function untilGone(pids, withinMs = stopWaitMs()) {
|
|
|
51408
51563
|
const gone = () => pids.every((pid) => !alive(pid));
|
|
51409
51564
|
const deadline = Date.now() + withinMs;
|
|
51410
51565
|
while (!gone() && Date.now() < deadline) {
|
|
51411
|
-
await
|
|
51566
|
+
await delay9(50);
|
|
51412
51567
|
}
|
|
51413
51568
|
return gone();
|
|
51414
51569
|
}
|
|
@@ -51612,7 +51767,7 @@ function endedToStartThis(ended) {
|
|
|
51612
51767
|
return ` ${which} was ended to start this one. Its output is no longer what editor_output answers about.`;
|
|
51613
51768
|
}
|
|
51614
51769
|
function lineNothingStopsOn(absolutePath, relativePath, line) {
|
|
51615
|
-
const text =
|
|
51770
|
+
const text = readFileSync42(absolutePath, "utf8");
|
|
51616
51771
|
const lines = text === "" ? 0 : text.split(`
|
|
51617
51772
|
`).length - (text.endsWith(`
|
|
51618
51773
|
`) ? 1 : 0);
|
|
@@ -51820,7 +51975,7 @@ function notRunNote(notRun, failFast) {
|
|
|
51820
51975
|
function presetsIn(projectPath) {
|
|
51821
51976
|
let text;
|
|
51822
51977
|
try {
|
|
51823
|
-
text =
|
|
51978
|
+
text = readFileSync42(join46(projectPath, "export_presets.cfg"), "utf8");
|
|
51824
51979
|
} catch {
|
|
51825
51980
|
return null;
|
|
51826
51981
|
}
|
|
@@ -51927,7 +52082,7 @@ function filesIn(folder) {
|
|
|
51927
52082
|
try {
|
|
51928
52083
|
for (const entry of readdirSync21(folder, { withFileTypes: true })) {
|
|
51929
52084
|
if (entry.isFile()) {
|
|
51930
|
-
found.set(entry.name,
|
|
52085
|
+
found.set(entry.name, statSync21(join46(folder, entry.name)).mtimeMs);
|
|
51931
52086
|
}
|
|
51932
52087
|
}
|
|
51933
52088
|
} catch {}
|
|
@@ -51967,7 +52122,7 @@ function unsavedInEditorNote(scripts) {
|
|
|
51967
52122
|
return `${listed} ${one ? "is" : "are"} open in the editor's script ${one ? "tab" : "tabs"} with unsaved edits, and saving ${one ? "that tab" : "them"} there writes ${one ? "its" : "their"} text over what was written here: close ${one ? "it" : "them"} without saving, or save first and edit again.`;
|
|
51968
52123
|
}
|
|
51969
52124
|
function editorAddonMissing(projectPath) {
|
|
51970
|
-
if (!existsSync36(
|
|
52125
|
+
if (!existsSync36(join46(projectPath, "project.godot"))) {
|
|
51971
52126
|
return null;
|
|
51972
52127
|
}
|
|
51973
52128
|
const report = inspectProject(projectPath);
|
|
@@ -51995,8 +52150,8 @@ function rewriteBusNamed(projectPath, from, to) {
|
|
|
51995
52150
|
const giving = [`bus = &"${quoted(from)}"`, `bus = "${quoted(from)}"`];
|
|
51996
52151
|
const rewritten = [];
|
|
51997
52152
|
for (const file of projectFiles(projectPath, [".tscn", ".tres"])) {
|
|
51998
|
-
const absolute =
|
|
51999
|
-
const lines =
|
|
52153
|
+
const absolute = join46(projectPath, file);
|
|
52154
|
+
const lines = readFileSync42(absolute, "utf8").split(`
|
|
52000
52155
|
`);
|
|
52001
52156
|
const at = [];
|
|
52002
52157
|
const after = lines.map((line, index) => {
|
|
@@ -52015,7 +52170,7 @@ function rewriteBusNamed(projectPath, from, to) {
|
|
|
52015
52170
|
}
|
|
52016
52171
|
const namedInScripts = [];
|
|
52017
52172
|
for (const file of projectFiles(projectPath, [".gd"])) {
|
|
52018
|
-
|
|
52173
|
+
readFileSync42(join46(projectPath, file), "utf8").split(`
|
|
52019
52174
|
`).forEach((line, index) => {
|
|
52020
52175
|
if (line.includes(`"${from}"`)) {
|
|
52021
52176
|
namedInScripts.push({ file: `res://${file}`, line: index + 1, text: line.trim() });
|
|
@@ -52306,7 +52461,7 @@ function profileFrameFunctions(projectPath) {
|
|
|
52306
52461
|
let projectGodot = null;
|
|
52307
52462
|
if (projectPath !== null) {
|
|
52308
52463
|
try {
|
|
52309
|
-
projectGodot =
|
|
52464
|
+
projectGodot = readFileSync42(join46(projectPath, "project.godot"), "utf8");
|
|
52310
52465
|
} catch {}
|
|
52311
52466
|
}
|
|
52312
52467
|
return frameFunctionsFor(queuedMessagesOf(projectGodot));
|
|
@@ -52330,7 +52485,7 @@ function projectSourceOf(projectPath) {
|
|
|
52330
52485
|
let source = null;
|
|
52331
52486
|
if (inside.ok) {
|
|
52332
52487
|
try {
|
|
52333
|
-
source =
|
|
52488
|
+
source = readFileSync42(inside.absolutePath, "utf8");
|
|
52334
52489
|
} catch {}
|
|
52335
52490
|
}
|
|
52336
52491
|
read.set(script, source);
|
|
@@ -52502,7 +52657,7 @@ function foldedEntriesAnswer(log, floor = "warning") {
|
|
|
52502
52657
|
}
|
|
52503
52658
|
function gdUnitFailsOnScriptErrors(projectPath) {
|
|
52504
52659
|
try {
|
|
52505
|
-
return settingKeys(
|
|
52660
|
+
return settingKeys(readFileSync42(join46(projectPath, "project.godot"), "utf8")).get("gdunit4/report/godot/script_error") !== false;
|
|
52506
52661
|
} catch {
|
|
52507
52662
|
return true;
|
|
52508
52663
|
}
|
|
@@ -52517,7 +52672,7 @@ function uncaughtNote(uncaught) {
|
|
|
52517
52672
|
}
|
|
52518
52673
|
function elsewhereIn(projectPath, asked) {
|
|
52519
52674
|
const wanted = asked.replace(/^res:\/\//, "").replace(/\/+$/, "");
|
|
52520
|
-
if (wanted === "" || existsSync36(
|
|
52675
|
+
if (wanted === "" || existsSync36(join46(projectPath, wanted))) {
|
|
52521
52676
|
return "";
|
|
52522
52677
|
}
|
|
52523
52678
|
const root = (wanted.split("/")[0] ?? "").toLowerCase();
|
|
@@ -52540,7 +52695,7 @@ function windowNoteFor(needing, sharing) {
|
|
|
52540
52695
|
return `${named(needing)} ${one ? "needs" : "need"} a window, as gdharness test reads ${one ? "it" : "them"}, so the run had one: the engine ran without --headless, at the size project.godot gives its viewport, with no sound${process.platform === "win32" ? ", on a desktop of its own, where its window neither shows nor takes the keyboard" : ""}. ${why}${shared}`;
|
|
52541
52696
|
}
|
|
52542
52697
|
function testPathsIn(projectPath, given, argument = "path") {
|
|
52543
|
-
const defaults = TEST_DIRECTORIES.filter((directory) => existsSync36(
|
|
52698
|
+
const defaults = TEST_DIRECTORIES.filter((directory) => existsSync36(join46(projectPath, directory)));
|
|
52544
52699
|
const entries = given === undefined || given === null || given === "" ? defaults.length > 0 ? defaults : TEST_DIRECTORIES : given;
|
|
52545
52700
|
const listed = Array.isArray(entries) ? entries : [entries];
|
|
52546
52701
|
if (listed.length === 0) {
|
|
@@ -52643,17 +52798,17 @@ function asWarning(raised) {
|
|
|
52643
52798
|
}
|
|
52644
52799
|
function isDirectory(path) {
|
|
52645
52800
|
try {
|
|
52646
|
-
return
|
|
52801
|
+
return statSync21(path).isDirectory();
|
|
52647
52802
|
} catch {
|
|
52648
52803
|
return false;
|
|
52649
52804
|
}
|
|
52650
52805
|
}
|
|
52651
52806
|
function mainSceneOf(projectFile) {
|
|
52652
|
-
const scene = parseProjectGodot(
|
|
52807
|
+
const scene = parseProjectGodot(readFileSync42(projectFile, "utf8"))["application"]?.["run/main_scene"];
|
|
52653
52808
|
return typeof scene === "string" && scene !== "" ? scene : null;
|
|
52654
52809
|
}
|
|
52655
52810
|
function editorPlaysHeadless(projectFile) {
|
|
52656
|
-
const runArgs = parseProjectGodot(
|
|
52811
|
+
const runArgs = parseProjectGodot(readFileSync42(projectFile, "utf8"))["editor"]?.["run/main_run_args"];
|
|
52657
52812
|
return typeof runArgs === "string" && /(?:^|\s)--headless(?:\s|$)/.test(runArgs);
|
|
52658
52813
|
}
|
|
52659
52814
|
function ownArguments(params) {
|
|
@@ -52685,7 +52840,7 @@ function unheldNote(changed) {
|
|
|
52685
52840
|
class GodotServer {
|
|
52686
52841
|
stdio = null;
|
|
52687
52842
|
locator = new GodotLocator;
|
|
52688
|
-
operationsScript =
|
|
52843
|
+
operationsScript = join46(__dirname2, "godot", "operations", "godot_operations.gd");
|
|
52689
52844
|
exportOptionsRead = new Map;
|
|
52690
52845
|
heldWhilePending = new Set;
|
|
52691
52846
|
evaluatedWhilePending = new Set;
|
|
@@ -52844,7 +52999,7 @@ class GodotServer {
|
|
|
52844
52999
|
taken = true;
|
|
52845
53000
|
this.logDebug(`Took port ${wanted} over from pid ${announced.pid}`);
|
|
52846
53001
|
} catch {
|
|
52847
|
-
await
|
|
53002
|
+
await delay9(HANDOVER_POLL_MS);
|
|
52848
53003
|
}
|
|
52849
53004
|
}
|
|
52850
53005
|
this.notHandedOverBy = taken ? null : announced.pid;
|
|
@@ -52975,7 +53130,7 @@ class GodotServer {
|
|
|
52975
53130
|
}
|
|
52976
53131
|
noteTheStart() {
|
|
52977
53132
|
const cwd = process.cwd();
|
|
52978
|
-
const mcpJson =
|
|
53133
|
+
const mcpJson = join46(cwd, ".mcp.json");
|
|
52979
53134
|
const announced = this.ownProject === null ? null : readAnnouncement(announcementPath(this.ownProject));
|
|
52980
53135
|
const predecessor = announced !== null && announced.pid !== process.pid && alive(announced.pid) ? announced.pid : null;
|
|
52981
53136
|
writeLifecycle("started", {
|
|
@@ -53124,7 +53279,7 @@ class GodotServer {
|
|
|
53124
53279
|
return checked.response;
|
|
53125
53280
|
}
|
|
53126
53281
|
const project = args["projectPath"];
|
|
53127
|
-
if (typeof project === "string" && existsSync36(
|
|
53282
|
+
if (typeof project === "string" && existsSync36(join46(project, "project.godot"))) {
|
|
53128
53283
|
this.lastProjectPath = project;
|
|
53129
53284
|
}
|
|
53130
53285
|
this.updates.refresh();
|
|
@@ -53167,7 +53322,7 @@ class GodotServer {
|
|
|
53167
53322
|
let notSaved;
|
|
53168
53323
|
try {
|
|
53169
53324
|
savedAt = answer.content.map((item, index) => {
|
|
53170
|
-
const file =
|
|
53325
|
+
const file = join46(directory, item.type === "text" ? `${stem}-${index}.txt` : `${stem}-${index}.${item.mimeType.split("/")[1] ?? "bin"}`);
|
|
53171
53326
|
writeFileSync24(file, item.type === "text" ? item.text : Buffer.from(item.data, "base64"));
|
|
53172
53327
|
return file;
|
|
53173
53328
|
});
|
|
@@ -53631,6 +53786,8 @@ class GodotServer {
|
|
|
53631
53786
|
return await this.handleEditorStatus();
|
|
53632
53787
|
case "editor_rescan":
|
|
53633
53788
|
return await this.handleRescanFilesystem(args);
|
|
53789
|
+
case "editor_capture":
|
|
53790
|
+
return await this.handleEditorCapture(args);
|
|
53634
53791
|
case "runtime_inspect": {
|
|
53635
53792
|
const patience = readPositiveNumber(args, "timeoutMs") ?? this.runtimeTimeoutMs();
|
|
53636
53793
|
switch (op) {
|
|
@@ -53864,7 +54021,7 @@ class GodotServer {
|
|
|
53864
54021
|
if (path === undefined) {
|
|
53865
54022
|
return { ok: false, response: this.createErrorResponse("projectPath is required.") };
|
|
53866
54023
|
}
|
|
53867
|
-
const file =
|
|
54024
|
+
const file = join46(path, "project.godot");
|
|
53868
54025
|
if (!existsSync36(file)) {
|
|
53869
54026
|
return {
|
|
53870
54027
|
ok: false,
|
|
@@ -54614,7 +54771,7 @@ class GodotServer {
|
|
|
54614
54771
|
if (!engine.ok) {
|
|
54615
54772
|
return engine.response;
|
|
54616
54773
|
}
|
|
54617
|
-
const application = parseProjectGodot(
|
|
54774
|
+
const application = parseProjectGodot(readFileSync42(project.value.file, "utf8"))["application"] ?? {};
|
|
54618
54775
|
const name = application["config/name"];
|
|
54619
54776
|
const mainScene = application["run/main_scene"];
|
|
54620
54777
|
const info = {
|
|
@@ -54768,7 +54925,7 @@ class GodotServer {
|
|
|
54768
54925
|
const platform = presetPlatform(project.value.path, preset);
|
|
54769
54926
|
if (platform !== null) {
|
|
54770
54927
|
const known = await this.exportOptions(platform, {
|
|
54771
|
-
presetsFile:
|
|
54928
|
+
presetsFile: join46(project.value.path, "export_presets.cfg"),
|
|
54772
54929
|
preset,
|
|
54773
54930
|
asked: options
|
|
54774
54931
|
});
|
|
@@ -54819,7 +54976,7 @@ ${platform}`;
|
|
|
54819
54976
|
}
|
|
54820
54977
|
const empty = scratchDirectory("export");
|
|
54821
54978
|
try {
|
|
54822
|
-
writeFileSync24(
|
|
54979
|
+
writeFileSync24(join46(empty, "project.godot"), `config_version=5
|
|
54823
54980
|
`);
|
|
54824
54981
|
const outcome = await runOperation({ godotPath: engine.value, script: this.operationsScript, debug: DEBUG_MODE, editor: true }, "export_options", { platform, ...standing }, empty);
|
|
54825
54982
|
const options = outcome.ok ? outcome.payload["options"] : undefined;
|
|
@@ -54858,7 +55015,7 @@ ${platform}`;
|
|
|
54858
55015
|
}
|
|
54859
55016
|
const folder = dirname25(output.absolutePath);
|
|
54860
55017
|
try {
|
|
54861
|
-
|
|
55018
|
+
mkdirSync24(folder, { recursive: true });
|
|
54862
55019
|
} catch (error) {
|
|
54863
55020
|
return this.createErrorResponse(`Cannot create ${dirname25(output.relativePath)} to export into: ${errorMessage(error)}`);
|
|
54864
55021
|
}
|
|
@@ -54876,7 +55033,7 @@ ${platform}`;
|
|
|
54876
55033
|
const exportArgs = [
|
|
54877
55034
|
"--headless",
|
|
54878
55035
|
"--log-file",
|
|
54879
|
-
|
|
55036
|
+
join46(exportLogs, "engine.log"),
|
|
54880
55037
|
"--path",
|
|
54881
55038
|
project.value.path,
|
|
54882
55039
|
pack ? "--export-pack" : debug ? "--export-debug" : "--export-release",
|
|
@@ -54884,7 +55041,7 @@ ${platform}`;
|
|
|
54884
55041
|
output.absolutePath
|
|
54885
55042
|
];
|
|
54886
55043
|
this.logDebug(`Exporting: ${engine.value} ${exportArgs.join(" ")}`);
|
|
54887
|
-
const writtenAt = () => existsSync36(output.absolutePath) ?
|
|
55044
|
+
const writtenAt = () => existsSync36(output.absolutePath) ? statSync21(output.absolutePath).mtimeMs : null;
|
|
54888
55045
|
const caches = editorCacheDirectory(engine.value);
|
|
54889
55046
|
const exportOnce = async () => {
|
|
54890
55047
|
const before = writtenAt();
|
|
@@ -54914,7 +55071,7 @@ ${platform}`;
|
|
|
54914
55071
|
return this.createErrorResponse(`Export could not be run: ${ending.failure ?? "the engine gave no exit status"}`);
|
|
54915
55072
|
}
|
|
54916
55073
|
const beside = writtenBeside(listedBefore, filesIn(folder), basename14(output.absolutePath), dirname25(output.relativePath).replaceAll("\\", "/"));
|
|
54917
|
-
return exportAnswer(ending, { preset, outputPath: output.relativePath, debug, ...pack ? { pack } : {} }, before, writtenAt(), beside, docCacheNote, existsSync36(output.absolutePath) ?
|
|
55074
|
+
return exportAnswer(ending, { preset, outputPath: output.relativePath, debug, ...pack ? { pack } : {} }, before, writtenAt(), beside, docCacheNote, existsSync36(output.absolutePath) ? statSync21(output.absolutePath).size : null, importedFirstAnswer(imported));
|
|
54918
55075
|
}
|
|
54919
55076
|
reportTestProgress(log) {
|
|
54920
55077
|
const progress = callProgress();
|
|
@@ -54975,7 +55132,7 @@ ${platform}`;
|
|
|
54975
55132
|
if (movie !== null) {
|
|
54976
55133
|
return this.createErrorResponse(movie);
|
|
54977
55134
|
}
|
|
54978
|
-
const missing = paths.value.filter((path) => !existsSync36(
|
|
55135
|
+
const missing = paths.value.filter((path) => !existsSync36(join46(project.value.path, path.slice("res://".length))));
|
|
54979
55136
|
const scripts = new ProjectScripts(project.value.path);
|
|
54980
55137
|
const present = paths.value.filter((path) => !missing.includes(path));
|
|
54981
55138
|
const asked = scripts.searchedAsTold(present);
|
|
@@ -55067,10 +55224,10 @@ ${platform}`;
|
|
|
55067
55224
|
const ignored = readStringArray(args, "ignore") ?? [];
|
|
55068
55225
|
const declaredUnder = ignored.length === 0 ? [] : asked.flatMap((path) => path.endsWith(".gd") ? scripts.suite(path) ?? [] : scripts.suitesUnder([path]));
|
|
55069
55226
|
const userData = scratchDirectory("tests");
|
|
55070
|
-
const suitesFile =
|
|
55071
|
-
const skippingFile =
|
|
55072
|
-
const engineLog =
|
|
55073
|
-
const expectedFile =
|
|
55227
|
+
const suitesFile = join46(userData, "suites.txt");
|
|
55228
|
+
const skippingFile = join46(userData, "skipping.txt");
|
|
55229
|
+
const engineLog = join46(userData, "engine.log");
|
|
55230
|
+
const expectedFile = join46(userData, "expected.json");
|
|
55074
55231
|
writeFileSync24(suitesFile, `${(selection.value?.suites ?? asked).join(`
|
|
55075
55232
|
`)}
|
|
55076
55233
|
`);
|
|
@@ -55111,11 +55268,11 @@ ${platform}`;
|
|
|
55111
55268
|
const timeoutMs = readPositiveNumber(args, "timeoutMs") ?? TEST_TIMEOUT_MS;
|
|
55112
55269
|
this.logDebug(`Running tests: ${engine.value} ${cmdArgs.join(" ")}`);
|
|
55113
55270
|
const helperFiles = {
|
|
55114
|
-
spec:
|
|
55115
|
-
pid:
|
|
55116
|
-
error:
|
|
55117
|
-
stop:
|
|
55118
|
-
step:
|
|
55271
|
+
spec: join46(userData, "spec"),
|
|
55272
|
+
pid: join46(userData, "pid"),
|
|
55273
|
+
error: join46(userData, "error"),
|
|
55274
|
+
stop: join46(userData, "stop"),
|
|
55275
|
+
step: join46(userData, "step")
|
|
55119
55276
|
};
|
|
55120
55277
|
const onDesktop = windowed && process.platform === "win32";
|
|
55121
55278
|
const run = await this.spawnGame(engine.value, cmdArgs, userDataIn(userData), onDesktop ? { directory: project.value.path, files: helperFiles } : undefined);
|
|
@@ -55169,7 +55326,7 @@ ${platform}`;
|
|
|
55169
55326
|
await run.settled;
|
|
55170
55327
|
const helperFailed = onDesktop ? helperFailure(helperFiles.error) ?? (existsSync36(helperFiles.pid) ? null : `it never started, and ${helperStep(helperFiles.step)}`) : null;
|
|
55171
55328
|
if (helperFailed !== null) {
|
|
55172
|
-
discard(
|
|
55329
|
+
discard(join46(project.value.path, ".godot", ...ours.split("/")), userData);
|
|
55173
55330
|
return this.createErrorResponse(`No tests ran: ${named(needing)} ${needing.length === 1 ? "needs" : "need"} a window, and the engine could not be started on a desktop of its own: ${helperFailed}`);
|
|
55174
55331
|
}
|
|
55175
55332
|
const endedAt = Date.now();
|
|
@@ -55180,16 +55337,16 @@ ${platform}`;
|
|
|
55180
55337
|
engine: endedAt - importedAt
|
|
55181
55338
|
};
|
|
55182
55339
|
const notCompiled = stopped.for ?? run.log.all.map((entry) => autoloadNotCompiled(entry, autoloaded)).find((found) => found !== null) ?? null;
|
|
55183
|
-
const reportsDir =
|
|
55340
|
+
const reportsDir = join46(project.value.path, ".godot", ...ours.split("/"));
|
|
55184
55341
|
let report = null;
|
|
55185
55342
|
let reportProblem = null;
|
|
55186
|
-
const casePrinted = withNestedErrors(readRun(existsSync36(engineLog) ?
|
|
55343
|
+
const casePrinted = withNestedErrors(readRun(existsSync36(engineLog) ? readFileSync42(engineLog, "utf8") : ""), run.log.all);
|
|
55187
55344
|
const declaredErrors = readExpected(expectedFile);
|
|
55188
55345
|
try {
|
|
55189
55346
|
const written = existsSync36(reportsDir) && notCompiled === null ? readdirSync21(reportsDir).filter((name) => name.startsWith("report_")).toSorted((a, b) => Number(a.slice("report_".length)) - Number(b.slice("report_".length))) : [];
|
|
55190
55347
|
const newest = written.at(-1);
|
|
55191
55348
|
if (newest !== undefined) {
|
|
55192
|
-
report = parseJUnit(
|
|
55349
|
+
report = parseJUnit(readFileSync42(join46(reportsDir, newest, "results.xml"), "utf8"));
|
|
55193
55350
|
}
|
|
55194
55351
|
} catch (error) {
|
|
55195
55352
|
reportProblem = errorMessage(error);
|
|
@@ -55602,7 +55759,7 @@ ${error.text}`;
|
|
|
55602
55759
|
const scripts = projectFiles(projectPath, [".gd"]).map((script) => `res://${script}`);
|
|
55603
55760
|
const wanted = `res://${relativePath}`;
|
|
55604
55761
|
const folder = dirname25(relativePath.replaceAll("\\", "/"));
|
|
55605
|
-
const inFolder =
|
|
55762
|
+
const inFolder = join46(projectPath, folder);
|
|
55606
55763
|
const beside = existsSync36(inFolder) ? readdirSync21(inFolder, { withFileTypes: true }).filter((entry) => entry.isFile()).map((entry) => `res://${folder === "." ? "" : `${folder}/`}${entry.name}`) : [];
|
|
55607
55764
|
return this.createErrorResponse(withNearestFiles(`Script file does not exist: ${wanted}`, wanted, scripts, beside));
|
|
55608
55765
|
}
|
|
@@ -55735,7 +55892,7 @@ ${error.text}`;
|
|
|
55735
55892
|
return null;
|
|
55736
55893
|
}
|
|
55737
55894
|
try {
|
|
55738
|
-
return
|
|
55895
|
+
return readFileSync42(contained.absolutePath, "utf8");
|
|
55739
55896
|
} catch {
|
|
55740
55897
|
return null;
|
|
55741
55898
|
}
|
|
@@ -55829,7 +55986,7 @@ ${error.text}`;
|
|
|
55829
55986
|
return null;
|
|
55830
55987
|
}
|
|
55831
55988
|
try {
|
|
55832
|
-
return
|
|
55989
|
+
return readFileSync42(contained.absolutePath, "utf8");
|
|
55833
55990
|
} catch {
|
|
55834
55991
|
return null;
|
|
55835
55992
|
}
|
|
@@ -56003,7 +56160,7 @@ ${error.text}`;
|
|
|
56003
56160
|
...Array.isArray(stack) ? {
|
|
56004
56161
|
stack: stack.map((frame) => ({
|
|
56005
56162
|
...frame,
|
|
56006
|
-
...projectPath !== "" && typeof frame["script"] === "string" && frame["script"].startsWith("res://") ? { path:
|
|
56163
|
+
...projectPath !== "" && typeof frame["script"] === "string" && frame["script"].startsWith("res://") ? { path: join46(projectPath, frame["script"].slice("res://".length)) } : {}
|
|
56007
56164
|
}))
|
|
56008
56165
|
} : {}
|
|
56009
56166
|
});
|
|
@@ -56127,7 +56284,7 @@ ${error.text}`;
|
|
|
56127
56284
|
try {
|
|
56128
56285
|
const askedAt = Date.now();
|
|
56129
56286
|
const asked = this.godotBridge.invokeTool("playing_status", {}, { counted: false });
|
|
56130
|
-
const answer = await Promise.race([asked,
|
|
56287
|
+
const answer = await Promise.race([asked, delay9(PLAYING_STATUS_MS).then(() => null)]);
|
|
56131
56288
|
if (answer === null) {
|
|
56132
56289
|
this.logDebug(`playing_status did not answer within ${PLAYING_STATUS_MS}ms`);
|
|
56133
56290
|
asked.then(() => {
|
|
@@ -56152,7 +56309,7 @@ ${error.text}`;
|
|
|
56152
56309
|
try {
|
|
56153
56310
|
const answer = await Promise.race([
|
|
56154
56311
|
this.godotBridge.invokeTool("scan_status", {}, { counted: false }),
|
|
56155
|
-
|
|
56312
|
+
delay9(PLAYING_STATUS_MS).then(() => null)
|
|
56156
56313
|
]);
|
|
56157
56314
|
if (answer === null) {
|
|
56158
56315
|
return null;
|
|
@@ -56294,7 +56451,7 @@ ${error.text}`;
|
|
|
56294
56451
|
if (answer.serversNote === undefined || adapterFailed || Date.now() >= deadline) {
|
|
56295
56452
|
return answer;
|
|
56296
56453
|
}
|
|
56297
|
-
await
|
|
56454
|
+
await delay9(500);
|
|
56298
56455
|
}
|
|
56299
56456
|
}
|
|
56300
56457
|
async rebindTheDebugAdapter(servers) {
|
|
@@ -56348,7 +56505,7 @@ ${error.text}`;
|
|
|
56348
56505
|
return null;
|
|
56349
56506
|
}
|
|
56350
56507
|
try {
|
|
56351
|
-
return settingKeys(
|
|
56508
|
+
return settingKeys(readFileSync42(join46(projectPath, "project.godot"), "utf8"));
|
|
56352
56509
|
} catch {
|
|
56353
56510
|
return null;
|
|
56354
56511
|
}
|
|
@@ -56555,7 +56712,7 @@ ${error.text}`;
|
|
|
56555
56712
|
if (Date.now() >= deadline) {
|
|
56556
56713
|
return false;
|
|
56557
56714
|
}
|
|
56558
|
-
await
|
|
56715
|
+
await delay9(250);
|
|
56559
56716
|
}
|
|
56560
56717
|
}
|
|
56561
56718
|
async handleLaunchEditor(args) {
|
|
@@ -56693,7 +56850,7 @@ ${error.text}`;
|
|
|
56693
56850
|
if (!paths.ok) {
|
|
56694
56851
|
return refused(paths.reason, PATH_SOLUTIONS);
|
|
56695
56852
|
}
|
|
56696
|
-
const missing = paths.value.filter((path) => !existsSync36(
|
|
56853
|
+
const missing = paths.value.filter((path) => !existsSync36(join46(projectPath, path.slice("res://".length))));
|
|
56697
56854
|
if (missing.length > 0) {
|
|
56698
56855
|
return refused(`Nothing at ${missing.join(" and ")} in the project at ${projectPath}.`, PATH_SOLUTIONS);
|
|
56699
56856
|
}
|
|
@@ -56866,7 +57023,7 @@ ${error.text}`;
|
|
|
56866
57023
|
}
|
|
56867
57024
|
async runtimeUp(projectPath, before, budget, withArgs) {
|
|
56868
57025
|
const budgetMs = budget.ms;
|
|
56869
|
-
if (!existsSync36(
|
|
57026
|
+
if (!existsSync36(join46(projectPath, RUNTIME_SCRIPT))) {
|
|
56870
57027
|
return runtimeVerdict(null, {
|
|
56871
57028
|
addon: false,
|
|
56872
57029
|
budgetMs,
|
|
@@ -56969,7 +57126,7 @@ ${error.text}`;
|
|
|
56969
57126
|
}
|
|
56970
57127
|
let announcedAt;
|
|
56971
57128
|
try {
|
|
56972
|
-
announcedAt =
|
|
57129
|
+
announcedAt = statSync21(endpoint.file).mtimeMs;
|
|
56973
57130
|
} catch {
|
|
56974
57131
|
return;
|
|
56975
57132
|
}
|
|
@@ -57029,7 +57186,7 @@ ${error.text}`;
|
|
|
57029
57186
|
stepFrom = now;
|
|
57030
57187
|
};
|
|
57031
57188
|
const transcript = openTranscript(startedAt);
|
|
57032
|
-
|
|
57189
|
+
closeSync7(transcript.fd);
|
|
57033
57190
|
step("transcript");
|
|
57034
57191
|
const playAnswer = asParams(JSON.parse(answer.content[0]?.text ?? "{}"));
|
|
57035
57192
|
const which = playOf(playAnswer);
|
|
@@ -57130,7 +57287,7 @@ ${error.text}`;
|
|
|
57130
57287
|
}
|
|
57131
57288
|
let handle;
|
|
57132
57289
|
try {
|
|
57133
|
-
handle =
|
|
57290
|
+
handle = openSync8(game.errorReport, "r");
|
|
57134
57291
|
} catch {
|
|
57135
57292
|
return;
|
|
57136
57293
|
}
|
|
@@ -57164,7 +57321,7 @@ ${error.text}`;
|
|
|
57164
57321
|
game.log.append("stderr", reported);
|
|
57165
57322
|
}
|
|
57166
57323
|
} finally {
|
|
57167
|
-
|
|
57324
|
+
closeSync7(handle);
|
|
57168
57325
|
}
|
|
57169
57326
|
}
|
|
57170
57327
|
announcedPidNamedBy(run, line) {
|
|
@@ -57184,7 +57341,7 @@ ${error.text}`;
|
|
|
57184
57341
|
return 0;
|
|
57185
57342
|
}
|
|
57186
57343
|
try {
|
|
57187
|
-
return reportAlreadyInTranscript(
|
|
57344
|
+
return reportAlreadyInTranscript(readFileSync42(game.transcript), readFileSync42(report));
|
|
57188
57345
|
} catch (error) {
|
|
57189
57346
|
this.logDebug(`Could not compare a picked-up run's report with its transcript: ${errorMessage(error)}`);
|
|
57190
57347
|
return 0;
|
|
@@ -57207,7 +57364,7 @@ ${error.text}`;
|
|
|
57207
57364
|
if (Date.now() >= deadline) {
|
|
57208
57365
|
return false;
|
|
57209
57366
|
}
|
|
57210
|
-
await
|
|
57367
|
+
await delay9(100);
|
|
57211
57368
|
}
|
|
57212
57369
|
}
|
|
57213
57370
|
async endActiveGame(reason, going, graceful) {
|
|
@@ -57471,7 +57628,7 @@ ${error.text}`;
|
|
|
57471
57628
|
const slot = await slotForCall(true);
|
|
57472
57629
|
const startedAt = Date.now();
|
|
57473
57630
|
const transcript = openTranscript(startedAt);
|
|
57474
|
-
|
|
57631
|
+
closeSync7(transcript.fd);
|
|
57475
57632
|
const profile = profiled ? profilePath(startedAt) : undefined;
|
|
57476
57633
|
const gameEnv = onTheSlot(profiled ? { ...env ?? process.env, [PROFILE_FROM_START]: "1" } : env ?? process.env, slot.lock);
|
|
57477
57634
|
let launched;
|
|
@@ -57531,7 +57688,7 @@ ${error.text}`;
|
|
|
57531
57688
|
}
|
|
57532
57689
|
let handle;
|
|
57533
57690
|
try {
|
|
57534
|
-
handle =
|
|
57691
|
+
handle = openSync8(game.transcript, "r");
|
|
57535
57692
|
} catch {
|
|
57536
57693
|
return;
|
|
57537
57694
|
}
|
|
@@ -57545,7 +57702,7 @@ ${error.text}`;
|
|
|
57545
57702
|
game.readOffset += read;
|
|
57546
57703
|
game.log.append("transcript", buffer.subarray(0, read));
|
|
57547
57704
|
} finally {
|
|
57548
|
-
|
|
57705
|
+
closeSync7(handle);
|
|
57549
57706
|
}
|
|
57550
57707
|
}
|
|
57551
57708
|
async pickUpWhatTheEditorIsPlaying() {
|
|
@@ -57942,7 +58099,7 @@ ${error.text}`;
|
|
|
57942
58099
|
};
|
|
57943
58100
|
const going = await this.runStillGoing(run);
|
|
57944
58101
|
const projectPath = run.projectPath;
|
|
57945
|
-
const projectGone = projectPath !== null && !existsSync36(
|
|
58102
|
+
const projectGone = projectPath !== null && !existsSync36(join46(projectPath, "project.godot"));
|
|
57946
58103
|
if (projectGone && callersName !== undefined) {
|
|
57947
58104
|
return this.createErrorResponse(`callersOf reads the project's scripts, and the run's project at ${projectPath} has no project.godot any more.`, ["editor_run profile without callersOf answers with the profile itself"]);
|
|
57948
58105
|
}
|
|
@@ -58100,7 +58257,7 @@ ${error.text}`;
|
|
|
58100
58257
|
return true;
|
|
58101
58258
|
}
|
|
58102
58259
|
}
|
|
58103
|
-
await
|
|
58260
|
+
await delay9(100);
|
|
58104
58261
|
}
|
|
58105
58262
|
return false;
|
|
58106
58263
|
}
|
|
@@ -58130,7 +58287,7 @@ ${error.text}`;
|
|
|
58130
58287
|
break;
|
|
58131
58288
|
}
|
|
58132
58289
|
}
|
|
58133
|
-
await
|
|
58290
|
+
await delay9(RUN_POLL_MS);
|
|
58134
58291
|
}
|
|
58135
58292
|
if (!await this.runStillGoing(run)) {
|
|
58136
58293
|
return await this.handleGetDebugOutput(args);
|
|
@@ -58192,7 +58349,7 @@ ${error.text}`;
|
|
|
58192
58349
|
if (Date.now() >= until) {
|
|
58193
58350
|
return false;
|
|
58194
58351
|
}
|
|
58195
|
-
await
|
|
58352
|
+
await delay9(50);
|
|
58196
58353
|
}
|
|
58197
58354
|
}
|
|
58198
58355
|
async handleEditorConsole(args) {
|
|
@@ -58349,7 +58506,7 @@ ${error.text}`;
|
|
|
58349
58506
|
notes.push(endedWithoutACode({
|
|
58350
58507
|
throughEditor: run.throughEditor,
|
|
58351
58508
|
announced: announced !== undefined || run.announcedPid !== undefined,
|
|
58352
|
-
couldAnnounce: run.projectPath !== null && existsSync36(
|
|
58509
|
+
couldAnnounce: run.projectPath !== null && existsSync36(join46(run.projectPath, RUNTIME_SCRIPT)),
|
|
58353
58510
|
endedHere: typeof run.endedHere === "string",
|
|
58354
58511
|
pickedUp: run.pickedUp === true
|
|
58355
58512
|
}));
|
|
@@ -58607,6 +58764,66 @@ ${error.text}`;
|
|
|
58607
58764
|
return error instanceof BridgeTimeout;
|
|
58608
58765
|
}
|
|
58609
58766
|
}
|
|
58767
|
+
async handleEditorCapture(args) {
|
|
58768
|
+
const projectPath = readNonEmptyString(args, "projectPath") ?? "";
|
|
58769
|
+
const saveTo = readNonEmptyString(args, "outputPath") ?? null;
|
|
58770
|
+
const sendsThePicture = readBoolean(args, "inline") !== false;
|
|
58771
|
+
if (!sendsThePicture && saveTo === null) {
|
|
58772
|
+
return this.createErrorResponse("inline: false sends no picture back, so it needs outputPath to save one to.", [
|
|
58773
|
+
"Pass outputPath, an absolute path ending in .png outside the project",
|
|
58774
|
+
"Or leave inline out to have the picture sent"
|
|
58775
|
+
]);
|
|
58776
|
+
}
|
|
58777
|
+
if (saveTo !== null) {
|
|
58778
|
+
const refusal = captureDestinationRefusal(saveTo, [
|
|
58779
|
+
...projectPath === "" ? [] : [projectPath],
|
|
58780
|
+
...this.ownProject === null ? [] : [this.ownProject]
|
|
58781
|
+
]);
|
|
58782
|
+
if (refusal !== null) {
|
|
58783
|
+
return this.createErrorResponse(refusal);
|
|
58784
|
+
}
|
|
58785
|
+
}
|
|
58786
|
+
const folder = scratchDirectory("editor-capture");
|
|
58787
|
+
const picture = join46(folder, "capture.png");
|
|
58788
|
+
try {
|
|
58789
|
+
const asked = await this.handleViaBridge("capture_editor_view", { ...args, output_path: picture }, "editor_capture");
|
|
58790
|
+
if (asked.isError === true) {
|
|
58791
|
+
return asked;
|
|
58792
|
+
}
|
|
58793
|
+
const payload = asParams(JSON.parse(asked.content[0]?.text ?? "{}"));
|
|
58794
|
+
const returned = readString(payload, "path");
|
|
58795
|
+
if (readString(payload, "type") !== "screenshot_file" || returned === undefined) {
|
|
58796
|
+
return this.createErrorResponse(`The editor answered a capture with ${JSON.stringify(payload)}`);
|
|
58797
|
+
}
|
|
58798
|
+
if (normalize2(returned) !== normalize2(picture)) {
|
|
58799
|
+
return this.createErrorResponse(`Rejected capture file path outside the managed capture path: '${returned}'`);
|
|
58800
|
+
}
|
|
58801
|
+
if (saveTo !== null) {
|
|
58802
|
+
try {
|
|
58803
|
+
copyFileSync2(picture, saveTo, constants.COPYFILE_EXCL);
|
|
58804
|
+
} catch (error) {
|
|
58805
|
+
return this.createErrorResponse(`The capture was taken and could not be saved at ${saveTo}: ${errorMessage(error)}`);
|
|
58806
|
+
}
|
|
58807
|
+
}
|
|
58808
|
+
const sent = sendsThePicture ? pictureToSend(picture, readString(payload, "jpegPath")) : null;
|
|
58809
|
+
const view = readString(payload, "view") === "3d" ? "3D" : "2D";
|
|
58810
|
+
const drawnAt = payload["drawnAt"];
|
|
58811
|
+
const scaled = isRecord(drawnAt) ? `, scaled from ${String(drawnAt["width"])}x${String(drawnAt["height"])}` : "";
|
|
58812
|
+
const unsaved = payload["unsavedInEditor"] === true ? ". The picture includes the scene's unsaved changes in the editor" : "";
|
|
58813
|
+
const how = sent === null ? ". Not sent, as inline: false asked" : sent.mimeType === "image/jpeg" ? `. Sent as JPEG, ${kilobytes(sent.bytes)} against the PNG's ${kilobytes(sent.pngBytes)}${saveTo === null ? "" : ", and the saved PNG is lossless"}` : "";
|
|
58814
|
+
return {
|
|
58815
|
+
content: [
|
|
58816
|
+
{
|
|
58817
|
+
type: "text",
|
|
58818
|
+
text: `The editor's ${view} view of ${readString(payload, "scenePath") ?? "the scene"}: ${String(readNumber(payload, "width") ?? 0)}x${String(readNumber(payload, "height") ?? 0)} png${scaled}${unsaved}${saveTo === null ? "" : `. Saved to ${saveTo}`}${how}`
|
|
58819
|
+
},
|
|
58820
|
+
...sent === null ? [] : [{ type: "image", data: sent.data, mimeType: sent.mimeType }]
|
|
58821
|
+
]
|
|
58822
|
+
};
|
|
58823
|
+
} finally {
|
|
58824
|
+
discard(folder);
|
|
58825
|
+
}
|
|
58826
|
+
}
|
|
58610
58827
|
async handleRescanFilesystem(args) {
|
|
58611
58828
|
const timeoutMs = readPositiveNumber(args, "timeoutMs") ?? 30000;
|
|
58612
58829
|
const started = Date.now();
|
|
@@ -59422,17 +59639,25 @@ ${error.text}`;
|
|
|
59422
59639
|
]);
|
|
59423
59640
|
}
|
|
59424
59641
|
}
|
|
59425
|
-
const
|
|
59426
|
-
|
|
59427
|
-
|
|
59428
|
-
|
|
59429
|
-
|
|
59642
|
+
const run = async () => {
|
|
59643
|
+
const declaring = declaredButUncached(projectPath, typeName);
|
|
59644
|
+
if (declaring !== null) {
|
|
59645
|
+
const rebuilt = await this.rebuildClassCache(projectPath);
|
|
59646
|
+
if (!rebuilt.ok) {
|
|
59647
|
+
return this.answer(rebuilt);
|
|
59648
|
+
}
|
|
59430
59649
|
}
|
|
59431
|
-
|
|
59432
|
-
|
|
59433
|
-
|
|
59434
|
-
|
|
59435
|
-
|
|
59650
|
+
const outcome = await this.operation("run_editor_tool", { command, arguments: contained.value }, projectPath);
|
|
59651
|
+
if (!outcome.ok) {
|
|
59652
|
+
return this.answer(outcome);
|
|
59653
|
+
}
|
|
59654
|
+
return this.headlessAnswer(outcome, effect, typeName, declaring);
|
|
59655
|
+
};
|
|
59656
|
+
const written = readNonEmptyString(contained.value, "scenePath") ?? readNonEmptyString(contained.value, "resourcePath");
|
|
59657
|
+
const target = effect === "writes" && written !== undefined ? resolveWithinProject(projectPath, written) : null;
|
|
59658
|
+
return target?.ok === true ? await inTurnFor(projectPath, target.absolutePath, run) : await run();
|
|
59659
|
+
}
|
|
59660
|
+
headlessAnswer(outcome, effect, typeName, declaring) {
|
|
59436
59661
|
return this.jsonTextResponse({
|
|
59437
59662
|
...outcome.payload,
|
|
59438
59663
|
...declaring === null ? {} : {
|
|
@@ -59691,7 +59916,7 @@ ${error.text}`;
|
|
|
59691
59916
|
}
|
|
59692
59917
|
const escaped = symbol.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
59693
59918
|
const declared = new RegExp(`^\\s*(?:@\\w+(?:\\([^)]*\\))?\\s+)*class_name\\s+${escaped}\\b`, "m");
|
|
59694
|
-
if (declared.test(
|
|
59919
|
+
if (declared.test(readFileSync42(scriptPath, "utf8"))) {
|
|
59695
59920
|
return null;
|
|
59696
59921
|
}
|
|
59697
59922
|
const checked = await this.classesTheEditorCannotSee(args);
|
|
@@ -59889,7 +60114,7 @@ ${error.text}`;
|
|
|
59889
60114
|
async ownRunOnItsWay(projectPath, budgetMs) {
|
|
59890
60115
|
const run = await this.currentRun();
|
|
59891
60116
|
const project = projectPath ?? run?.projectPath ?? null;
|
|
59892
|
-
if (run === null || project === null || run.announcedPid !== undefined || run.projectPath !== null && !isSameDirectory(run.projectPath, project) || !existsSync36(
|
|
60117
|
+
if (run === null || project === null || run.announcedPid !== undefined || run.projectPath !== null && !isSameDirectory(run.projectPath, project) || !existsSync36(join46(project, RUNTIME_SCRIPT)) || !await this.runStillGoing(run)) {
|
|
59893
60118
|
return null;
|
|
59894
60119
|
}
|
|
59895
60120
|
const already = runtimesAnnounced().running;
|
|
@@ -59898,7 +60123,7 @@ ${error.text}`;
|
|
|
59898
60123
|
return !run.announcedBefore.has(one.pid);
|
|
59899
60124
|
}
|
|
59900
60125
|
try {
|
|
59901
|
-
return
|
|
60126
|
+
return statSync21(one.file).mtimeMs >= run.startedAt;
|
|
59902
60127
|
} catch {
|
|
59903
60128
|
return false;
|
|
59904
60129
|
}
|
|
@@ -59999,7 +60224,7 @@ ${error.text}`;
|
|
|
59999
60224
|
return this.createErrorResponse(staleNote);
|
|
60000
60225
|
}
|
|
60001
60226
|
const screenshotDir = expectsScreenshot ? scratchDirectory("runtime-screenshot") : null;
|
|
60002
|
-
const screenshotPath = screenshotDir ?
|
|
60227
|
+
const screenshotPath = screenshotDir ? join46(screenshotDir, "capture.png") : null;
|
|
60003
60228
|
const refused = (message) => this.createErrorResponse(message);
|
|
60004
60229
|
const onGame = {
|
|
60005
60230
|
pid: choice.endpoint.pid,
|
|
@@ -60272,6 +60497,7 @@ var init_server = __esm(() => {
|
|
|
60272
60497
|
init_tool_definitions();
|
|
60273
60498
|
init_tool_reference();
|
|
60274
60499
|
init_update_check();
|
|
60500
|
+
init_write_turns();
|
|
60275
60501
|
TYPE_ARGUMENTS = {
|
|
60276
60502
|
add_node: "nodeType",
|
|
60277
60503
|
create_resource: "resourceType",
|
|
@@ -60344,7 +60570,7 @@ init_bridge_announce();
|
|
|
60344
60570
|
init_engine_launch();
|
|
60345
60571
|
init_engine_slots();
|
|
60346
60572
|
import { existsSync as existsSync37 } from "node:fs";
|
|
60347
|
-
import { dirname as dirname26, join as
|
|
60573
|
+
import { dirname as dirname26, join as join47, resolve as resolve16 } from "node:path";
|
|
60348
60574
|
|
|
60349
60575
|
// src/error-mode.ts
|
|
60350
60576
|
import process8 from "node:process";
|
|
@@ -60694,6 +60920,7 @@ that is running, and the project on disk. ${TOOL_SPECS.length} tools, named \`do
|
|
|
60694
60920
|
| 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 |
|
|
60695
60921
|
| 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 |
|
|
60696
60922
|
| A picture, for a person who asked to see one | \`runtime_capture\` |
|
|
60923
|
+
| What a scene's @tool scripts draw in the editor | \`editor_capture\`, the editor's own 2D or 3D view of the scene. No game runs their editor branch |
|
|
60697
60924
|
|
|
60698
60925
|
These need the runtime autoload, which \`gdharness setup\` registers. \`runtime_capture\` needs a
|
|
60699
60926
|
window and refuses headless rather than handing back the last frame anything drew. On Windows a
|
|
@@ -62615,7 +62842,7 @@ class UsageError extends Refusal {
|
|
|
62615
62842
|
function projectArgument(at) {
|
|
62616
62843
|
const named = args.slice(at).find((value) => !value.startsWith("--"));
|
|
62617
62844
|
const projectPath = resolve16(named ?? ".");
|
|
62618
|
-
if (!existsSync37(
|
|
62845
|
+
if (!existsSync37(join47(projectPath, "project.godot"))) {
|
|
62619
62846
|
throw new UsageError(`Not a Godot project: ${projectPath} holds no project.godot. Run from the project directory, or name it on the command line.`);
|
|
62620
62847
|
}
|
|
62621
62848
|
return projectPath;
|
|
@@ -62679,7 +62906,7 @@ async function registerRuntime(godot, projectPath) {
|
|
|
62679
62906
|
}
|
|
62680
62907
|
const path = (named ?? "").replace(/^res:\/\//, "");
|
|
62681
62908
|
console.log(`${RUNTIME_AUTOLOAD.name} autoload left at ${named}, which this project registers itself`);
|
|
62682
|
-
if (!existsSync37(
|
|
62909
|
+
if (!existsSync37(join47(projectPath, path))) {
|
|
62683
62910
|
console.log(" that file is not in this project, so it boots with a missing script");
|
|
62684
62911
|
}
|
|
62685
62912
|
console.log(` ${useTheShippedLoader(RUNTIME_AUTOLOAD.name, path)}`);
|