gdharness 1.2.20 → 1.2.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/cli.js +51 -9
- package/build/godot/operations/project_config.gd +5 -0
- package/build/index.js +50 -7
- package/package.json +1 -1
package/build/cli.js
CHANGED
|
@@ -8002,6 +8002,11 @@ class GameLog {
|
|
|
8002
8002
|
transcript: null
|
|
8003
8003
|
};
|
|
8004
8004
|
seen = { error: 0, warning: 0, info: 0 };
|
|
8005
|
+
crashed = {
|
|
8006
|
+
stdout: null,
|
|
8007
|
+
stderr: null,
|
|
8008
|
+
transcript: null
|
|
8009
|
+
};
|
|
8005
8010
|
byScript = new Set;
|
|
8006
8011
|
append(source, chunk) {
|
|
8007
8012
|
const decoded = typeof chunk === "string" ? chunk : this.decoders[source].write(chunk);
|
|
@@ -8026,6 +8031,21 @@ class GameLog {
|
|
|
8026
8031
|
if (line.trim() === "") {
|
|
8027
8032
|
return;
|
|
8028
8033
|
}
|
|
8034
|
+
const report = this.crashed[source];
|
|
8035
|
+
if (report !== null) {
|
|
8036
|
+
const said = line.trim();
|
|
8037
|
+
if (!CRASH_FILLER.test(said)) {
|
|
8038
|
+
report.push(said);
|
|
8039
|
+
}
|
|
8040
|
+
return;
|
|
8041
|
+
}
|
|
8042
|
+
if (CRASH.test(line)) {
|
|
8043
|
+
const detail = [];
|
|
8044
|
+
this.entries.push({ index: this.entries.length, severity: "error", source, text: line.trim(), detail });
|
|
8045
|
+
this.crashed[source] = detail;
|
|
8046
|
+
this.lastHeadline[source] = null;
|
|
8047
|
+
return;
|
|
8048
|
+
}
|
|
8029
8049
|
const owner = this.lastHeadline[source];
|
|
8030
8050
|
if (owner !== null && /^\s/.test(line)) {
|
|
8031
8051
|
owner.detail.push(line.trim());
|
|
@@ -8096,9 +8116,11 @@ function answersTo(entry, severity, contains) {
|
|
|
8096
8116
|
const needle = contains.toLowerCase();
|
|
8097
8117
|
return [entry.text, ...entry.detail].some((line) => line.toLowerCase().includes(needle));
|
|
8098
8118
|
}
|
|
8099
|
-
var HEADLINE, COLOUR_CODE, STREAMS, ADMITTED, FRAME, LONGEST_CYCLE = 64;
|
|
8119
|
+
var HEADLINE, CRASH, CRASH_FILLER, COLOUR_CODE, STREAMS, ADMITTED, FRAME, LONGEST_CYCLE = 64;
|
|
8100
8120
|
var init_game_log = __esm(() => {
|
|
8101
8121
|
HEADLINE = /^(USER )?(SCRIPT ERROR|SHADER ERROR|ERROR|WARNING):\s?(.*)$/;
|
|
8122
|
+
CRASH = /^(?:CrashHandlerException|handle_crash): Program crashed with signal \d+/;
|
|
8123
|
+
CRASH_FILLER = /^(?:=+|-- END OF .* BACKTRACE --|\[\d+\] .* - no debug info in .*)$/;
|
|
8102
8124
|
COLOUR_CODE = new RegExp(`${String.fromCharCode(27)}\\[[0-9;]*m`, "g");
|
|
8103
8125
|
STREAMS = ["stdout", "stderr", "transcript"];
|
|
8104
8126
|
ADMITTED = {
|
|
@@ -11082,7 +11104,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
11082
11104
|
{
|
|
11083
11105
|
name: "project_settings",
|
|
11084
11106
|
title: "Project settings",
|
|
11085
|
-
description: "Reads or writes project.godot: settings, autoloads, the main scene, input actions, plugins and audio buses. A write with an editor open on the project is handed to the editor, which takes the settings the write changed or names from the file (named under editorAdopted), including a setting the file already held, since the editor can hold another value after the file was put back, or reloads the bus layout, so it answers the new values and does not save the old ones back over them. editorNote is an editor that could not take the write, one on an addon from before this, which still holds the old values until editor_launch restart. An enabled plugin's code still loads on the editor's next start. set answers old_value and new_value, with was_new true when project.godot did not set the setting before, and was_default true when what it held was the engine's own default, which old_value then is. get answers exists, true for any setting the engine knows, and at_default, true when the value is the engine's own default and so has no line in project.godot.",
|
|
11107
|
+
description: "Reads or writes project.godot: settings, autoloads, the main scene, input actions, plugins and audio buses. A write with an editor open on the project is handed to the editor, which takes the settings the write changed or names from the file (named under editorAdopted), including a setting the file already held, since the editor can hold another value after the file was put back, or reloads the bus layout, so it answers the new values and does not save the old ones back over them. editorNote is an editor that could not take the write, one on an addon from before this, which still holds the old values until editor_launch restart. An enabled plugin's code still loads on the editor's next start. set answers old_value and new_value, with was_new true when project.godot did not set the setting before, and was_default true when what it held was the engine's own default, which old_value then is. get answers exists, true for any setting the engine knows, and at_default, true when the value is the engine's own default and so has no line in project.godot. Each setting under prefix carries at_default too, so a family read separates what the project sets from the engine's defaults; it is absent for a setting the engine registers no default for, such as an input action the project added, which only the project can have set.",
|
|
11086
11108
|
parameters: {
|
|
11087
11109
|
projectPath: PROJECT_PATH,
|
|
11088
11110
|
setting: {
|
|
@@ -11809,8 +11831,8 @@ var init_tool_definitions = __esm(() => {
|
|
|
11809
11831
|
parameters: {
|
|
11810
11832
|
projectPath: {
|
|
11811
11833
|
...PROJECT_PATH,
|
|
11812
|
-
ops: ["start", "check", "stop", "wait"],
|
|
11813
|
-
description: "Absolute path to the project directory, the one holding project.godot. stop, wait: optional, the project whose run is meant; a run of another project is refused rather than touched."
|
|
11834
|
+
ops: ["start", "check", "stop", "wait", "profile"],
|
|
11835
|
+
description: "Absolute path to the project directory, the one holding project.godot. stop, wait, profile: optional, the project whose run is meant; a run of another project is refused rather than touched."
|
|
11814
11836
|
},
|
|
11815
11837
|
scene: {
|
|
11816
11838
|
type: "string",
|
|
@@ -11924,7 +11946,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
11924
11946
|
requires: []
|
|
11925
11947
|
}
|
|
11926
11948
|
},
|
|
11927
|
-
answer: "stop: endedPid is, for a run the editor plays, the number its game announced under, read at the stop rather than remembered, and the operating system hands a freed number to the next process, so it can equal the game before it. stopped is false, with the number under notSignalled, when that number no longer answers as the process the run was started as and nothing was signalled, the note naming which property it failed on, and false with the number under stillGoing when what was told to end was still running ten seconds later. endedThrough says how the stop ended a run that was going: keeper (the process that started the game ended it through the handle it holds), signal (its pid was signalled once shown to still be the run), or editor (the editor playing it was asked). A start that ends the run before it says how under endedPreviousRunThrough, with the same values, and one that refused to signal that run names the property it failed on under previousRunLeftBecause. check: hung is a run killed at timeoutMs, and errors and warnings count what it printed, which comes back under entries with repeats folded together. arguments is the engine command line the check ran, as a start's is. profile: per function its script, the function (Class.name when the script declares a class_name), the line its body's first statement is on, for a lambda the named function it is written in (within), calls, and self and total milliseconds, with functionsCounted, scriptMs (the self time of them all) and coveredMs. complete is true when the engine's own totals arrived, which it sends only when the profiler is switched off: the runtime addon does that as a game quits, so a scene that does its work in _ready and quits is covered whole; otherwise the answer is the frames summed so far and the note says what that misses. Self time counts a call into the engine's own methods as the caller's, and a call through super too, while the overridden function's row counts it again: such a row carries selfIncludesSuper, so self times along an override chain overlap. The profiler's own cost of timing a call lands in the self time of the function called; with the runtime addon it is measured each run on an empty function, as perCallOverheadUs, and a row with at least half its selfMs in it carries overheadMs, since a small function called often costs that much less without the profiler. A function the profiler started inside is left out, since its time would be counted from the engine's start. The milliseconds are time passed while the function ran, not processor time given to it, so they grow when other processes compete for the processor: compare two profiles taken under the same load. They are also summed over every thread: a function run by WorkerThreadPool or a Thread on several threads at once counts each thread's time, the engine does not say which thread a call ran on, and a function waiting for them has the wait in its own self time, so selfMs is not time taken from frames, and scriptMs can exceed coveredMs, which the note then says. A property's get and set blocks are named Class.@name_getter and Class.@name_setter, as the engine names them, and their rows carry property, the property's name. A game the editor plays that was not started with profile: true says, in the answer, that it was profiled from the moment it was asked.",
|
|
11949
|
+
answer: "stop: endedPid is, for a run the editor plays, the number its game announced under, read at the stop rather than remembered, and the operating system hands a freed number to the next process, so it can equal the game before it. stopped is false, with the number under notSignalled, when that number no longer answers as the process the run was started as and nothing was signalled, the note naming which property it failed on, and false with the number under stillGoing when what was told to end was still running ten seconds later. endedThrough says how the stop ended a run that was going: keeper (the process that started the game ended it through the handle it holds), signal (its pid was signalled once shown to still be the run), or editor (the editor playing it was asked). A start that ends the run before it says how under endedPreviousRunThrough, with the same values, and one that refused to signal that run names the property it failed on under previousRunLeftBecause. check: hung is a run killed at timeoutMs, and errors and warnings count what it printed, which comes back under entries with repeats folded together. entries holds the errors and warnings and not the ordinary lines the run printed, which printedLinesNotShown counts; a check given --verbose holds those too, and editor_run start then editor_output reads everything. An engine crash is an error whatever it prints after: the crash handler's report is the entry, and the engine version and the native and GDScript backtraces are its detail, so a crashed run is never booted or clean. arguments is the engine command line the check ran, as a start's is. profile: per function its script, the function (Class.name when the script declares a class_name), the line its body's first statement is on, for a lambda the named function it is written in (within), calls, and self and total milliseconds, with functionsCounted, scriptMs (the self time of them all) and coveredMs. complete is true when the engine's own totals arrived, which it sends only when the profiler is switched off: the runtime addon does that as a game quits, so a scene that does its work in _ready and quits is covered whole; otherwise the answer is the frames summed so far and the note says what that misses. Self time counts a call into the engine's own methods as the caller's, and a call through super too, while the overridden function's row counts it again: such a row carries selfIncludesSuper, so self times along an override chain overlap. The profiler's own cost of timing a call lands in the self time of the function called; with the runtime addon it is measured each run on an empty function, as perCallOverheadUs, and a row with at least half its selfMs in it carries overheadMs, since a small function called often costs that much less without the profiler. A function the profiler started inside is left out, since its time would be counted from the engine's start. The milliseconds are time passed while the function ran, not processor time given to it, so they grow when other processes compete for the processor: compare two profiles taken under the same load. They are also summed over every thread: a function run by WorkerThreadPool or a Thread on several threads at once counts each thread's time, the engine does not say which thread a call ran on, and a function waiting for them has the wait in its own self time, so selfMs is not time taken from frames, and scriptMs can exceed coveredMs, which the note then says. A property's get and set blocks are named Class.@name_getter and Class.@name_setter, as the engine names them, and their rows carry property, the property's name. A game the editor plays that was not started with profile: true says, in the answer, that it was profiled from the moment it was asked.",
|
|
11928
11950
|
defaultOperation: "start"
|
|
11929
11951
|
},
|
|
11930
11952
|
{
|
|
@@ -43558,6 +43580,14 @@ function runtimeVerdict(endpoint, after) {
|
|
|
43558
43580
|
}
|
|
43559
43581
|
const state = after.playingSeen === false ? "the editor has not yet reported the game as playing, which a restarted editor does not until its scan is over" : "the game is still running";
|
|
43560
43582
|
const sized = after.sizedToMs === undefined ? "" : halfAsLongAgain(after.sizedToMs) > after.budgetMs ? `, the longest a start waits unasked though the slowest of its last boots took ${after.sizedToMs}ms to announce,` : `, half as long again as the ${after.sizedToMs}ms the slowest of its last boots took,`;
|
|
43583
|
+
if (!after.running && after.runtimeCameUp === true) {
|
|
43584
|
+
return {
|
|
43585
|
+
listening: false,
|
|
43586
|
+
mayYetAnnounce: false,
|
|
43587
|
+
note: "the game's runtime came up and the game quit before this start could reach it, so there is nothing to talk to: editor_output has what it printed, and a scene that is to be inspected has to stay running past its first frame",
|
|
43588
|
+
heldAt: null
|
|
43589
|
+
};
|
|
43590
|
+
}
|
|
43561
43591
|
return {
|
|
43562
43592
|
listening: false,
|
|
43563
43593
|
mayYetAnnounce: after.running,
|
|
@@ -47119,15 +47149,20 @@ class GodotServer {
|
|
|
47119
47149
|
note: `${fresh.length} games of this project announced at once and none could be told from the process tree as this run's own game rather than one it started, so none is taken as its runtime. editor_status lists them under runtimes.`
|
|
47120
47150
|
};
|
|
47121
47151
|
}
|
|
47152
|
+
const running = endpoint !== null || going !== null && await this.runStillGoing(going);
|
|
47153
|
+
if (going !== null && !running) {
|
|
47154
|
+
this.drainTranscript(going);
|
|
47155
|
+
}
|
|
47122
47156
|
return runtimeVerdict(endpoint, {
|
|
47123
47157
|
addon: true,
|
|
47124
47158
|
budgetMs,
|
|
47125
47159
|
...budget.sizedToMs === undefined ? {} : { sizedToMs: budget.sizedToMs },
|
|
47126
47160
|
heldAt: this.dapClient?.whereItStopped() ?? null,
|
|
47127
|
-
running
|
|
47161
|
+
running,
|
|
47128
47162
|
withArgs,
|
|
47129
47163
|
servesAProject: this.ownProject !== null,
|
|
47130
|
-
...going?.throughEditor === true ? { playingSeen: going.seenPlaying === true } : {}
|
|
47164
|
+
...going?.throughEditor === true ? { playingSeen: going.seenPlaying === true } : {},
|
|
47165
|
+
runtimeCameUp: going !== null && !running && going.log.all.some((entry) => entry.text.startsWith(RUNTIME_LISTENING_LINE))
|
|
47131
47166
|
});
|
|
47132
47167
|
}
|
|
47133
47168
|
announceBudget(args, projectPath, scene) {
|
|
@@ -47983,6 +48018,8 @@ class GodotServer {
|
|
|
47983
48018
|
await boot.settled;
|
|
47984
48019
|
const errors = boot.log.count("error");
|
|
47985
48020
|
const warnings = boot.log.count("warning");
|
|
48021
|
+
const floor = engineArgs.includes("--verbose") ? "info" : "warning";
|
|
48022
|
+
const printed = boot.log.count("info");
|
|
47986
48023
|
return this.jsonTextResponse({
|
|
47987
48024
|
booted: !hung && boot.exitCode === 0 && errors === 0,
|
|
47988
48025
|
hung,
|
|
@@ -47996,7 +48033,8 @@ class GodotServer {
|
|
|
47996
48033
|
argsNote: engineFlagsNote(userArgs),
|
|
47997
48034
|
...scanWaitAnswer(scanned),
|
|
47998
48035
|
savesNote: asked.savesIn !== undefined && savesStayPut() ? SAVES_NOT_MOVED_NOTE : undefined,
|
|
47999
|
-
|
|
48036
|
+
printedLinesNotShown: floor === "warning" && printed > 0 ? printed : undefined,
|
|
48037
|
+
...foldedEntriesAnswer(boot.log, floor)
|
|
48000
48038
|
});
|
|
48001
48039
|
}
|
|
48002
48040
|
async handleRunProfile(args) {
|
|
@@ -48005,6 +48043,10 @@ class GodotServer {
|
|
|
48005
48043
|
if (!run) {
|
|
48006
48044
|
return this.createErrorResponse(this.nothingOfOursIsRunning());
|
|
48007
48045
|
}
|
|
48046
|
+
const elsewhere = this.anotherProjectsRun(run.projectPath, args, "read");
|
|
48047
|
+
if (elsewhere) {
|
|
48048
|
+
return elsewhere;
|
|
48049
|
+
}
|
|
48008
48050
|
const limit = readPositiveNumber(args, "limit") ?? PROFILE_FUNCTIONS;
|
|
48009
48051
|
const callersName = readString(args, "callersOf");
|
|
48010
48052
|
if (callersName !== undefined && !callersName.replace(/@([^.@]+)_(?:getter|setter)$/, "$1").split(".").every(isIdentifier)) {
|
|
@@ -49374,7 +49416,7 @@ async function runGodotServer() {
|
|
|
49374
49416
|
const server = new GodotServer;
|
|
49375
49417
|
await server.run();
|
|
49376
49418
|
}
|
|
49377
|
-
var UPDATE_NOTICE_EVERY = 500, FEEDBACK_NOTICE_EVERY = 250, run4, __dirname2, EDITOR_SERVERS_START_MS = 20000, HOLD_PING_MS = 2000, SLOWEST_FRAME_RATE = 20, PLAY_STARTS_WITHIN_MS = 30000, KEEPER_ANSWER_MS = 15000, EXIT_REPORTED_WITHIN_MS = 2000, CONSOLE_WRAPPER, LONGEST_BOOT_NOTED_MS, PROFILE_SEND_MS = 5000, MOST_GROUPS = 20, VALUE_NAMES, BRIDGE_RETRY_MS = 2000, SUCCESSOR_CHECK_MS = 1e4, CONSOLE_WATCH_MS = 250, BOUNDARY_WAIT_MS = 2000, HANDOVER_MS, HANDOVER_POLL_MS = 250, CACHE_WRITE_MS = 2000, STREAMS_GRACE_MS = 2000, PLAYING_STATUS_MS = 1000, SCAN_START_MS = 2000, SCAN_SETTLED_MS = 500, REIMPORT_TIMEOUT_MS = 300000, OPS_ELSEWHERE, AUDIO_BUS_OPS, STALLED_AFTER_MS = 30000, TEST_HEARTBEAT_MS = 20000, DEPENDED_FAILED = "Failed to compile depended scripts", WAIT_FOR_RUN_MS = 600000, RUN_POLL_MS = 250, PATH_SOLUTIONS, PROJECT_FILE_ARGUMENTS, DEBUG_STATE_CALLS, PROJECT_INFO_SECTIONS2, RUNNER_DIRECTORY = "addons/gdUnit4/", PROFILE_FUNCTIONS = 30, PROFILER_ON_MS = 3000, PROPERTY_PLACES = "It is a property, so each place says whether it writes it (access write), which runs its set block, or reads it (access read), which runs its get block: withinCalls on a read says nothing of the set block, nor on a write of the get block unless the write is a compound one such as +=. Inside the property's own blocks its name is the value stored, which the engine reads and writes without running them, so those lines are not listed.", CALLERS_FOUND_BY_NAME = "Found by name in the project's scripts, since the engine's profiler counts calls and time per function and records nothing about who made them: a method of the same name on another class is listed too, unless one class was asked about and it is called through another class by name or through a variable declared as one. Each place carries the profile's numbers for the function it is written in.", MOST_ENGINE_ENTRIES = 200, ERROR_ASSERT = "GdUnitGodotErrorAssertImpl.gd", EXTERNAL_MEMBER = 'Could not resolve external class member "', STATIC_LIST_HINT;
|
|
49419
|
+
var UPDATE_NOTICE_EVERY = 500, FEEDBACK_NOTICE_EVERY = 250, run4, __dirname2, EDITOR_SERVERS_START_MS = 20000, HOLD_PING_MS = 2000, SLOWEST_FRAME_RATE = 20, PLAY_STARTS_WITHIN_MS = 30000, KEEPER_ANSWER_MS = 15000, EXIT_REPORTED_WITHIN_MS = 2000, CONSOLE_WRAPPER, LONGEST_BOOT_NOTED_MS, PROFILE_SEND_MS = 5000, MOST_GROUPS = 20, RUNTIME_LISTENING_LINE = "[gdharness] runtime listening on ", VALUE_NAMES, BRIDGE_RETRY_MS = 2000, SUCCESSOR_CHECK_MS = 1e4, CONSOLE_WATCH_MS = 250, BOUNDARY_WAIT_MS = 2000, HANDOVER_MS, HANDOVER_POLL_MS = 250, CACHE_WRITE_MS = 2000, STREAMS_GRACE_MS = 2000, PLAYING_STATUS_MS = 1000, SCAN_START_MS = 2000, SCAN_SETTLED_MS = 500, REIMPORT_TIMEOUT_MS = 300000, OPS_ELSEWHERE, AUDIO_BUS_OPS, STALLED_AFTER_MS = 30000, TEST_HEARTBEAT_MS = 20000, DEPENDED_FAILED = "Failed to compile depended scripts", WAIT_FOR_RUN_MS = 600000, RUN_POLL_MS = 250, PATH_SOLUTIONS, PROJECT_FILE_ARGUMENTS, DEBUG_STATE_CALLS, PROJECT_INFO_SECTIONS2, RUNNER_DIRECTORY = "addons/gdUnit4/", PROFILE_FUNCTIONS = 30, PROFILER_ON_MS = 3000, PROPERTY_PLACES = "It is a property, so each place says whether it writes it (access write), which runs its set block, or reads it (access read), which runs its get block: withinCalls on a read says nothing of the set block, nor on a write of the get block unless the write is a compound one such as +=. Inside the property's own blocks its name is the value stored, which the engine reads and writes without running them, so those lines are not listed.", CALLERS_FOUND_BY_NAME = "Found by name in the project's scripts, since the engine's profiler counts calls and time per function and records nothing about who made them: a method of the same name on another class is listed too, unless one class was asked about and it is called through another class by name or through a variable declared as one. Each place carries the profile's numbers for the function it is written in.", MOST_ENGINE_ENTRIES = 200, ERROR_ASSERT = "GdUnitGodotErrorAssertImpl.gd", EXTERNAL_MEMBER = 'Could not resolve external class member "', STATIC_LIST_HINT;
|
|
49378
49420
|
var init_server = __esm(() => {
|
|
49379
49421
|
init_dist();
|
|
49380
49422
|
init_stdio();
|
|
@@ -82,6 +82,11 @@ func _settings_under(prefix: String) -> Dictionary:
|
|
|
82
82
|
"type": type_string(kind),
|
|
83
83
|
"value": _values.serialize_value(value),
|
|
84
84
|
}
|
|
85
|
+
# As a single get says it, so a family read can be told apart into what the project sets
|
|
86
|
+
# and the engine's defaults: "input/" is ninety actions the engine ships and the project's few.
|
|
87
|
+
var default_value: Variant = ProjectSettings.property_get_revert(name)
|
|
88
|
+
if default_value != null:
|
|
89
|
+
described["at_default"] = value == default_value
|
|
85
90
|
found.append(described)
|
|
86
91
|
|
|
87
92
|
found.sort_custom(func(a: Dictionary, b: Dictionary) -> bool: return a["setting"] < b["setting"])
|
package/build/index.js
CHANGED
|
@@ -28147,6 +28147,8 @@ import { dirname as dirname7, join as join10 } from "node:path";
|
|
|
28147
28147
|
// src/game-log.ts
|
|
28148
28148
|
import { StringDecoder } from "node:string_decoder";
|
|
28149
28149
|
var HEADLINE = /^(USER )?(SCRIPT ERROR|SHADER ERROR|ERROR|WARNING):\s?(.*)$/;
|
|
28150
|
+
var CRASH = /^(?:CrashHandlerException|handle_crash): Program crashed with signal \d+/;
|
|
28151
|
+
var CRASH_FILLER = /^(?:=+|-- END OF .* BACKTRACE --|\[\d+\] .* - no debug info in .*)$/;
|
|
28150
28152
|
var COLOUR_CODE = new RegExp(`${String.fromCharCode(27)}\\[[0-9;]*m`, "g");
|
|
28151
28153
|
function announced(line) {
|
|
28152
28154
|
const match = HEADLINE.exec(line);
|
|
@@ -28261,6 +28263,11 @@ class GameLog {
|
|
|
28261
28263
|
transcript: null
|
|
28262
28264
|
};
|
|
28263
28265
|
seen = { error: 0, warning: 0, info: 0 };
|
|
28266
|
+
crashed = {
|
|
28267
|
+
stdout: null,
|
|
28268
|
+
stderr: null,
|
|
28269
|
+
transcript: null
|
|
28270
|
+
};
|
|
28264
28271
|
byScript = new Set;
|
|
28265
28272
|
append(source, chunk) {
|
|
28266
28273
|
const decoded = typeof chunk === "string" ? chunk : this.decoders[source].write(chunk);
|
|
@@ -28285,6 +28292,21 @@ class GameLog {
|
|
|
28285
28292
|
if (line.trim() === "") {
|
|
28286
28293
|
return;
|
|
28287
28294
|
}
|
|
28295
|
+
const report = this.crashed[source];
|
|
28296
|
+
if (report !== null) {
|
|
28297
|
+
const said = line.trim();
|
|
28298
|
+
if (!CRASH_FILLER.test(said)) {
|
|
28299
|
+
report.push(said);
|
|
28300
|
+
}
|
|
28301
|
+
return;
|
|
28302
|
+
}
|
|
28303
|
+
if (CRASH.test(line)) {
|
|
28304
|
+
const detail = [];
|
|
28305
|
+
this.entries.push({ index: this.entries.length, severity: "error", source, text: line.trim(), detail });
|
|
28306
|
+
this.crashed[source] = detail;
|
|
28307
|
+
this.lastHeadline[source] = null;
|
|
28308
|
+
return;
|
|
28309
|
+
}
|
|
28288
28310
|
const owner = this.lastHeadline[source];
|
|
28289
28311
|
if (owner !== null && /^\s/.test(line)) {
|
|
28290
28312
|
owner.detail.push(line.trim());
|
|
@@ -34114,7 +34136,7 @@ var TOOL_SPECS = [
|
|
|
34114
34136
|
{
|
|
34115
34137
|
name: "project_settings",
|
|
34116
34138
|
title: "Project settings",
|
|
34117
|
-
description: "Reads or writes project.godot: settings, autoloads, the main scene, input actions, plugins and audio buses. A write with an editor open on the project is handed to the editor, which takes the settings the write changed or names from the file (named under editorAdopted), including a setting the file already held, since the editor can hold another value after the file was put back, or reloads the bus layout, so it answers the new values and does not save the old ones back over them. editorNote is an editor that could not take the write, one on an addon from before this, which still holds the old values until editor_launch restart. An enabled plugin's code still loads on the editor's next start. set answers old_value and new_value, with was_new true when project.godot did not set the setting before, and was_default true when what it held was the engine's own default, which old_value then is. get answers exists, true for any setting the engine knows, and at_default, true when the value is the engine's own default and so has no line in project.godot.",
|
|
34139
|
+
description: "Reads or writes project.godot: settings, autoloads, the main scene, input actions, plugins and audio buses. A write with an editor open on the project is handed to the editor, which takes the settings the write changed or names from the file (named under editorAdopted), including a setting the file already held, since the editor can hold another value after the file was put back, or reloads the bus layout, so it answers the new values and does not save the old ones back over them. editorNote is an editor that could not take the write, one on an addon from before this, which still holds the old values until editor_launch restart. An enabled plugin's code still loads on the editor's next start. set answers old_value and new_value, with was_new true when project.godot did not set the setting before, and was_default true when what it held was the engine's own default, which old_value then is. get answers exists, true for any setting the engine knows, and at_default, true when the value is the engine's own default and so has no line in project.godot. Each setting under prefix carries at_default too, so a family read separates what the project sets from the engine's defaults; it is absent for a setting the engine registers no default for, such as an input action the project added, which only the project can have set.",
|
|
34118
34140
|
parameters: {
|
|
34119
34141
|
projectPath: PROJECT_PATH2,
|
|
34120
34142
|
setting: {
|
|
@@ -34841,8 +34863,8 @@ var TOOL_SPECS = [
|
|
|
34841
34863
|
parameters: {
|
|
34842
34864
|
projectPath: {
|
|
34843
34865
|
...PROJECT_PATH2,
|
|
34844
|
-
ops: ["start", "check", "stop", "wait"],
|
|
34845
|
-
description: "Absolute path to the project directory, the one holding project.godot. stop, wait: optional, the project whose run is meant; a run of another project is refused rather than touched."
|
|
34866
|
+
ops: ["start", "check", "stop", "wait", "profile"],
|
|
34867
|
+
description: "Absolute path to the project directory, the one holding project.godot. stop, wait, profile: optional, the project whose run is meant; a run of another project is refused rather than touched."
|
|
34846
34868
|
},
|
|
34847
34869
|
scene: {
|
|
34848
34870
|
type: "string",
|
|
@@ -34956,7 +34978,7 @@ var TOOL_SPECS = [
|
|
|
34956
34978
|
requires: []
|
|
34957
34979
|
}
|
|
34958
34980
|
},
|
|
34959
|
-
answer: "stop: endedPid is, for a run the editor plays, the number its game announced under, read at the stop rather than remembered, and the operating system hands a freed number to the next process, so it can equal the game before it. stopped is false, with the number under notSignalled, when that number no longer answers as the process the run was started as and nothing was signalled, the note naming which property it failed on, and false with the number under stillGoing when what was told to end was still running ten seconds later. endedThrough says how the stop ended a run that was going: keeper (the process that started the game ended it through the handle it holds), signal (its pid was signalled once shown to still be the run), or editor (the editor playing it was asked). A start that ends the run before it says how under endedPreviousRunThrough, with the same values, and one that refused to signal that run names the property it failed on under previousRunLeftBecause. check: hung is a run killed at timeoutMs, and errors and warnings count what it printed, which comes back under entries with repeats folded together. arguments is the engine command line the check ran, as a start's is. profile: per function its script, the function (Class.name when the script declares a class_name), the line its body's first statement is on, for a lambda the named function it is written in (within), calls, and self and total milliseconds, with functionsCounted, scriptMs (the self time of them all) and coveredMs. complete is true when the engine's own totals arrived, which it sends only when the profiler is switched off: the runtime addon does that as a game quits, so a scene that does its work in _ready and quits is covered whole; otherwise the answer is the frames summed so far and the note says what that misses. Self time counts a call into the engine's own methods as the caller's, and a call through super too, while the overridden function's row counts it again: such a row carries selfIncludesSuper, so self times along an override chain overlap. The profiler's own cost of timing a call lands in the self time of the function called; with the runtime addon it is measured each run on an empty function, as perCallOverheadUs, and a row with at least half its selfMs in it carries overheadMs, since a small function called often costs that much less without the profiler. A function the profiler started inside is left out, since its time would be counted from the engine's start. The milliseconds are time passed while the function ran, not processor time given to it, so they grow when other processes compete for the processor: compare two profiles taken under the same load. They are also summed over every thread: a function run by WorkerThreadPool or a Thread on several threads at once counts each thread's time, the engine does not say which thread a call ran on, and a function waiting for them has the wait in its own self time, so selfMs is not time taken from frames, and scriptMs can exceed coveredMs, which the note then says. A property's get and set blocks are named Class.@name_getter and Class.@name_setter, as the engine names them, and their rows carry property, the property's name. A game the editor plays that was not started with profile: true says, in the answer, that it was profiled from the moment it was asked.",
|
|
34981
|
+
answer: "stop: endedPid is, for a run the editor plays, the number its game announced under, read at the stop rather than remembered, and the operating system hands a freed number to the next process, so it can equal the game before it. stopped is false, with the number under notSignalled, when that number no longer answers as the process the run was started as and nothing was signalled, the note naming which property it failed on, and false with the number under stillGoing when what was told to end was still running ten seconds later. endedThrough says how the stop ended a run that was going: keeper (the process that started the game ended it through the handle it holds), signal (its pid was signalled once shown to still be the run), or editor (the editor playing it was asked). A start that ends the run before it says how under endedPreviousRunThrough, with the same values, and one that refused to signal that run names the property it failed on under previousRunLeftBecause. check: hung is a run killed at timeoutMs, and errors and warnings count what it printed, which comes back under entries with repeats folded together. entries holds the errors and warnings and not the ordinary lines the run printed, which printedLinesNotShown counts; a check given --verbose holds those too, and editor_run start then editor_output reads everything. An engine crash is an error whatever it prints after: the crash handler's report is the entry, and the engine version and the native and GDScript backtraces are its detail, so a crashed run is never booted or clean. arguments is the engine command line the check ran, as a start's is. profile: per function its script, the function (Class.name when the script declares a class_name), the line its body's first statement is on, for a lambda the named function it is written in (within), calls, and self and total milliseconds, with functionsCounted, scriptMs (the self time of them all) and coveredMs. complete is true when the engine's own totals arrived, which it sends only when the profiler is switched off: the runtime addon does that as a game quits, so a scene that does its work in _ready and quits is covered whole; otherwise the answer is the frames summed so far and the note says what that misses. Self time counts a call into the engine's own methods as the caller's, and a call through super too, while the overridden function's row counts it again: such a row carries selfIncludesSuper, so self times along an override chain overlap. The profiler's own cost of timing a call lands in the self time of the function called; with the runtime addon it is measured each run on an empty function, as perCallOverheadUs, and a row with at least half its selfMs in it carries overheadMs, since a small function called often costs that much less without the profiler. A function the profiler started inside is left out, since its time would be counted from the engine's start. The milliseconds are time passed while the function ran, not processor time given to it, so they grow when other processes compete for the processor: compare two profiles taken under the same load. They are also summed over every thread: a function run by WorkerThreadPool or a Thread on several threads at once counts each thread's time, the engine does not say which thread a call ran on, and a function waiting for them has the wait in its own self time, so selfMs is not time taken from frames, and scriptMs can exceed coveredMs, which the note then says. A property's get and set blocks are named Class.@name_getter and Class.@name_setter, as the engine names them, and their rows carry property, the property's name. A game the editor plays that was not started with profile: true says, in the answer, that it was profiled from the moment it was asked.",
|
|
34960
34982
|
defaultOperation: "start"
|
|
34961
34983
|
},
|
|
34962
34984
|
{
|
|
@@ -36211,6 +36233,7 @@ function endedToStartThis(ended) {
|
|
|
36211
36233
|
var LONGEST_BOOT_NOTED_MS = 5 * 60000;
|
|
36212
36234
|
var PROFILE_SEND_MS = 5000;
|
|
36213
36235
|
var MOST_GROUPS = 20;
|
|
36236
|
+
var RUNTIME_LISTENING_LINE = "[gdharness] runtime listening on ";
|
|
36214
36237
|
function runtimeVerdict(endpoint, after) {
|
|
36215
36238
|
if (endpoint !== null) {
|
|
36216
36239
|
return { listening: true, pid: endpoint.pid, port: endpoint.port };
|
|
@@ -36232,6 +36255,14 @@ function runtimeVerdict(endpoint, after) {
|
|
|
36232
36255
|
}
|
|
36233
36256
|
const state = after.playingSeen === false ? "the editor has not yet reported the game as playing, which a restarted editor does not until its scan is over" : "the game is still running";
|
|
36234
36257
|
const sized = after.sizedToMs === undefined ? "" : halfAsLongAgain(after.sizedToMs) > after.budgetMs ? `, the longest a start waits unasked though the slowest of its last boots took ${after.sizedToMs}ms to announce,` : `, half as long again as the ${after.sizedToMs}ms the slowest of its last boots took,`;
|
|
36258
|
+
if (!after.running && after.runtimeCameUp === true) {
|
|
36259
|
+
return {
|
|
36260
|
+
listening: false,
|
|
36261
|
+
mayYetAnnounce: false,
|
|
36262
|
+
note: "the game's runtime came up and the game quit before this start could reach it, so there is nothing to talk to: editor_output has what it printed, and a scene that is to be inspected has to stay running past its first frame",
|
|
36263
|
+
heldAt: null
|
|
36264
|
+
};
|
|
36265
|
+
}
|
|
36235
36266
|
return {
|
|
36236
36267
|
listening: false,
|
|
36237
36268
|
mayYetAnnounce: after.running,
|
|
@@ -39863,15 +39894,20 @@ class GodotServer {
|
|
|
39863
39894
|
note: `${fresh.length} games of this project announced at once and none could be told from the process tree as this run's own game rather than one it started, so none is taken as its runtime. editor_status lists them under runtimes.`
|
|
39864
39895
|
};
|
|
39865
39896
|
}
|
|
39897
|
+
const running = endpoint !== null || going !== null && await this.runStillGoing(going);
|
|
39898
|
+
if (going !== null && !running) {
|
|
39899
|
+
this.drainTranscript(going);
|
|
39900
|
+
}
|
|
39866
39901
|
return runtimeVerdict(endpoint, {
|
|
39867
39902
|
addon: true,
|
|
39868
39903
|
budgetMs,
|
|
39869
39904
|
...budget.sizedToMs === undefined ? {} : { sizedToMs: budget.sizedToMs },
|
|
39870
39905
|
heldAt: this.dapClient?.whereItStopped() ?? null,
|
|
39871
|
-
running
|
|
39906
|
+
running,
|
|
39872
39907
|
withArgs,
|
|
39873
39908
|
servesAProject: this.ownProject !== null,
|
|
39874
|
-
...going?.throughEditor === true ? { playingSeen: going.seenPlaying === true } : {}
|
|
39909
|
+
...going?.throughEditor === true ? { playingSeen: going.seenPlaying === true } : {},
|
|
39910
|
+
runtimeCameUp: going !== null && !running && going.log.all.some((entry) => entry.text.startsWith(RUNTIME_LISTENING_LINE))
|
|
39875
39911
|
});
|
|
39876
39912
|
}
|
|
39877
39913
|
announceBudget(args, projectPath, scene) {
|
|
@@ -40727,6 +40763,8 @@ class GodotServer {
|
|
|
40727
40763
|
await boot.settled;
|
|
40728
40764
|
const errors = boot.log.count("error");
|
|
40729
40765
|
const warnings = boot.log.count("warning");
|
|
40766
|
+
const floor = engineArgs.includes("--verbose") ? "info" : "warning";
|
|
40767
|
+
const printed = boot.log.count("info");
|
|
40730
40768
|
return this.jsonTextResponse({
|
|
40731
40769
|
booted: !hung && boot.exitCode === 0 && errors === 0,
|
|
40732
40770
|
hung,
|
|
@@ -40740,7 +40778,8 @@ class GodotServer {
|
|
|
40740
40778
|
argsNote: engineFlagsNote(userArgs),
|
|
40741
40779
|
...scanWaitAnswer(scanned),
|
|
40742
40780
|
savesNote: asked.savesIn !== undefined && savesStayPut() ? SAVES_NOT_MOVED_NOTE : undefined,
|
|
40743
|
-
|
|
40781
|
+
printedLinesNotShown: floor === "warning" && printed > 0 ? printed : undefined,
|
|
40782
|
+
...foldedEntriesAnswer(boot.log, floor)
|
|
40744
40783
|
});
|
|
40745
40784
|
}
|
|
40746
40785
|
async handleRunProfile(args) {
|
|
@@ -40749,6 +40788,10 @@ class GodotServer {
|
|
|
40749
40788
|
if (!run) {
|
|
40750
40789
|
return this.createErrorResponse(this.nothingOfOursIsRunning());
|
|
40751
40790
|
}
|
|
40791
|
+
const elsewhere = this.anotherProjectsRun(run.projectPath, args, "read");
|
|
40792
|
+
if (elsewhere) {
|
|
40793
|
+
return elsewhere;
|
|
40794
|
+
}
|
|
40752
40795
|
const limit = readPositiveNumber(args, "limit") ?? PROFILE_FUNCTIONS;
|
|
40753
40796
|
const callersName = readString(args, "callersOf");
|
|
40754
40797
|
if (callersName !== undefined && !callersName.replace(/@([^.@]+)_(?:getter|setter)$/, "$1").split(".").every(isIdentifier)) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gdharness",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.22",
|
|
4
4
|
"mcpName": "io.github.Aureliolo/gdharness",
|
|
5
5
|
"description": "A harness for driving a Godot 4 project from an agent: editor addons, a runtime bridge into the running game, an MCP server in front of them, and a CLI that installs and diagnoses the Godot side.",
|
|
6
6
|
"type": "module",
|