gdharness 1.2.0 → 1.2.1
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 +23 -9
- package/build/godot/addons/gdharness_runtime/runtime_autoload.gd +2 -2
- package/build/godot/addons/gdharness_runtime/runtime_input.gd +5 -2
- package/build/godot/addons/gdharness_runtime/runtime_targets.gd +87 -0
- package/build/godot/addons/gdharness_runtime/runtime_typing.gd +6 -1
- package/build/index.js +23 -9
- package/build/keeper.js +4 -2
- package/package.json +1 -1
package/build/cli.js
CHANGED
|
@@ -18421,7 +18421,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
18421
18421
|
},
|
|
18422
18422
|
{
|
|
18423
18423
|
name: "editor_output",
|
|
18424
|
-
description: "What the project started by editor_run has printed, as entries with a severity: the errors and warnings the engine reported, each with where it happened, and everything else as info. For a run the editor plays the reports come from the game itself, through the runtime addon, since the editor's debugger relays what a game prints and not what it reports; a played game without the runtime addon answers here with its prints alone, with errorsUnread true and no clean, since what it raised reaches nothing here. Answers with the counts and the verdict as well as the entries. heldAt is three answers: null for a run this server knows is not held, running or over, an object for one it knows is held at a breakpoint, whether the adapter said so or the runtime did by accepting a connection and never answering, and absent with heldUnknown beside it for a server that connected to the debugger after the stop and has no runtime to ask, since the adapter reports a stop only to the sessions connected when it happened. A run that has quit still answers here, with running false and its exit code, until the next one starts; a run ended by a signal has no exit code, and exitSignal names the signal instead. A run this server started also answers with transcript, the file both its streams are written to: uncapped, written while the run is going, and the thing to read or tail for a long run rather than the engine's own log, which every engine start rotates away. Read the file for output and this answer for state: endedBy says whether gdharness ended the run, and a transcript that has stopped growing is a run between prints rather than a run that is over. running is asked of the operating system whenever there is a process to ask about, which is every run this server started and every editor-played run whose game announced its runtime. For an editor-played run that never announced, there is no process id here and running is the editor's answer, which can lag: one measured elsewhere reported a game as playing for fifteen seconds after its process had been ended from outside. editor_status names that game under runtimes, which does not depend on the editor answering. op: \"editor\" answers about the editor itself instead, which is a different log and the only way anything here can read what an editor printed: its console reaches no plugin, so the addon cannot scrape it, and an editor gdharness opened is asked for a log file as it starts. That covers the whole session from the editor's first line, which is where the output worth reading usually is, since the class cache, the plugins and the language server all say what they have to say before any harness has connected. The answer carries the entries in the order they were printed and, beside them under repeated, each message shape several lines share: how many, what varied in each slot of it and how many different values stood there, the first occurrence, and the ordinary lines above the group, found by walking back past the group rather than by counting lines. An editor somebody opened by hand has no console here and is refused saying so rather than answered empty, because an editor that printed nothing and an editor nobody captured are not the same thing.",
|
|
18424
|
+
description: "What the project started by editor_run has printed, as entries with a severity: the errors and warnings the engine reported, each with where it happened, and everything else as info. For a run the editor plays the reports come from the game itself, through the runtime addon, since the editor's debugger relays what a game prints and not what it reports; a played game without the runtime addon answers here with its prints alone, with errorsUnread true and no clean, since what it raised reaches nothing here. Answers with the counts and the verdict as well as the entries. heldAt is three answers: null for a run this server knows is not held, running or over, an object for one it knows is held at a breakpoint, whether the adapter said so or the runtime did by accepting a connection and never answering, and absent with heldUnknown beside it for a server that connected to the debugger after the stop and has no runtime to ask, since the adapter reports a stop only to the sessions connected when it happened. A run that has quit still answers here, with running false and its exit code, until the next one starts; a run ended by a signal has no exit code, and exitSignal names the signal instead. A run this server started also answers with transcript, the file both its streams are written to: uncapped, written while the run is going, and the thing to read or tail for a long run rather than the engine's own log, which every engine start rotates away. Read the file for output and this answer for state: endedBy says whether gdharness ended the run, and a transcript that has stopped growing is a run between prints rather than a run that is over. running is asked of the operating system whenever there is a process to ask about, which is every run this server started and every editor-played run whose game announced its runtime. For an editor-played run that never announced, there is no process id here and running is the editor's answer, which can lag: one measured elsewhere reported a game as playing for fifteen seconds after its process had been ended from outside. editor_status names that game under runtimes, which does not depend on the editor answering. op: \"editor\" answers about the editor itself instead, which is a different log and the only way anything here can read what an editor printed: its console reaches no plugin, so the addon cannot scrape it, and an editor gdharness opened is asked for a log file as it starts. That covers the whole session from the editor's first line, which is where the output worth reading usually is, since the class cache, the plugins and the language server all say what they have to say before any harness has connected. The answer carries the entries in the order they were printed and, beside them under repeated, each message shape several lines share: how many, what varied in each slot of it and how many different values stood there, the first occurrence, and the ordinary lines above the group, found by walking back past the group rather than by counting lines. An editor somebody opened by hand has no console here and is refused saying so rather than answered empty, because an editor that printed nothing and an editor nobody captured are not the same thing. startedAt is when the run began and elapsedMs how long it has been going; for a run that is over, endedAt is when it ended and elapsedMs how long it ran, and both are left out when nobody saw the end, as for a game closed in the editor or a run picked up from a note written before ends were recorded.",
|
|
18425
18425
|
parameters: {
|
|
18426
18426
|
severity: {
|
|
18427
18427
|
type: "string",
|
|
@@ -18469,7 +18469,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
18469
18469
|
},
|
|
18470
18470
|
{
|
|
18471
18471
|
name: "editor_status",
|
|
18472
|
-
description: "Whether the editor addon is connected, which Godot answers, whether the editor is playing something, and whether a game with the runtime addon is reachable: runtimeConnected counts the games of the project this server serves, 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. 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. 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; null when there is no run at all. 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, and editor_launch restart binds it again. 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. 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 gdharness upgrade, 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. Every runtime_* answer from such a game carries addonIsStale and staleNote too. 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 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, 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. 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 while that is read 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.",
|
|
18472
|
+
description: "Whether the editor addon is connected, which Godot answers, whether the editor is playing something, and whether a game with the runtime addon is reachable: runtimeConnected counts the games of the project this server serves, 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. 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. 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; 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, and editor_launch restart binds it again. 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. 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 gdharness upgrade, 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. Every runtime_* answer from such a game carries addonIsStale and staleNote too. 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 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, 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. 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 while that is read 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.",
|
|
18473
18473
|
parameters: {},
|
|
18474
18474
|
requires: []
|
|
18475
18475
|
},
|
|
@@ -42202,7 +42202,8 @@ function recordAt(path) {
|
|
|
42202
42202
|
arguments: Array.isArray(args) ? args.filter((value) => typeof value === "string") : [],
|
|
42203
42203
|
...typeof fields["command"] === "string" ? { command: fields["command"] } : {},
|
|
42204
42204
|
...typeof fields["exitCode"] === "number" ? { exitCode: fields["exitCode"] } : {},
|
|
42205
|
-
...typeof fields["exitSignal"] === "string" ? { exitSignal: fields["exitSignal"] } : {}
|
|
42205
|
+
...typeof fields["exitSignal"] === "string" ? { exitSignal: fields["exitSignal"] } : {},
|
|
42206
|
+
...typeof fields["endedAt"] === "number" ? { endedAt: fields["endedAt"] } : {}
|
|
42206
42207
|
};
|
|
42207
42208
|
}
|
|
42208
42209
|
async function runningAs(pid) {
|
|
@@ -43482,7 +43483,7 @@ function profileAnswer(reading, limit) {
|
|
|
43482
43483
|
notes.push(`${table.superCallers === 1 ? "One function calls" : `${table.superCallers} functions call`} through super, marked selfIncludesSuper: the engine takes every other call out of the caller's self time, and not a super call, so ${table.superCallers === 1 ? "its" : "their"} selfMs holds the overridden function's time, which that function's own row counts again. Self time therefore cannot be added along an override chain, and scriptMs counts the time under a super call once more for each level above it.`);
|
|
43483
43484
|
}
|
|
43484
43485
|
if (table.overheadDominated > 0 && table.perCallUs !== null) {
|
|
43485
|
-
notes.push(`The profiler costs about ${table.perCallUs}µs a call in this run, measured on an empty function the runtime addon calls as it goes on, and that time lands in the self time of the function called. ${table.overheadDominated === 1 ? "One function has" : `${table.overheadDominated} functions have`} at least half ${table.overheadDominated === 1 ? "its" : "their"} selfMs in it, under overheadMs:
|
|
43486
|
+
notes.push(`The profiler costs about ${table.perCallUs}µs a call in this run, measured on an empty function the runtime addon calls as it goes on, and that time lands in the self time of the function called. ${table.overheadDominated === 1 ? "One function has" : `${table.overheadDominated} functions have`} at least half ${table.overheadDominated === 1 ? "its" : "their"} selfMs in it, under overheadMs: that much of its time is the profiler's and goes once the profiler is off, whether the function is inlined or not. What inlining saves is the call itself, which costs something without the profiler too, not the work inside it.`);
|
|
43486
43487
|
}
|
|
43487
43488
|
notes.push(...cappedNotes(totals));
|
|
43488
43489
|
if (totals.unreadable > 0) {
|
|
@@ -45932,7 +45933,9 @@ class GodotServer {
|
|
|
45932
45933
|
return {
|
|
45933
45934
|
pid: run.pid ?? this.announcedPidOf(run) ?? run.announcedPid ?? null,
|
|
45934
45935
|
...run.throughEditor || run.projectPath === null ? {} : { project: run.projectPath },
|
|
45935
|
-
...run.pickedUp === true ? { adopted: true } : {}
|
|
45936
|
+
...run.pickedUp === true ? { adopted: true } : {},
|
|
45937
|
+
startedAt: new Date(run.startedAt).toISOString(),
|
|
45938
|
+
...run.endedAt === undefined ? {} : { endedAt: new Date(run.endedAt).toISOString() }
|
|
45936
45939
|
};
|
|
45937
45940
|
}
|
|
45938
45941
|
async handleEditorStatus() {
|
|
@@ -46705,10 +46708,11 @@ class GodotServer {
|
|
|
46705
46708
|
return "unreached";
|
|
46706
46709
|
}
|
|
46707
46710
|
running.endedThrough = "editor";
|
|
46708
|
-
|
|
46709
|
-
|
|
46711
|
+
const stopped = announced === undefined ? await this.untilTheEditorStopsPlaying() : await untilGone([announced]);
|
|
46712
|
+
if (stopped) {
|
|
46713
|
+
running.endedAt = Date.now();
|
|
46710
46714
|
}
|
|
46711
|
-
return
|
|
46715
|
+
return stopped ? "gone" : "lingering";
|
|
46712
46716
|
}
|
|
46713
46717
|
if (running.pid === null) {
|
|
46714
46718
|
return "gone";
|
|
@@ -46757,6 +46761,7 @@ class GodotServer {
|
|
|
46757
46761
|
if (landed && gone) {
|
|
46758
46762
|
running.exitCode = null;
|
|
46759
46763
|
running.exitSignal = "SIGTERM";
|
|
46764
|
+
running.endedAt ??= Date.now();
|
|
46760
46765
|
}
|
|
46761
46766
|
if (landed) {
|
|
46762
46767
|
running.endedThrough = "signal";
|
|
@@ -46781,6 +46786,7 @@ class GodotServer {
|
|
|
46781
46786
|
if (asked === "signalled" && gone) {
|
|
46782
46787
|
run.exitCode = null;
|
|
46783
46788
|
run.exitSignal = "SIGTERM";
|
|
46789
|
+
run.endedAt ??= Date.now();
|
|
46784
46790
|
}
|
|
46785
46791
|
run.log.record(gone ? "info" : "warning", !gone ? `gdharness had the keeper holding pid ${pid} end it, and it was still running ${stopWaitMs() / 1000} seconds later.` : asked === "signalled" ? `gdharness ended pid ${pid} through the keeper holding it.` : `gdharness asked the keeper holding pid ${pid} to end it, and it had already gone.`);
|
|
46786
46792
|
return gone ? "gone" : "lingering";
|
|
@@ -46828,6 +46834,7 @@ class GodotServer {
|
|
|
46828
46834
|
this.logDebug(`Godot process exited with code ${code ?? "none"}, signal ${signal ?? "none"}`);
|
|
46829
46835
|
started.exitCode = code;
|
|
46830
46836
|
started.exitSignal = code === null ? signal : null;
|
|
46837
|
+
started.endedAt = Date.now();
|
|
46831
46838
|
});
|
|
46832
46839
|
child.on("error", (err) => {
|
|
46833
46840
|
console.error("Failed to start Godot process:", err);
|
|
@@ -47142,6 +47149,9 @@ class GodotServer {
|
|
|
47142
47149
|
adopted.log.finish();
|
|
47143
47150
|
adopted.exitCode = record.exitCode ?? null;
|
|
47144
47151
|
adopted.exitSignal = record.exitSignal ?? null;
|
|
47152
|
+
if (record.endedAt !== undefined) {
|
|
47153
|
+
adopted.endedAt = record.endedAt;
|
|
47154
|
+
}
|
|
47145
47155
|
const exitMayBeComing = !alive(record.pid) && !exited(adopted);
|
|
47146
47156
|
if (!exitMayBeComing) {
|
|
47147
47157
|
adopted.endedUnwatched = !exited(adopted);
|
|
@@ -47435,6 +47445,9 @@ class GodotServer {
|
|
|
47435
47445
|
if (record !== null && record.pid === run.pid && (record.exitCode !== undefined || record.exitSignal !== undefined)) {
|
|
47436
47446
|
run.exitCode = record.exitCode ?? null;
|
|
47437
47447
|
run.exitSignal = record.exitSignal ?? null;
|
|
47448
|
+
if (record.endedAt !== undefined) {
|
|
47449
|
+
run.endedAt = record.endedAt;
|
|
47450
|
+
}
|
|
47438
47451
|
return true;
|
|
47439
47452
|
}
|
|
47440
47453
|
if (Date.now() >= until) {
|
|
@@ -47581,7 +47594,8 @@ class GodotServer {
|
|
|
47581
47594
|
project: run.throughEditor ? undefined : run.projectPath ?? undefined,
|
|
47582
47595
|
transcript: run.transcript ?? undefined,
|
|
47583
47596
|
startedAt: new Date(run.startedAt).toISOString(),
|
|
47584
|
-
|
|
47597
|
+
endedAt: stillRunning(run) || run.endedAt === undefined ? undefined : new Date(run.endedAt).toISOString(),
|
|
47598
|
+
elapsedMs: stillRunning(run) ? Date.now() - run.startedAt : run.endedAt === undefined ? undefined : run.endedAt - run.startedAt,
|
|
47585
47599
|
cpuSeconds,
|
|
47586
47600
|
note: notes.length > 0 ? notes.join(" ") : undefined,
|
|
47587
47601
|
omitted: selected.omitted,
|
|
@@ -201,8 +201,8 @@ func _send_the_profile() -> void:
|
|
|
201
201
|
## What the script profiler costs per call in this game, measured once each time it is switched on.
|
|
202
202
|
##
|
|
203
203
|
## The profiler times every call it counts, and that bookkeeping lands in the self time of the
|
|
204
|
-
## function called
|
|
205
|
-
## second of its own
|
|
204
|
+
## function called, so a function doing almost nothing, called six hundred thousand times, can read
|
|
205
|
+
## as a second of its own that is mostly the profiler's. So an empty function
|
|
206
206
|
## is called here [constant PROFILER_PROBE_CALLS] times while the profiler watches, under the load
|
|
207
207
|
## the game is under, and the server reads its row as the cost of one call.
|
|
208
208
|
func _calibrate_the_profiler() -> void:
|
|
@@ -560,7 +560,7 @@ func click(params: Dictionary) -> Dictionary:
|
|
|
560
560
|
## that does not process while the game is paused is hovered and never handed the event.
|
|
561
561
|
func _takes_no_click(control: Control, node_path: String) -> String:
|
|
562
562
|
if not control.is_visible_in_tree():
|
|
563
|
-
return "%s is not visible, so nothing can click it" % node_path
|
|
563
|
+
return "%s is not visible%s, so nothing can click it" % [node_path, Targets.why_hidden(control)]
|
|
564
564
|
var button: BaseButton = control as BaseButton
|
|
565
565
|
if button != null and button.disabled:
|
|
566
566
|
return "%s is disabled, so clicking it presses nothing" % node_path
|
|
@@ -681,7 +681,10 @@ func _press_button(viewport: Viewport, event: InputEventMouseButton) -> void:
|
|
|
681
681
|
## anything the engine can be asked, so it is not claimed.
|
|
682
682
|
func _click_in_the_world(node_path: String, item: Node3D, params: Dictionary) -> Dictionary:
|
|
683
683
|
if not item.is_visible_in_tree():
|
|
684
|
-
return {
|
|
684
|
+
return {
|
|
685
|
+
"type": "error",
|
|
686
|
+
"message": "%s is not visible%s, so nothing can click it" % [node_path, Targets.why_hidden(item)]
|
|
687
|
+
}
|
|
685
688
|
|
|
686
689
|
var found: Dictionary = Queries.in_frame(item)
|
|
687
690
|
if found.is_empty():
|
|
@@ -376,6 +376,93 @@ static func drawn_over(target: Control) -> Node:
|
|
|
376
376
|
return _cover_of(target, drawn, _areas_of(drawn))
|
|
377
377
|
|
|
378
378
|
|
|
379
|
+
## Why [param node], which is not visible in the tree, is not, as a parenthesis to follow its path:
|
|
380
|
+
## which nodes hide it, nearest first, and what is shown beside the nearest. Reading visible off
|
|
381
|
+
## the node itself says true when only an ancestor is hidden, so without this a caller had nothing
|
|
382
|
+
## to unhide; every hidden one is named because unhiding only the nearest leaves the rest hiding it.
|
|
383
|
+
## "" when nothing on the way up says so.
|
|
384
|
+
static func why_hidden(node: Node) -> String:
|
|
385
|
+
var hiding: Array[Node] = _hiding(node)
|
|
386
|
+
if hiding.is_empty():
|
|
387
|
+
return ""
|
|
388
|
+
var named: Array[String] = []
|
|
389
|
+
for one: Node in hiding:
|
|
390
|
+
if one == node:
|
|
391
|
+
named.append("its own visible is false")
|
|
392
|
+
else:
|
|
393
|
+
named.append("%s is hidden%s" % [one.get_path(), "" if named.is_empty() else " too"])
|
|
394
|
+
var beside: Array[String] = []
|
|
395
|
+
var parent: Node = hiding[0].get_parent()
|
|
396
|
+
if parent != null:
|
|
397
|
+
for sibling: Node in parent.get_children():
|
|
398
|
+
if sibling != hiding[0] and _shown(sibling):
|
|
399
|
+
beside.append(str(sibling.get_path()))
|
|
400
|
+
return " (%s%s)" % [", and ".join(named), _shown_beside(beside)]
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
## "; A, B and C beside it are shown" for [param beside], naming three at most and counting the rest.
|
|
404
|
+
static func _shown_beside(beside: Array[String]) -> String:
|
|
405
|
+
if beside.is_empty():
|
|
406
|
+
return ""
|
|
407
|
+
if beside.size() == 1:
|
|
408
|
+
return "; %s beside it is shown" % beside[0]
|
|
409
|
+
var listed: Array[String] = beside.slice(0, 3)
|
|
410
|
+
var rest: int = beside.size() - listed.size()
|
|
411
|
+
if rest > 0:
|
|
412
|
+
return "; %s and %d more beside it are shown" % [", ".join(listed), rest]
|
|
413
|
+
var last: String = listed.pop_back()
|
|
414
|
+
return "; %s and %s beside it are shown" % [", ".join(listed), last]
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
## The nodes whose visible is false and keeps [param node] from being drawn, nearest first. Walked
|
|
418
|
+
## the way the engine inherits visibility rather than by parent: an item under a plain Node is drawn
|
|
419
|
+
## from its canvas layer, whatever a control above that Node says, so naming that control would send
|
|
420
|
+
## a caller to unhide something that hides nothing.
|
|
421
|
+
static func _hiding(node: Node) -> Array[Node]:
|
|
422
|
+
var hiding: Array[Node] = []
|
|
423
|
+
var at: Node = node
|
|
424
|
+
while at != null:
|
|
425
|
+
var item: CanvasItem = at as CanvasItem
|
|
426
|
+
var spatial: Node3D = at as Node3D
|
|
427
|
+
if item != null:
|
|
428
|
+
if not item.visible:
|
|
429
|
+
hiding.append(item)
|
|
430
|
+
var above: CanvasItem = item.get_parent() as CanvasItem
|
|
431
|
+
if above != null:
|
|
432
|
+
at = above
|
|
433
|
+
continue
|
|
434
|
+
var layer: CanvasLayer = item.get_canvas_layer_node()
|
|
435
|
+
if layer != null and not layer.visible:
|
|
436
|
+
hiding.append(layer)
|
|
437
|
+
at = item.get_viewport()
|
|
438
|
+
elif spatial != null:
|
|
439
|
+
if not spatial.visible:
|
|
440
|
+
hiding.append(spatial)
|
|
441
|
+
at = spatial.get_parent_node_3d()
|
|
442
|
+
if at == null:
|
|
443
|
+
at = spatial.get_viewport()
|
|
444
|
+
else:
|
|
445
|
+
var window: Window = at as Window
|
|
446
|
+
if window != null and not window.visible:
|
|
447
|
+
hiding.append(window)
|
|
448
|
+
at = null
|
|
449
|
+
return hiding
|
|
450
|
+
|
|
451
|
+
|
|
452
|
+
## Whether [param node] is something drawn and on show, for naming what stands beside a hidden one.
|
|
453
|
+
static func _shown(node: Node) -> bool:
|
|
454
|
+
var item: CanvasItem = node as CanvasItem
|
|
455
|
+
if item != null:
|
|
456
|
+
return item.is_visible_in_tree()
|
|
457
|
+
var spatial: Node3D = node as Node3D
|
|
458
|
+
if spatial != null:
|
|
459
|
+
return spatial.is_visible_in_tree()
|
|
460
|
+
var layer: CanvasLayer = node as CanvasLayer
|
|
461
|
+
if layer != null:
|
|
462
|
+
return layer.visible
|
|
463
|
+
return false
|
|
464
|
+
|
|
465
|
+
|
|
379
466
|
## Everything in the tree [param root] heads that a pointer can land on: each control on screen
|
|
380
467
|
## that does not let the pointer through, and each window embedded in another.
|
|
381
468
|
static func _pointer_takers(root: Node) -> Array[Node]:
|
|
@@ -5,6 +5,7 @@ extends RefCounted
|
|
|
5
5
|
|
|
6
6
|
const Menus = preload("runtime_menus.gd")
|
|
7
7
|
const Read = preload("reading.gd")
|
|
8
|
+
const Targets = preload("runtime_targets.gd")
|
|
8
9
|
const Values = preload("runtime_values.gd")
|
|
9
10
|
const Words = preload("runtime_words.gd")
|
|
10
11
|
|
|
@@ -251,7 +252,11 @@ func choose(params: Dictionary) -> Dictionary:
|
|
|
251
252
|
if holder != null and holder.disabled:
|
|
252
253
|
return {"type": "error", "message": "%s is disabled, so its menu cannot be opened" % node_path}
|
|
253
254
|
if holder != null and not holder.is_visible_in_tree():
|
|
254
|
-
return {
|
|
255
|
+
return {
|
|
256
|
+
"type": "error",
|
|
257
|
+
"message":
|
|
258
|
+
"%s is not visible%s, so its menu cannot be opened" % [node_path, Targets.why_hidden(holder)]
|
|
259
|
+
}
|
|
255
260
|
|
|
256
261
|
# Shown first, because a menu nobody has opened has no focus to move and the press would go to
|
|
257
262
|
# whatever is behind it, and before the item is looked for, because opening is when a game fills
|
package/build/index.js
CHANGED
|
@@ -33008,7 +33008,8 @@ function recordAt(path) {
|
|
|
33008
33008
|
arguments: Array.isArray(args) ? args.filter((value) => typeof value === "string") : [],
|
|
33009
33009
|
...typeof fields["command"] === "string" ? { command: fields["command"] } : {},
|
|
33010
33010
|
...typeof fields["exitCode"] === "number" ? { exitCode: fields["exitCode"] } : {},
|
|
33011
|
-
...typeof fields["exitSignal"] === "string" ? { exitSignal: fields["exitSignal"] } : {}
|
|
33011
|
+
...typeof fields["exitSignal"] === "string" ? { exitSignal: fields["exitSignal"] } : {},
|
|
33012
|
+
...typeof fields["endedAt"] === "number" ? { endedAt: fields["endedAt"] } : {}
|
|
33012
33013
|
};
|
|
33013
33014
|
}
|
|
33014
33015
|
async function runningAs(pid) {
|
|
@@ -34497,7 +34498,7 @@ var TOOL_SPECS = [
|
|
|
34497
34498
|
},
|
|
34498
34499
|
{
|
|
34499
34500
|
name: "editor_output",
|
|
34500
|
-
description: "What the project started by editor_run has printed, as entries with a severity: the errors and warnings the engine reported, each with where it happened, and everything else as info. For a run the editor plays the reports come from the game itself, through the runtime addon, since the editor's debugger relays what a game prints and not what it reports; a played game without the runtime addon answers here with its prints alone, with errorsUnread true and no clean, since what it raised reaches nothing here. Answers with the counts and the verdict as well as the entries. heldAt is three answers: null for a run this server knows is not held, running or over, an object for one it knows is held at a breakpoint, whether the adapter said so or the runtime did by accepting a connection and never answering, and absent with heldUnknown beside it for a server that connected to the debugger after the stop and has no runtime to ask, since the adapter reports a stop only to the sessions connected when it happened. A run that has quit still answers here, with running false and its exit code, until the next one starts; a run ended by a signal has no exit code, and exitSignal names the signal instead. A run this server started also answers with transcript, the file both its streams are written to: uncapped, written while the run is going, and the thing to read or tail for a long run rather than the engine's own log, which every engine start rotates away. Read the file for output and this answer for state: endedBy says whether gdharness ended the run, and a transcript that has stopped growing is a run between prints rather than a run that is over. running is asked of the operating system whenever there is a process to ask about, which is every run this server started and every editor-played run whose game announced its runtime. For an editor-played run that never announced, there is no process id here and running is the editor's answer, which can lag: one measured elsewhere reported a game as playing for fifteen seconds after its process had been ended from outside. editor_status names that game under runtimes, which does not depend on the editor answering. op: \"editor\" answers about the editor itself instead, which is a different log and the only way anything here can read what an editor printed: its console reaches no plugin, so the addon cannot scrape it, and an editor gdharness opened is asked for a log file as it starts. That covers the whole session from the editor's first line, which is where the output worth reading usually is, since the class cache, the plugins and the language server all say what they have to say before any harness has connected. The answer carries the entries in the order they were printed and, beside them under repeated, each message shape several lines share: how many, what varied in each slot of it and how many different values stood there, the first occurrence, and the ordinary lines above the group, found by walking back past the group rather than by counting lines. An editor somebody opened by hand has no console here and is refused saying so rather than answered empty, because an editor that printed nothing and an editor nobody captured are not the same thing.",
|
|
34501
|
+
description: "What the project started by editor_run has printed, as entries with a severity: the errors and warnings the engine reported, each with where it happened, and everything else as info. For a run the editor plays the reports come from the game itself, through the runtime addon, since the editor's debugger relays what a game prints and not what it reports; a played game without the runtime addon answers here with its prints alone, with errorsUnread true and no clean, since what it raised reaches nothing here. Answers with the counts and the verdict as well as the entries. heldAt is three answers: null for a run this server knows is not held, running or over, an object for one it knows is held at a breakpoint, whether the adapter said so or the runtime did by accepting a connection and never answering, and absent with heldUnknown beside it for a server that connected to the debugger after the stop and has no runtime to ask, since the adapter reports a stop only to the sessions connected when it happened. A run that has quit still answers here, with running false and its exit code, until the next one starts; a run ended by a signal has no exit code, and exitSignal names the signal instead. A run this server started also answers with transcript, the file both its streams are written to: uncapped, written while the run is going, and the thing to read or tail for a long run rather than the engine's own log, which every engine start rotates away. Read the file for output and this answer for state: endedBy says whether gdharness ended the run, and a transcript that has stopped growing is a run between prints rather than a run that is over. running is asked of the operating system whenever there is a process to ask about, which is every run this server started and every editor-played run whose game announced its runtime. For an editor-played run that never announced, there is no process id here and running is the editor's answer, which can lag: one measured elsewhere reported a game as playing for fifteen seconds after its process had been ended from outside. editor_status names that game under runtimes, which does not depend on the editor answering. op: \"editor\" answers about the editor itself instead, which is a different log and the only way anything here can read what an editor printed: its console reaches no plugin, so the addon cannot scrape it, and an editor gdharness opened is asked for a log file as it starts. That covers the whole session from the editor's first line, which is where the output worth reading usually is, since the class cache, the plugins and the language server all say what they have to say before any harness has connected. The answer carries the entries in the order they were printed and, beside them under repeated, each message shape several lines share: how many, what varied in each slot of it and how many different values stood there, the first occurrence, and the ordinary lines above the group, found by walking back past the group rather than by counting lines. An editor somebody opened by hand has no console here and is refused saying so rather than answered empty, because an editor that printed nothing and an editor nobody captured are not the same thing. startedAt is when the run began and elapsedMs how long it has been going; for a run that is over, endedAt is when it ended and elapsedMs how long it ran, and both are left out when nobody saw the end, as for a game closed in the editor or a run picked up from a note written before ends were recorded.",
|
|
34501
34502
|
parameters: {
|
|
34502
34503
|
severity: {
|
|
34503
34504
|
type: "string",
|
|
@@ -34545,7 +34546,7 @@ var TOOL_SPECS = [
|
|
|
34545
34546
|
},
|
|
34546
34547
|
{
|
|
34547
34548
|
name: "editor_status",
|
|
34548
|
-
description: "Whether the editor addon is connected, which Godot answers, whether the editor is playing something, and whether a game with the runtime addon is reachable: runtimeConnected counts the games of the project this server serves, 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. 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. 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; null when there is no run at all. 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, and editor_launch restart binds it again. 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. 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 gdharness upgrade, 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. Every runtime_* answer from such a game carries addonIsStale and staleNote too. 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 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, 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. 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 while that is read 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.",
|
|
34549
|
+
description: "Whether the editor addon is connected, which Godot answers, whether the editor is playing something, and whether a game with the runtime addon is reachable: runtimeConnected counts the games of the project this server serves, 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. 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. 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; 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, and editor_launch restart binds it again. 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. 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 gdharness upgrade, 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. Every runtime_* answer from such a game carries addonIsStale and staleNote too. 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 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, 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. 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 while that is read 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.",
|
|
34549
34550
|
parameters: {},
|
|
34550
34551
|
requires: []
|
|
34551
34552
|
},
|
|
@@ -35976,7 +35977,7 @@ function profileAnswer(reading, limit) {
|
|
|
35976
35977
|
notes.push(`${table.superCallers === 1 ? "One function calls" : `${table.superCallers} functions call`} through super, marked selfIncludesSuper: the engine takes every other call out of the caller's self time, and not a super call, so ${table.superCallers === 1 ? "its" : "their"} selfMs holds the overridden function's time, which that function's own row counts again. Self time therefore cannot be added along an override chain, and scriptMs counts the time under a super call once more for each level above it.`);
|
|
35977
35978
|
}
|
|
35978
35979
|
if (table.overheadDominated > 0 && table.perCallUs !== null) {
|
|
35979
|
-
notes.push(`The profiler costs about ${table.perCallUs}µs a call in this run, measured on an empty function the runtime addon calls as it goes on, and that time lands in the self time of the function called. ${table.overheadDominated === 1 ? "One function has" : `${table.overheadDominated} functions have`} at least half ${table.overheadDominated === 1 ? "its" : "their"} selfMs in it, under overheadMs:
|
|
35980
|
+
notes.push(`The profiler costs about ${table.perCallUs}µs a call in this run, measured on an empty function the runtime addon calls as it goes on, and that time lands in the self time of the function called. ${table.overheadDominated === 1 ? "One function has" : `${table.overheadDominated} functions have`} at least half ${table.overheadDominated === 1 ? "its" : "their"} selfMs in it, under overheadMs: that much of its time is the profiler's and goes once the profiler is off, whether the function is inlined or not. What inlining saves is the call itself, which costs something without the profiler too, not the work inside it.`);
|
|
35980
35981
|
}
|
|
35981
35982
|
notes.push(...cappedNotes(totals));
|
|
35982
35983
|
if (totals.unreadable > 0) {
|
|
@@ -38429,7 +38430,9 @@ class GodotServer {
|
|
|
38429
38430
|
return {
|
|
38430
38431
|
pid: run.pid ?? this.announcedPidOf(run) ?? run.announcedPid ?? null,
|
|
38431
38432
|
...run.throughEditor || run.projectPath === null ? {} : { project: run.projectPath },
|
|
38432
|
-
...run.pickedUp === true ? { adopted: true } : {}
|
|
38433
|
+
...run.pickedUp === true ? { adopted: true } : {},
|
|
38434
|
+
startedAt: new Date(run.startedAt).toISOString(),
|
|
38435
|
+
...run.endedAt === undefined ? {} : { endedAt: new Date(run.endedAt).toISOString() }
|
|
38433
38436
|
};
|
|
38434
38437
|
}
|
|
38435
38438
|
async handleEditorStatus() {
|
|
@@ -39202,10 +39205,11 @@ class GodotServer {
|
|
|
39202
39205
|
return "unreached";
|
|
39203
39206
|
}
|
|
39204
39207
|
running.endedThrough = "editor";
|
|
39205
|
-
|
|
39206
|
-
|
|
39208
|
+
const stopped = announced === undefined ? await this.untilTheEditorStopsPlaying() : await untilGone([announced]);
|
|
39209
|
+
if (stopped) {
|
|
39210
|
+
running.endedAt = Date.now();
|
|
39207
39211
|
}
|
|
39208
|
-
return
|
|
39212
|
+
return stopped ? "gone" : "lingering";
|
|
39209
39213
|
}
|
|
39210
39214
|
if (running.pid === null) {
|
|
39211
39215
|
return "gone";
|
|
@@ -39254,6 +39258,7 @@ class GodotServer {
|
|
|
39254
39258
|
if (landed && gone) {
|
|
39255
39259
|
running.exitCode = null;
|
|
39256
39260
|
running.exitSignal = "SIGTERM";
|
|
39261
|
+
running.endedAt ??= Date.now();
|
|
39257
39262
|
}
|
|
39258
39263
|
if (landed) {
|
|
39259
39264
|
running.endedThrough = "signal";
|
|
@@ -39278,6 +39283,7 @@ class GodotServer {
|
|
|
39278
39283
|
if (asked === "signalled" && gone) {
|
|
39279
39284
|
run.exitCode = null;
|
|
39280
39285
|
run.exitSignal = "SIGTERM";
|
|
39286
|
+
run.endedAt ??= Date.now();
|
|
39281
39287
|
}
|
|
39282
39288
|
run.log.record(gone ? "info" : "warning", !gone ? `gdharness had the keeper holding pid ${pid} end it, and it was still running ${stopWaitMs() / 1000} seconds later.` : asked === "signalled" ? `gdharness ended pid ${pid} through the keeper holding it.` : `gdharness asked the keeper holding pid ${pid} to end it, and it had already gone.`);
|
|
39283
39289
|
return gone ? "gone" : "lingering";
|
|
@@ -39325,6 +39331,7 @@ class GodotServer {
|
|
|
39325
39331
|
this.logDebug(`Godot process exited with code ${code ?? "none"}, signal ${signal ?? "none"}`);
|
|
39326
39332
|
started.exitCode = code;
|
|
39327
39333
|
started.exitSignal = code === null ? signal : null;
|
|
39334
|
+
started.endedAt = Date.now();
|
|
39328
39335
|
});
|
|
39329
39336
|
child.on("error", (err) => {
|
|
39330
39337
|
console.error("Failed to start Godot process:", err);
|
|
@@ -39639,6 +39646,9 @@ class GodotServer {
|
|
|
39639
39646
|
adopted.log.finish();
|
|
39640
39647
|
adopted.exitCode = record.exitCode ?? null;
|
|
39641
39648
|
adopted.exitSignal = record.exitSignal ?? null;
|
|
39649
|
+
if (record.endedAt !== undefined) {
|
|
39650
|
+
adopted.endedAt = record.endedAt;
|
|
39651
|
+
}
|
|
39642
39652
|
const exitMayBeComing = !alive(record.pid) && !exited(adopted);
|
|
39643
39653
|
if (!exitMayBeComing) {
|
|
39644
39654
|
adopted.endedUnwatched = !exited(adopted);
|
|
@@ -39932,6 +39942,9 @@ class GodotServer {
|
|
|
39932
39942
|
if (record !== null && record.pid === run.pid && (record.exitCode !== undefined || record.exitSignal !== undefined)) {
|
|
39933
39943
|
run.exitCode = record.exitCode ?? null;
|
|
39934
39944
|
run.exitSignal = record.exitSignal ?? null;
|
|
39945
|
+
if (record.endedAt !== undefined) {
|
|
39946
|
+
run.endedAt = record.endedAt;
|
|
39947
|
+
}
|
|
39935
39948
|
return true;
|
|
39936
39949
|
}
|
|
39937
39950
|
if (Date.now() >= until) {
|
|
@@ -40078,7 +40091,8 @@ class GodotServer {
|
|
|
40078
40091
|
project: run.throughEditor ? undefined : run.projectPath ?? undefined,
|
|
40079
40092
|
transcript: run.transcript ?? undefined,
|
|
40080
40093
|
startedAt: new Date(run.startedAt).toISOString(),
|
|
40081
|
-
|
|
40094
|
+
endedAt: stillRunning(run) || run.endedAt === undefined ? undefined : new Date(run.endedAt).toISOString(),
|
|
40095
|
+
elapsedMs: stillRunning(run) ? Date.now() - run.startedAt : run.endedAt === undefined ? undefined : run.endedAt - run.startedAt,
|
|
40082
40096
|
cpuSeconds,
|
|
40083
40097
|
note: notes.length > 0 ? notes.join(" ") : undefined,
|
|
40084
40098
|
omitted: selected.omitted,
|
package/build/keeper.js
CHANGED
|
@@ -421,7 +421,8 @@ function recordRunEnded(projectPath, pid, ending) {
|
|
|
421
421
|
const written = {
|
|
422
422
|
...record,
|
|
423
423
|
...ending.exitCode === null ? {} : { exitCode: ending.exitCode },
|
|
424
|
-
...ending.exitSignal === null ? {} : { exitSignal: ending.exitSignal }
|
|
424
|
+
...ending.exitSignal === null ? {} : { exitSignal: ending.exitSignal },
|
|
425
|
+
endedAt: Date.now()
|
|
425
426
|
};
|
|
426
427
|
try {
|
|
427
428
|
writeFileSync2(path, JSON.stringify(written, null, 2), "utf8");
|
|
@@ -457,7 +458,8 @@ function recordAt(path) {
|
|
|
457
458
|
arguments: Array.isArray(args) ? args.filter((value) => typeof value === "string") : [],
|
|
458
459
|
...typeof fields["command"] === "string" ? { command: fields["command"] } : {},
|
|
459
460
|
...typeof fields["exitCode"] === "number" ? { exitCode: fields["exitCode"] } : {},
|
|
460
|
-
...typeof fields["exitSignal"] === "string" ? { exitSignal: fields["exitSignal"] } : {}
|
|
461
|
+
...typeof fields["exitSignal"] === "string" ? { exitSignal: fields["exitSignal"] } : {},
|
|
462
|
+
...typeof fields["endedAt"] === "number" ? { endedAt: fields["endedAt"] } : {}
|
|
461
463
|
};
|
|
462
464
|
}
|
|
463
465
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gdharness",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.1",
|
|
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",
|