gdharness 1.2.8 → 1.2.9
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 +65 -19
- package/build/index.js +65 -19
- package/package.json +1 -1
package/build/cli.js
CHANGED
|
@@ -9347,7 +9347,7 @@ function bodyHolding(body, offset) {
|
|
|
9347
9347
|
const inner = body.inner.find((one) => one.start <= offset && offset <= one.end);
|
|
9348
9348
|
return inner === undefined ? body : bodyHolding(inner, offset);
|
|
9349
9349
|
}
|
|
9350
|
-
function callSitesOf(source, name, otherClasses = new Set) {
|
|
9350
|
+
function callSitesOf(source, name, otherClasses = new Set, property = false) {
|
|
9351
9351
|
const regions = regionsOf(source);
|
|
9352
9352
|
const code = codeOf(source, regions);
|
|
9353
9353
|
const codeLines = code.split(`
|
|
@@ -9377,7 +9377,7 @@ function callSitesOf(source, name, otherClasses = new Set) {
|
|
|
9377
9377
|
if (localAt(codeLines, index, name) !== null) {
|
|
9378
9378
|
return false;
|
|
9379
9379
|
}
|
|
9380
|
-
return !bodyHolding(shape.body, one.offset).declarations.some((declared) => declared.name === name && MEMBER_KINDS.has(declared.kind));
|
|
9380
|
+
return !bodyHolding(shape.body, one.offset).declarations.some((declared) => declared.name === name && MEMBER_KINDS.has(declared.kind) && !(property && declared.kind === "var"));
|
|
9381
9381
|
}).map((one) => {
|
|
9382
9382
|
const line = lines.positionOf(one.offset).line + 1;
|
|
9383
9383
|
const after = code.slice(one.offset + name.length).trimStart();
|
|
@@ -18377,7 +18377,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
18377
18377
|
callersOf: {
|
|
18378
18378
|
type: "string",
|
|
18379
18379
|
ops: ["profile"],
|
|
18380
|
-
description: "profile: also answer, under callers, where the project's scripts name this function in code: its name, or Class.name for the functions of one class. Each place has its script, line, the named function it is written in (within), whether the function is called there or passed on as a Callable (called), and the profile's calls, self and total milliseconds for the function it is in, those taking the most time first, forty at most. Found by name in the source, since the engine's profiler records nothing about who made a call, so a method of the same name on another class is listed too, except, when Class.name was asked, one called through another class of the project by name or through a parameter, local or class variable declared as one, and a call made through a string is not. A parameter, local or class variable of the same name is not the function and is not listed."
|
|
18380
|
+
description: "profile: also answer, under callers, where the project's scripts name this function in code: its name, or Class.name for the functions of one class. Each place has its script, line, the named function it is written in (within), whether the function is called there or passed on as a Callable (called), and the profile's calls, self and total milliseconds for the function it is in, those taking the most time first, forty at most. Found by name in the source, since the engine's profiler records nothing about who made a call, so a method of the same name on another class is listed too, except, when Class.name was asked, one called through another class of the project by name or through a parameter, local or class variable declared as one, and a call made through a string is not. A parameter, local or class variable of the same name is not the function and is not listed. A property's get or set block, which the profile names Class.@name_getter or Class.@name_setter, is asked about by that name or by the property's: the places are where the property is named, and the rows are both blocks'."
|
|
18381
18381
|
},
|
|
18382
18382
|
limit: {
|
|
18383
18383
|
type: "integer",
|
|
@@ -18413,7 +18413,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
18413
18413
|
},
|
|
18414
18414
|
wait: { summary: "wait for the run to end, then answer as editor_output does", requires: [] },
|
|
18415
18415
|
profile: {
|
|
18416
|
-
summary: "where the run spends its time by GDScript function, while it runs or after it ended: 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, sorted by self time, 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. A run started with profile: true is profiled from its start. A game the editor plays that was not is profiled from the moment this is asked, and the answer says so; a run this server started without it is refused, since its debugger is fixed as it starts",
|
|
18416
|
+
summary: "where the run spends its time by GDScript function, while it runs or after it ended: 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, sorted by self time, 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 run started with profile: true is profiled from its start. A game the editor plays that was not is profiled from the moment this is asked, and the answer says so; a run this server started without it is refused, since its debugger is fixed as it starts",
|
|
18417
18417
|
requires: []
|
|
18418
18418
|
}
|
|
18419
18419
|
},
|
|
@@ -42603,6 +42603,9 @@ function queuedMessagesOf(projectGodot) {
|
|
|
42603
42603
|
const set = projectGodot === null ? undefined : parseProjectGodot(projectGodot)["network"]?.[QUEUE_SETTING];
|
|
42604
42604
|
return typeof set === "number" && Number.isInteger(set) && set > 0 ? set : QUEUE_DEFAULT;
|
|
42605
42605
|
}
|
|
42606
|
+
function propertyOf(fn) {
|
|
42607
|
+
return /^@(.+)_(?:getter|setter)$/.exec(bareName(fn))?.[1] ?? null;
|
|
42608
|
+
}
|
|
42606
42609
|
function signatureParts(signature) {
|
|
42607
42610
|
const parts = signature.split("::");
|
|
42608
42611
|
if (parts.length < 3) {
|
|
@@ -42624,7 +42627,8 @@ function profiledFunctions(totals, limit, sourceOf = () => null) {
|
|
|
42624
42627
|
superCallers: rows.filter((row) => row.selfIncludesSuper === true).length,
|
|
42625
42628
|
perCallUs,
|
|
42626
42629
|
overheadDominated: rows.filter((row) => row.overheadMs !== undefined).length,
|
|
42627
|
-
overheadNamed: rows.filter((row) => row.overheadMs !== undefined).map((row) => row.function)
|
|
42630
|
+
overheadNamed: rows.filter((row) => row.overheadMs !== undefined).map((row) => row.function),
|
|
42631
|
+
overheadReach: rows.findLastIndex((row) => row.overheadMs !== undefined) + 1
|
|
42628
42632
|
};
|
|
42629
42633
|
}
|
|
42630
42634
|
function isCalibration(signature) {
|
|
@@ -42659,6 +42663,7 @@ function rowsOf(totals, sourceOf) {
|
|
|
42659
42663
|
return Object.entries(totals.functions).filter(([signature, sum]) => sum.calls > 0 && !isCalibration(signature)).map(([signature, sum]) => {
|
|
42660
42664
|
const parts = signatureParts(signature);
|
|
42661
42665
|
const code = codeOfScript(parts.script);
|
|
42666
|
+
const property = propertyOf(parts.function);
|
|
42662
42667
|
return {
|
|
42663
42668
|
script: parts.script,
|
|
42664
42669
|
function: parts.function,
|
|
@@ -42666,7 +42671,8 @@ function rowsOf(totals, sourceOf) {
|
|
|
42666
42671
|
calls: sum.calls,
|
|
42667
42672
|
selfMs: ms(sum.selfSeconds),
|
|
42668
42673
|
totalMs: ms(sum.totalSeconds),
|
|
42669
|
-
...code !== null && bodyCallsSuper(code, parts.line, parts.function.endsWith("(lambda)")) ? { selfIncludesSuper: true } : {}
|
|
42674
|
+
...code !== null && bodyCallsSuper(code, parts.line, parts.function.endsWith("(lambda)")) ? { selfIncludesSuper: true } : {},
|
|
42675
|
+
...property === null ? {} : { property }
|
|
42670
42676
|
};
|
|
42671
42677
|
}).sort((a, b) => b.selfMs - a.selfMs || b.totalMs - a.totalMs || a.function.localeCompare(b.function));
|
|
42672
42678
|
}
|
|
@@ -42690,9 +42696,11 @@ function callersOf(totals, name, scripts) {
|
|
|
42690
42696
|
const listed = [...scripts];
|
|
42691
42697
|
const sources = new Map(listed);
|
|
42692
42698
|
const rows = rowsOf(totals, (script) => sources.get(script) ?? null);
|
|
42693
|
-
const bare = bareName(name);
|
|
42694
|
-
const target = rows.filter((row) => name.includes(".") ? row.function === name : bareName(row.function) === bare);
|
|
42695
42699
|
const asked = name.includes(".") ? name.slice(0, name.lastIndexOf(".")) : null;
|
|
42700
|
+
const bare = propertyOf(name) ?? bareName(name);
|
|
42701
|
+
const ownerOf = (fn) => fn.includes(".") ? fn.slice(0, fn.lastIndexOf(".")) : null;
|
|
42702
|
+
const target = rows.filter((row) => (bareName(row.function) === bare || row.property === bare) && (asked === null || ownerOf(row.function) === asked));
|
|
42703
|
+
const property = propertyOf(name) !== null || target.some((row) => row.property === bare);
|
|
42696
42704
|
const otherClasses = new Set;
|
|
42697
42705
|
if (asked !== null) {
|
|
42698
42706
|
for (const [, source] of listed) {
|
|
@@ -42705,7 +42713,7 @@ function callersOf(totals, name, scripts) {
|
|
|
42705
42713
|
}
|
|
42706
42714
|
const sites = [];
|
|
42707
42715
|
for (const [script, source] of listed) {
|
|
42708
|
-
for (const site of callSitesOf(source, bare, otherClasses)) {
|
|
42716
|
+
for (const site of callSitesOf(source, bare, otherClasses, property)) {
|
|
42709
42717
|
const within = site.within === null ? undefined : rows.find((row) => row.script === script && bareName(row.function) === site.within);
|
|
42710
42718
|
sites.push({
|
|
42711
42719
|
script,
|
|
@@ -43526,8 +43534,8 @@ function profileAnswer(reading, limit) {
|
|
|
43526
43534
|
}
|
|
43527
43535
|
const table = profiledFunctions(totals, limit, reading.sourceOf);
|
|
43528
43536
|
const notes = [];
|
|
43529
|
-
const addonNote = reading.runtimeAddon ? "" : " This project has no gdharness runtime addon, which switches the profiler off as the game quits so the engine sends its totals: setup installs it.";
|
|
43530
|
-
const totalsWithheld = reading.runtimeAddon ? " The runtime addon switches the profiler off as the game quits, and did not here: either it was no longer running in this game by then, which a project can arrange for some scenes; or the game ended without taking its tree down, as a crash or a kill does; or the totals did not reach the debugger before the game exited, which the game then says in its output." : addonNote;
|
|
43537
|
+
const addonNote = reading.runtimeAddon === true ? "" : reading.runtimeAddon === null ? " The run's project is no longer at its path, so whether it had the gdharness runtime addon, which switches the profiler off as the game quits so the engine sends its totals, cannot be read." : " This project has no gdharness runtime addon, which switches the profiler off as the game quits so the engine sends its totals: setup installs it.";
|
|
43538
|
+
const totalsWithheld = reading.runtimeAddon === true ? " The runtime addon switches the profiler off as the game quits, and did not here: either it was no longer running in this game by then, which a project can arrange for some scenes; or the game ended without taking its tree down, as a crash or a kill does; or the totals did not reach the debugger before the game exited, which the game then says in its output." : addonNote;
|
|
43531
43539
|
if (totals.complete && reading.running) {
|
|
43532
43540
|
notes.push(`The engine's totals arrived while the game is still running, which it sends only when the profiler is switched off, so ${reading.through === "editor" ? "the editor's Profiler tab or the game itself, through EngineDebugger," : "the game itself, through EngineDebugger,"} switched it off before the game ended. They cover the run up to then and nothing after.`);
|
|
43533
43541
|
} else if (totals.complete) {} else if (reading.running && totals.frames === 0) {
|
|
@@ -43543,7 +43551,11 @@ function profileAnswer(reading, limit) {
|
|
|
43543
43551
|
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.`);
|
|
43544
43552
|
}
|
|
43545
43553
|
if (table.overheadDominated > 0 && table.perCallUs !== null) {
|
|
43546
|
-
|
|
43554
|
+
const one = table.overheadDominated === 1;
|
|
43555
|
+
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. ${one ? "One function has" : `${table.overheadDominated} functions have`} at least half ${one ? "its" : "their"} selfMs in it: ${namedFew(table.overheadNamed)}.${overheadRowsReturned(table, one)} That much of ${one ? "its" : "their"} 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.`);
|
|
43556
|
+
}
|
|
43557
|
+
if (reading.coveredMs > 0 && table.scriptMs > reading.coveredMs) {
|
|
43558
|
+
notes.push(`scriptMs, ${table.scriptMs}ms, is more than the ${reading.coveredMs}ms the profile covers.${table.superCallers > 0 ? " The super calls above, counted twice, can account for some or all of that; whatever they do not is" : " One thread cannot spend more time than passes, so that is"} functions running on several threads at once, through WorkerThreadPool or Thread. The engine adds every thread's time to the same function and does not say which thread a call ran on, so a function's selfMs is summed over every thread it ran on rather than taken out of frames, and a function that waits for the others, through wait_for_group_task_completion or wait_to_finish, has the wait in its own selfMs as well.`);
|
|
43547
43559
|
}
|
|
43548
43560
|
notes.push(...cappedNotes(totals));
|
|
43549
43561
|
if (totals.unreadable > 0) {
|
|
@@ -43565,6 +43577,17 @@ function profileAnswer(reading, limit) {
|
|
|
43565
43577
|
note: notes.length > 0 ? notes.join(" ") : undefined
|
|
43566
43578
|
};
|
|
43567
43579
|
}
|
|
43580
|
+
function overheadRowsReturned(table, one) {
|
|
43581
|
+
const shown = table.functions.filter((row) => row.overheadMs !== undefined).length;
|
|
43582
|
+
const returned = table.functions.length;
|
|
43583
|
+
if (shown === table.overheadDominated) {
|
|
43584
|
+
return one ? " Its row carries that share as overheadMs." : " Their rows carry that share as overheadMs.";
|
|
43585
|
+
}
|
|
43586
|
+
if (shown === 0) {
|
|
43587
|
+
return one ? ` Its row, which carries that share as overheadMs, is not among the ${returned} returned; limit ${table.overheadReach} returns it.` : ` Their rows, which carry that share as overheadMs, are none of the ${returned} returned; limit ${table.overheadReach} returns them all.`;
|
|
43588
|
+
}
|
|
43589
|
+
return ` Their rows carry that share as overheadMs, and ${shown} of them ${shown === 1 ? "is" : "are"} among the ${returned} returned; limit ${table.overheadReach} returns them all.`;
|
|
43590
|
+
}
|
|
43568
43591
|
function namedFew(names) {
|
|
43569
43592
|
const shown = names.slice(0, 5);
|
|
43570
43593
|
const more = names.length - shown.length;
|
|
@@ -43584,12 +43607,29 @@ function cappedNotes(totals) {
|
|
|
43584
43607
|
const capped = totals.cappedFrames ?? 0;
|
|
43585
43608
|
notes.push(`${capped === totals.frames ? capped === 1 ? "The one frame" : `All ${capped} frames` : `${capped} of the ${totals.frames} frames`} carried the most functions the profiler asks for in one, ${most}, so the functions with the least time in ${capped === 1 ? "it" : "them"} are not counted there: ${why}`);
|
|
43586
43609
|
}
|
|
43587
|
-
const unnamed =
|
|
43610
|
+
const unnamed = unnamedCalled(totals);
|
|
43588
43611
|
if (unnamed > 0) {
|
|
43589
43612
|
notes.push(`${unnamed === 1 ? "One function" : `${unnamed} functions`} arrived without ${unnamed === 1 ? "its name" : "their names"}, shown as ${UNNAMED_PREFIX} and the number the engine gave ${unnamed === 1 ? "it" : "them"}: the engine names a function once, as it first appears, and the game's debugger queue was full when that message was sent. They are script functions of the game; which ones cannot be recovered from this profile.`);
|
|
43590
43613
|
}
|
|
43591
43614
|
return notes;
|
|
43592
43615
|
}
|
|
43616
|
+
function unnamedCalled(totals) {
|
|
43617
|
+
return Object.entries(totals.functions).filter(([name, sum]) => name.startsWith(UNNAMED_PREFIX) && sum.calls > 0).length;
|
|
43618
|
+
}
|
|
43619
|
+
function uncountedCallee(totals, name) {
|
|
43620
|
+
const others = [];
|
|
43621
|
+
const most = totals.frameFunctions;
|
|
43622
|
+
const capped = totals.complete ? totals.totalCapped === true : (totals.cappedFrames ?? 0) > 0;
|
|
43623
|
+
if (most !== undefined && capped) {
|
|
43624
|
+
others.push(`its calls took too little time to be among the ${most} functions ${totals.complete ? "the engine's totals" : "a frame"} carried`);
|
|
43625
|
+
}
|
|
43626
|
+
const unnamed = unnamedCalled(totals);
|
|
43627
|
+
if (unnamed > 0) {
|
|
43628
|
+
others.push(`it is ${unnamed === 1 ? "the function" : `one of the ${unnamed} functions`} whose name never arrived`);
|
|
43629
|
+
}
|
|
43630
|
+
const ranNot = "none of these places ran it while the profiler was on: withinCalls says the function a place is in ran, not that the line did";
|
|
43631
|
+
return others.length === 0 ? `The profile counts no call of ${name}, so ${ranNot}.` : `The profile counts no call of ${name}: either ${ranNot}, or ${others.join(", or ")}.`;
|
|
43632
|
+
}
|
|
43593
43633
|
function callersAnswer(totals, asked) {
|
|
43594
43634
|
const found = callersOf(totals, asked.name, asked.scripts());
|
|
43595
43635
|
return {
|
|
@@ -43597,7 +43637,7 @@ function callersAnswer(totals, asked) {
|
|
|
43597
43637
|
rows: found.target,
|
|
43598
43638
|
sites: found.sites,
|
|
43599
43639
|
omitted: found.omitted > 0 ? found.omitted : undefined,
|
|
43600
|
-
note: found.sites.length === 0 ? `No script in the project names ${asked.name} in code, outside its declaration: a call made through a string, such as call("name"), is not found by name.` : found.target.length === 0 ? `${CALLERS_FOUND_BY_NAME}
|
|
43640
|
+
note: found.sites.length === 0 ? `No script in the project names ${asked.name} in code, outside its declaration: a call made through a string, such as call("name"), is not found by name.` : found.target.length === 0 ? `${CALLERS_FOUND_BY_NAME} ${uncountedCallee(totals, asked.name)}` : CALLERS_FOUND_BY_NAME
|
|
43601
43641
|
};
|
|
43602
43642
|
}
|
|
43603
43643
|
function foldedEntriesAnswer(log) {
|
|
@@ -46940,6 +46980,7 @@ class GodotServer {
|
|
|
46940
46980
|
const transcript = openTranscript(startedAt);
|
|
46941
46981
|
closeSync2(transcript.fd);
|
|
46942
46982
|
const gameEnv = profiled ? { ...env ?? process.env, [PROFILE_FROM_START]: "1" } : env;
|
|
46983
|
+
const profile = profiled ? profilePath(startedAt) : undefined;
|
|
46943
46984
|
const launched = await launchOutsideTheTree({
|
|
46944
46985
|
command: godotPath,
|
|
46945
46986
|
args: cmdArgs,
|
|
@@ -46949,7 +46990,7 @@ class GodotServer {
|
|
|
46949
46990
|
startedAt,
|
|
46950
46991
|
projectPath,
|
|
46951
46992
|
...this.servedBy(),
|
|
46952
|
-
...
|
|
46993
|
+
...profile === undefined ? {} : { profile }
|
|
46953
46994
|
},
|
|
46954
46995
|
...desktop === undefined ? {} : { desktop }
|
|
46955
46996
|
});
|
|
@@ -46970,6 +47011,7 @@ class GodotServer {
|
|
|
46970
47011
|
startedAt,
|
|
46971
47012
|
startedBy,
|
|
46972
47013
|
...keeper === undefined ? {} : { keeper },
|
|
47014
|
+
...profile === undefined ? {} : { profile },
|
|
46973
47015
|
command: godotPath,
|
|
46974
47016
|
exitCode: null,
|
|
46975
47017
|
exitSignal: null,
|
|
@@ -47210,6 +47252,7 @@ class GodotServer {
|
|
|
47210
47252
|
startedAt: record.startedAt,
|
|
47211
47253
|
...record.startedBy === undefined ? {} : { startedBy: record.startedBy },
|
|
47212
47254
|
...record.keeper === undefined ? {} : { keeper: record.keeper },
|
|
47255
|
+
...record.profile === undefined ? {} : { profile: record.profile },
|
|
47213
47256
|
...record.command === undefined ? {} : { command: record.command },
|
|
47214
47257
|
exitCode: null,
|
|
47215
47258
|
exitSignal: null,
|
|
@@ -47362,12 +47405,16 @@ class GodotServer {
|
|
|
47362
47405
|
}
|
|
47363
47406
|
const limit = readPositiveNumber(args, "limit") ?? PROFILE_FUNCTIONS;
|
|
47364
47407
|
const callersName = readString(args, "callersOf");
|
|
47365
|
-
if (callersName !== undefined && !callersName.split(".").every(isIdentifier)) {
|
|
47408
|
+
if (callersName !== undefined && !callersName.replace(/@([^.@]+)_(?:getter|setter)$/, "$1").split(".").every(isIdentifier)) {
|
|
47366
47409
|
return this.createErrorResponse(`callersOf takes a function's name, or Class.name for the functions of one class, and ${JSON.stringify(callersName)} is neither.`, ["editor_run profile names each function as it answers; pass one of those"]);
|
|
47367
47410
|
}
|
|
47368
47411
|
const going = await this.runStillGoing(run);
|
|
47369
47412
|
const projectPath = run.projectPath;
|
|
47370
|
-
const
|
|
47413
|
+
const projectGone = projectPath !== null && !existsSync20(join26(projectPath, "project.godot"));
|
|
47414
|
+
if (projectGone && callersName !== undefined) {
|
|
47415
|
+
return this.createErrorResponse(`callersOf reads the project's scripts, and the run's project at ${projectPath} has no project.godot any more.`, ["editor_run profile without callersOf answers with the profile itself"]);
|
|
47416
|
+
}
|
|
47417
|
+
const fromProject = projectPath === null ? { runtimeAddon: false } : projectGone ? { runtimeAddon: null } : {
|
|
47371
47418
|
runtimeAddon: runtimeAddonRegistered(projectPath),
|
|
47372
47419
|
sourceOf: projectSourceOf(projectPath),
|
|
47373
47420
|
...callersName === undefined ? {} : { callersOf: { name: callersName, scripts: () => projectScripts(projectPath) } }
|
|
@@ -47375,8 +47422,7 @@ class GodotServer {
|
|
|
47375
47422
|
if (run.throughEditor) {
|
|
47376
47423
|
return await this.profileOfThePlayedGame(going, projectPath, fromProject, limit);
|
|
47377
47424
|
}
|
|
47378
|
-
const
|
|
47379
|
-
const path = record !== null && record.pid === run.pid ? record.profile : undefined;
|
|
47425
|
+
const path = run.profile;
|
|
47380
47426
|
if (path === undefined) {
|
|
47381
47427
|
return this.createErrorResponse("This run was started without profile: true, and a game this server starts can be profiled only from its start: its debugger is whatever it dials as it starts, once.", ["editor_run start with profile: true starts the run again with the profiler on"]);
|
|
47382
47428
|
}
|
package/build/index.js
CHANGED
|
@@ -26720,7 +26720,7 @@ function bodyHolding(body, offset) {
|
|
|
26720
26720
|
const inner = body.inner.find((one) => one.start <= offset && offset <= one.end);
|
|
26721
26721
|
return inner === undefined ? body : bodyHolding(inner, offset);
|
|
26722
26722
|
}
|
|
26723
|
-
function callSitesOf(source, name, otherClasses = new Set) {
|
|
26723
|
+
function callSitesOf(source, name, otherClasses = new Set, property = false) {
|
|
26724
26724
|
const regions = regionsOf(source);
|
|
26725
26725
|
const code = codeOf(source, regions);
|
|
26726
26726
|
const codeLines = code.split(`
|
|
@@ -26750,7 +26750,7 @@ function callSitesOf(source, name, otherClasses = new Set) {
|
|
|
26750
26750
|
if (localAt(codeLines, index, name) !== null) {
|
|
26751
26751
|
return false;
|
|
26752
26752
|
}
|
|
26753
|
-
return !bodyHolding(shape.body, one.offset).declarations.some((declared) => declared.name === name && MEMBER_KINDS.has(declared.kind));
|
|
26753
|
+
return !bodyHolding(shape.body, one.offset).declarations.some((declared) => declared.name === name && MEMBER_KINDS.has(declared.kind) && !(property && declared.kind === "var"));
|
|
26754
26754
|
}).map((one) => {
|
|
26755
26755
|
const line = lines.positionOf(one.offset).line + 1;
|
|
26756
26756
|
const after = code.slice(one.offset + name.length).trimStart();
|
|
@@ -33394,6 +33394,9 @@ function queuedMessagesOf(projectGodot) {
|
|
|
33394
33394
|
}
|
|
33395
33395
|
var UNNAMED_PREFIX = "#";
|
|
33396
33396
|
var PROFILE_FROM_START = "GDHARNESS_PROFILE_FROM_START";
|
|
33397
|
+
function propertyOf(fn) {
|
|
33398
|
+
return /^@(.+)_(?:getter|setter)$/.exec(bareName(fn))?.[1] ?? null;
|
|
33399
|
+
}
|
|
33397
33400
|
function signatureParts(signature) {
|
|
33398
33401
|
const parts = signature.split("::");
|
|
33399
33402
|
if (parts.length < 3) {
|
|
@@ -33416,7 +33419,8 @@ function profiledFunctions(totals, limit, sourceOf = () => null) {
|
|
|
33416
33419
|
superCallers: rows.filter((row) => row.selfIncludesSuper === true).length,
|
|
33417
33420
|
perCallUs,
|
|
33418
33421
|
overheadDominated: rows.filter((row) => row.overheadMs !== undefined).length,
|
|
33419
|
-
overheadNamed: rows.filter((row) => row.overheadMs !== undefined).map((row) => row.function)
|
|
33422
|
+
overheadNamed: rows.filter((row) => row.overheadMs !== undefined).map((row) => row.function),
|
|
33423
|
+
overheadReach: rows.findLastIndex((row) => row.overheadMs !== undefined) + 1
|
|
33420
33424
|
};
|
|
33421
33425
|
}
|
|
33422
33426
|
var RUNTIME_SCRIPT = "gdharness_runtime/runtime_autoload.gd";
|
|
@@ -33454,6 +33458,7 @@ function rowsOf(totals, sourceOf) {
|
|
|
33454
33458
|
return Object.entries(totals.functions).filter(([signature, sum]) => sum.calls > 0 && !isCalibration(signature)).map(([signature, sum]) => {
|
|
33455
33459
|
const parts = signatureParts(signature);
|
|
33456
33460
|
const code = codeOfScript(parts.script);
|
|
33461
|
+
const property = propertyOf(parts.function);
|
|
33457
33462
|
return {
|
|
33458
33463
|
script: parts.script,
|
|
33459
33464
|
function: parts.function,
|
|
@@ -33461,7 +33466,8 @@ function rowsOf(totals, sourceOf) {
|
|
|
33461
33466
|
calls: sum.calls,
|
|
33462
33467
|
selfMs: ms(sum.selfSeconds),
|
|
33463
33468
|
totalMs: ms(sum.totalSeconds),
|
|
33464
|
-
...code !== null && bodyCallsSuper(code, parts.line, parts.function.endsWith("(lambda)")) ? { selfIncludesSuper: true } : {}
|
|
33469
|
+
...code !== null && bodyCallsSuper(code, parts.line, parts.function.endsWith("(lambda)")) ? { selfIncludesSuper: true } : {},
|
|
33470
|
+
...property === null ? {} : { property }
|
|
33465
33471
|
};
|
|
33466
33472
|
}).sort((a, b) => b.selfMs - a.selfMs || b.totalMs - a.totalMs || a.function.localeCompare(b.function));
|
|
33467
33473
|
}
|
|
@@ -33486,9 +33492,11 @@ function callersOf(totals, name, scripts) {
|
|
|
33486
33492
|
const listed = [...scripts];
|
|
33487
33493
|
const sources = new Map(listed);
|
|
33488
33494
|
const rows = rowsOf(totals, (script) => sources.get(script) ?? null);
|
|
33489
|
-
const bare = bareName(name);
|
|
33490
|
-
const target = rows.filter((row) => name.includes(".") ? row.function === name : bareName(row.function) === bare);
|
|
33491
33495
|
const asked = name.includes(".") ? name.slice(0, name.lastIndexOf(".")) : null;
|
|
33496
|
+
const bare = propertyOf(name) ?? bareName(name);
|
|
33497
|
+
const ownerOf = (fn) => fn.includes(".") ? fn.slice(0, fn.lastIndexOf(".")) : null;
|
|
33498
|
+
const target = rows.filter((row) => (bareName(row.function) === bare || row.property === bare) && (asked === null || ownerOf(row.function) === asked));
|
|
33499
|
+
const property = propertyOf(name) !== null || target.some((row) => row.property === bare);
|
|
33492
33500
|
const otherClasses = new Set;
|
|
33493
33501
|
if (asked !== null) {
|
|
33494
33502
|
for (const [, source] of listed) {
|
|
@@ -33501,7 +33509,7 @@ function callersOf(totals, name, scripts) {
|
|
|
33501
33509
|
}
|
|
33502
33510
|
const sites = [];
|
|
33503
33511
|
for (const [script, source] of listed) {
|
|
33504
|
-
for (const site of callSitesOf(source, bare, otherClasses)) {
|
|
33512
|
+
for (const site of callSitesOf(source, bare, otherClasses, property)) {
|
|
33505
33513
|
const within = site.within === null ? undefined : rows.find((row) => row.script === script && bareName(row.function) === site.within);
|
|
33506
33514
|
sites.push({
|
|
33507
33515
|
script,
|
|
@@ -34506,7 +34514,7 @@ var TOOL_SPECS = [
|
|
|
34506
34514
|
callersOf: {
|
|
34507
34515
|
type: "string",
|
|
34508
34516
|
ops: ["profile"],
|
|
34509
|
-
description: "profile: also answer, under callers, where the project's scripts name this function in code: its name, or Class.name for the functions of one class. Each place has its script, line, the named function it is written in (within), whether the function is called there or passed on as a Callable (called), and the profile's calls, self and total milliseconds for the function it is in, those taking the most time first, forty at most. Found by name in the source, since the engine's profiler records nothing about who made a call, so a method of the same name on another class is listed too, except, when Class.name was asked, one called through another class of the project by name or through a parameter, local or class variable declared as one, and a call made through a string is not. A parameter, local or class variable of the same name is not the function and is not listed."
|
|
34517
|
+
description: "profile: also answer, under callers, where the project's scripts name this function in code: its name, or Class.name for the functions of one class. Each place has its script, line, the named function it is written in (within), whether the function is called there or passed on as a Callable (called), and the profile's calls, self and total milliseconds for the function it is in, those taking the most time first, forty at most. Found by name in the source, since the engine's profiler records nothing about who made a call, so a method of the same name on another class is listed too, except, when Class.name was asked, one called through another class of the project by name or through a parameter, local or class variable declared as one, and a call made through a string is not. A parameter, local or class variable of the same name is not the function and is not listed. A property's get or set block, which the profile names Class.@name_getter or Class.@name_setter, is asked about by that name or by the property's: the places are where the property is named, and the rows are both blocks'."
|
|
34510
34518
|
},
|
|
34511
34519
|
limit: {
|
|
34512
34520
|
type: "integer",
|
|
@@ -34542,7 +34550,7 @@ var TOOL_SPECS = [
|
|
|
34542
34550
|
},
|
|
34543
34551
|
wait: { summary: "wait for the run to end, then answer as editor_output does", requires: [] },
|
|
34544
34552
|
profile: {
|
|
34545
|
-
summary: "where the run spends its time by GDScript function, while it runs or after it ended: 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, sorted by self time, 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. A run started with profile: true is profiled from its start. A game the editor plays that was not is profiled from the moment this is asked, and the answer says so; a run this server started without it is refused, since its debugger is fixed as it starts",
|
|
34553
|
+
summary: "where the run spends its time by GDScript function, while it runs or after it ended: 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, sorted by self time, 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 run started with profile: true is profiled from its start. A game the editor plays that was not is profiled from the moment this is asked, and the answer says so; a run this server started without it is refused, since its debugger is fixed as it starts",
|
|
34546
34554
|
requires: []
|
|
34547
34555
|
}
|
|
34548
34556
|
},
|
|
@@ -36021,8 +36029,8 @@ function profileAnswer(reading, limit) {
|
|
|
36021
36029
|
}
|
|
36022
36030
|
const table = profiledFunctions(totals, limit, reading.sourceOf);
|
|
36023
36031
|
const notes = [];
|
|
36024
|
-
const addonNote = reading.runtimeAddon ? "" : " This project has no gdharness runtime addon, which switches the profiler off as the game quits so the engine sends its totals: setup installs it.";
|
|
36025
|
-
const totalsWithheld = reading.runtimeAddon ? " The runtime addon switches the profiler off as the game quits, and did not here: either it was no longer running in this game by then, which a project can arrange for some scenes; or the game ended without taking its tree down, as a crash or a kill does; or the totals did not reach the debugger before the game exited, which the game then says in its output." : addonNote;
|
|
36032
|
+
const addonNote = reading.runtimeAddon === true ? "" : reading.runtimeAddon === null ? " The run's project is no longer at its path, so whether it had the gdharness runtime addon, which switches the profiler off as the game quits so the engine sends its totals, cannot be read." : " This project has no gdharness runtime addon, which switches the profiler off as the game quits so the engine sends its totals: setup installs it.";
|
|
36033
|
+
const totalsWithheld = reading.runtimeAddon === true ? " The runtime addon switches the profiler off as the game quits, and did not here: either it was no longer running in this game by then, which a project can arrange for some scenes; or the game ended without taking its tree down, as a crash or a kill does; or the totals did not reach the debugger before the game exited, which the game then says in its output." : addonNote;
|
|
36026
36034
|
if (totals.complete && reading.running) {
|
|
36027
36035
|
notes.push(`The engine's totals arrived while the game is still running, which it sends only when the profiler is switched off, so ${reading.through === "editor" ? "the editor's Profiler tab or the game itself, through EngineDebugger," : "the game itself, through EngineDebugger,"} switched it off before the game ended. They cover the run up to then and nothing after.`);
|
|
36028
36036
|
} else if (totals.complete) {} else if (reading.running && totals.frames === 0) {
|
|
@@ -36038,7 +36046,11 @@ function profileAnswer(reading, limit) {
|
|
|
36038
36046
|
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.`);
|
|
36039
36047
|
}
|
|
36040
36048
|
if (table.overheadDominated > 0 && table.perCallUs !== null) {
|
|
36041
|
-
|
|
36049
|
+
const one = table.overheadDominated === 1;
|
|
36050
|
+
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. ${one ? "One function has" : `${table.overheadDominated} functions have`} at least half ${one ? "its" : "their"} selfMs in it: ${namedFew(table.overheadNamed)}.${overheadRowsReturned(table, one)} That much of ${one ? "its" : "their"} 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.`);
|
|
36051
|
+
}
|
|
36052
|
+
if (reading.coveredMs > 0 && table.scriptMs > reading.coveredMs) {
|
|
36053
|
+
notes.push(`scriptMs, ${table.scriptMs}ms, is more than the ${reading.coveredMs}ms the profile covers.${table.superCallers > 0 ? " The super calls above, counted twice, can account for some or all of that; whatever they do not is" : " One thread cannot spend more time than passes, so that is"} functions running on several threads at once, through WorkerThreadPool or Thread. The engine adds every thread's time to the same function and does not say which thread a call ran on, so a function's selfMs is summed over every thread it ran on rather than taken out of frames, and a function that waits for the others, through wait_for_group_task_completion or wait_to_finish, has the wait in its own selfMs as well.`);
|
|
36042
36054
|
}
|
|
36043
36055
|
notes.push(...cappedNotes(totals));
|
|
36044
36056
|
if (totals.unreadable > 0) {
|
|
@@ -36060,6 +36072,17 @@ function profileAnswer(reading, limit) {
|
|
|
36060
36072
|
note: notes.length > 0 ? notes.join(" ") : undefined
|
|
36061
36073
|
};
|
|
36062
36074
|
}
|
|
36075
|
+
function overheadRowsReturned(table, one) {
|
|
36076
|
+
const shown = table.functions.filter((row) => row.overheadMs !== undefined).length;
|
|
36077
|
+
const returned = table.functions.length;
|
|
36078
|
+
if (shown === table.overheadDominated) {
|
|
36079
|
+
return one ? " Its row carries that share as overheadMs." : " Their rows carry that share as overheadMs.";
|
|
36080
|
+
}
|
|
36081
|
+
if (shown === 0) {
|
|
36082
|
+
return one ? ` Its row, which carries that share as overheadMs, is not among the ${returned} returned; limit ${table.overheadReach} returns it.` : ` Their rows, which carry that share as overheadMs, are none of the ${returned} returned; limit ${table.overheadReach} returns them all.`;
|
|
36083
|
+
}
|
|
36084
|
+
return ` Their rows carry that share as overheadMs, and ${shown} of them ${shown === 1 ? "is" : "are"} among the ${returned} returned; limit ${table.overheadReach} returns them all.`;
|
|
36085
|
+
}
|
|
36063
36086
|
function namedFew(names) {
|
|
36064
36087
|
const shown = names.slice(0, 5);
|
|
36065
36088
|
const more = names.length - shown.length;
|
|
@@ -36079,12 +36102,29 @@ function cappedNotes(totals) {
|
|
|
36079
36102
|
const capped = totals.cappedFrames ?? 0;
|
|
36080
36103
|
notes.push(`${capped === totals.frames ? capped === 1 ? "The one frame" : `All ${capped} frames` : `${capped} of the ${totals.frames} frames`} carried the most functions the profiler asks for in one, ${most}, so the functions with the least time in ${capped === 1 ? "it" : "them"} are not counted there: ${why}`);
|
|
36081
36104
|
}
|
|
36082
|
-
const unnamed =
|
|
36105
|
+
const unnamed = unnamedCalled(totals);
|
|
36083
36106
|
if (unnamed > 0) {
|
|
36084
36107
|
notes.push(`${unnamed === 1 ? "One function" : `${unnamed} functions`} arrived without ${unnamed === 1 ? "its name" : "their names"}, shown as ${UNNAMED_PREFIX} and the number the engine gave ${unnamed === 1 ? "it" : "them"}: the engine names a function once, as it first appears, and the game's debugger queue was full when that message was sent. They are script functions of the game; which ones cannot be recovered from this profile.`);
|
|
36085
36108
|
}
|
|
36086
36109
|
return notes;
|
|
36087
36110
|
}
|
|
36111
|
+
function unnamedCalled(totals) {
|
|
36112
|
+
return Object.entries(totals.functions).filter(([name, sum]) => name.startsWith(UNNAMED_PREFIX) && sum.calls > 0).length;
|
|
36113
|
+
}
|
|
36114
|
+
function uncountedCallee(totals, name) {
|
|
36115
|
+
const others = [];
|
|
36116
|
+
const most = totals.frameFunctions;
|
|
36117
|
+
const capped = totals.complete ? totals.totalCapped === true : (totals.cappedFrames ?? 0) > 0;
|
|
36118
|
+
if (most !== undefined && capped) {
|
|
36119
|
+
others.push(`its calls took too little time to be among the ${most} functions ${totals.complete ? "the engine's totals" : "a frame"} carried`);
|
|
36120
|
+
}
|
|
36121
|
+
const unnamed = unnamedCalled(totals);
|
|
36122
|
+
if (unnamed > 0) {
|
|
36123
|
+
others.push(`it is ${unnamed === 1 ? "the function" : `one of the ${unnamed} functions`} whose name never arrived`);
|
|
36124
|
+
}
|
|
36125
|
+
const ranNot = "none of these places ran it while the profiler was on: withinCalls says the function a place is in ran, not that the line did";
|
|
36126
|
+
return others.length === 0 ? `The profile counts no call of ${name}, so ${ranNot}.` : `The profile counts no call of ${name}: either ${ranNot}, or ${others.join(", or ")}.`;
|
|
36127
|
+
}
|
|
36088
36128
|
function callersAnswer(totals, asked) {
|
|
36089
36129
|
const found = callersOf(totals, asked.name, asked.scripts());
|
|
36090
36130
|
return {
|
|
@@ -36092,7 +36132,7 @@ function callersAnswer(totals, asked) {
|
|
|
36092
36132
|
rows: found.target,
|
|
36093
36133
|
sites: found.sites,
|
|
36094
36134
|
omitted: found.omitted > 0 ? found.omitted : undefined,
|
|
36095
|
-
note: found.sites.length === 0 ? `No script in the project names ${asked.name} in code, outside its declaration: a call made through a string, such as call("name"), is not found by name.` : found.target.length === 0 ? `${CALLERS_FOUND_BY_NAME}
|
|
36135
|
+
note: found.sites.length === 0 ? `No script in the project names ${asked.name} in code, outside its declaration: a call made through a string, such as call("name"), is not found by name.` : found.target.length === 0 ? `${CALLERS_FOUND_BY_NAME} ${uncountedCallee(totals, asked.name)}` : CALLERS_FOUND_BY_NAME
|
|
36096
36136
|
};
|
|
36097
36137
|
}
|
|
36098
36138
|
var MOST_ENGINE_ENTRIES = 200;
|
|
@@ -39438,6 +39478,7 @@ class GodotServer {
|
|
|
39438
39478
|
const transcript = openTranscript(startedAt);
|
|
39439
39479
|
closeSync2(transcript.fd);
|
|
39440
39480
|
const gameEnv = profiled ? { ...env ?? process.env, [PROFILE_FROM_START]: "1" } : env;
|
|
39481
|
+
const profile = profiled ? profilePath(startedAt) : undefined;
|
|
39441
39482
|
const launched = await launchOutsideTheTree({
|
|
39442
39483
|
command: godotPath,
|
|
39443
39484
|
args: cmdArgs,
|
|
@@ -39447,7 +39488,7 @@ class GodotServer {
|
|
|
39447
39488
|
startedAt,
|
|
39448
39489
|
projectPath,
|
|
39449
39490
|
...this.servedBy(),
|
|
39450
|
-
...
|
|
39491
|
+
...profile === undefined ? {} : { profile }
|
|
39451
39492
|
},
|
|
39452
39493
|
...desktop === undefined ? {} : { desktop }
|
|
39453
39494
|
});
|
|
@@ -39468,6 +39509,7 @@ class GodotServer {
|
|
|
39468
39509
|
startedAt,
|
|
39469
39510
|
startedBy,
|
|
39470
39511
|
...keeper === undefined ? {} : { keeper },
|
|
39512
|
+
...profile === undefined ? {} : { profile },
|
|
39471
39513
|
command: godotPath,
|
|
39472
39514
|
exitCode: null,
|
|
39473
39515
|
exitSignal: null,
|
|
@@ -39708,6 +39750,7 @@ class GodotServer {
|
|
|
39708
39750
|
startedAt: record.startedAt,
|
|
39709
39751
|
...record.startedBy === undefined ? {} : { startedBy: record.startedBy },
|
|
39710
39752
|
...record.keeper === undefined ? {} : { keeper: record.keeper },
|
|
39753
|
+
...record.profile === undefined ? {} : { profile: record.profile },
|
|
39711
39754
|
...record.command === undefined ? {} : { command: record.command },
|
|
39712
39755
|
exitCode: null,
|
|
39713
39756
|
exitSignal: null,
|
|
@@ -39860,12 +39903,16 @@ class GodotServer {
|
|
|
39860
39903
|
}
|
|
39861
39904
|
const limit = readPositiveNumber(args, "limit") ?? PROFILE_FUNCTIONS;
|
|
39862
39905
|
const callersName = readString(args, "callersOf");
|
|
39863
|
-
if (callersName !== undefined && !callersName.split(".").every(isIdentifier)) {
|
|
39906
|
+
if (callersName !== undefined && !callersName.replace(/@([^.@]+)_(?:getter|setter)$/, "$1").split(".").every(isIdentifier)) {
|
|
39864
39907
|
return this.createErrorResponse(`callersOf takes a function's name, or Class.name for the functions of one class, and ${JSON.stringify(callersName)} is neither.`, ["editor_run profile names each function as it answers; pass one of those"]);
|
|
39865
39908
|
}
|
|
39866
39909
|
const going = await this.runStillGoing(run);
|
|
39867
39910
|
const projectPath = run.projectPath;
|
|
39868
|
-
const
|
|
39911
|
+
const projectGone = projectPath !== null && !existsSync17(join22(projectPath, "project.godot"));
|
|
39912
|
+
if (projectGone && callersName !== undefined) {
|
|
39913
|
+
return this.createErrorResponse(`callersOf reads the project's scripts, and the run's project at ${projectPath} has no project.godot any more.`, ["editor_run profile without callersOf answers with the profile itself"]);
|
|
39914
|
+
}
|
|
39915
|
+
const fromProject = projectPath === null ? { runtimeAddon: false } : projectGone ? { runtimeAddon: null } : {
|
|
39869
39916
|
runtimeAddon: runtimeAddonRegistered(projectPath),
|
|
39870
39917
|
sourceOf: projectSourceOf(projectPath),
|
|
39871
39918
|
...callersName === undefined ? {} : { callersOf: { name: callersName, scripts: () => projectScripts(projectPath) } }
|
|
@@ -39873,8 +39920,7 @@ class GodotServer {
|
|
|
39873
39920
|
if (run.throughEditor) {
|
|
39874
39921
|
return await this.profileOfThePlayedGame(going, projectPath, fromProject, limit);
|
|
39875
39922
|
}
|
|
39876
|
-
const
|
|
39877
|
-
const path = record !== null && record.pid === run.pid ? record.profile : undefined;
|
|
39923
|
+
const path = run.profile;
|
|
39878
39924
|
if (path === undefined) {
|
|
39879
39925
|
return this.createErrorResponse("This run was started without profile: true, and a game this server starts can be profiled only from its start: its debugger is whatever it dials as it starts, once.", ["editor_run start with profile: true starts the run again with the profiler on"]);
|
|
39880
39926
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gdharness",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.9",
|
|
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",
|