gdharness 1.2.9 → 1.2.10
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 +98 -27
- package/build/godot/addons/gdharness_runtime/runtime_autoload.gd +11 -0
- package/build/index.js +99 -24
- package/package.json +1 -1
package/build/cli.js
CHANGED
|
@@ -9358,8 +9358,31 @@ function callSitesOf(source, name, otherClasses = new Set, property = false) {
|
|
|
9358
9358
|
const declared = bodyHolding(shape.body, at).declarations.find((one) => one.name === member && (one.kind === "var" || one.kind === "const"));
|
|
9359
9359
|
return declared === undefined ? null : declaredClass(codeLines[lines.positionOf(declared.offset).line] ?? "", member);
|
|
9360
9360
|
};
|
|
9361
|
+
const blockUnder = (declared) => {
|
|
9362
|
+
const indent = (codeLines[declared] ?? "").search(/\S|$/);
|
|
9363
|
+
let last = declared;
|
|
9364
|
+
for (let index = declared + 1;index < codeLines.length; index += 1) {
|
|
9365
|
+
const text = codeLines[index] ?? "";
|
|
9366
|
+
if (text.trim() === "") {
|
|
9367
|
+
continue;
|
|
9368
|
+
}
|
|
9369
|
+
if (text.search(/\S/) <= indent) {
|
|
9370
|
+
break;
|
|
9371
|
+
}
|
|
9372
|
+
last = index;
|
|
9373
|
+
}
|
|
9374
|
+
return [declared, last];
|
|
9375
|
+
};
|
|
9376
|
+
const accessorsIn = (body) => [
|
|
9377
|
+
...body.declarations.filter((one) => one.kind === "var" && one.name === name).map((one) => blockUnder(lines.positionOf(one.offset).line)),
|
|
9378
|
+
...body.inner.flatMap(accessorsIn)
|
|
9379
|
+
];
|
|
9380
|
+
const accessors = property ? accessorsIn(shape.body) : [];
|
|
9361
9381
|
return occurrencesOf(source, name, regions).filter((one) => one.kind === "code" && one.declaredAs === null).filter((one) => {
|
|
9362
9382
|
const index = lines.positionOf(one.offset).line;
|
|
9383
|
+
if (!one.afterDot && accessors.some(([declared, last]) => index > declared && index <= last)) {
|
|
9384
|
+
return false;
|
|
9385
|
+
}
|
|
9363
9386
|
if (one.afterDot) {
|
|
9364
9387
|
const receiver = receiverBefore(code, one.offset);
|
|
9365
9388
|
if (receiver === null || otherClasses.size === 0) {
|
|
@@ -9381,7 +9404,8 @@ function callSitesOf(source, name, otherClasses = new Set, property = false) {
|
|
|
9381
9404
|
}).map((one) => {
|
|
9382
9405
|
const line = lines.positionOf(one.offset).line + 1;
|
|
9383
9406
|
const after = code.slice(one.offset + name.length).trimStart();
|
|
9384
|
-
|
|
9407
|
+
const within = enclosingFunction(source, line, code);
|
|
9408
|
+
return property ? { line, within, access: ASSIGNED.test(after) ? "write" : "read" } : { line, within, called: after.startsWith("(") };
|
|
9385
9409
|
});
|
|
9386
9410
|
}
|
|
9387
9411
|
function collectEnumValues(text, from, into) {
|
|
@@ -9399,7 +9423,7 @@ function collectEnumValues(text, from, into) {
|
|
|
9399
9423
|
function isIdentifier(name) {
|
|
9400
9424
|
return new RegExp(`^${NAME}$`, "u").test(name) && !KEYWORDS.has(name);
|
|
9401
9425
|
}
|
|
9402
|
-
var IDENTIFIER_START, IDENTIFIER_PART, DECLARING, NAME = "[\\p{L}_][\\p{L}\\p{N}_]*", ANNOTATIONS = "(?:@[\\p{L}_][\\p{L}\\p{N}_]*(?:\\([^)]*\\))?\\s+)*", DECLARATION_LINE, CLASS_NAME_LINE, EXTENDS_LINE, EXTENDS_CLAUSE, FUNC_LINE, SUPER_CALL, LAMBDA_HEADER, DOTTED, MEMBER_KINDS, KEYWORDS;
|
|
9426
|
+
var IDENTIFIER_START, IDENTIFIER_PART, DECLARING, NAME = "[\\p{L}_][\\p{L}\\p{N}_]*", ANNOTATIONS = "(?:@[\\p{L}_][\\p{L}\\p{N}_]*(?:\\([^)]*\\))?\\s+)*", DECLARATION_LINE, CLASS_NAME_LINE, EXTENDS_LINE, EXTENDS_CLAUSE, FUNC_LINE, SUPER_CALL, LAMBDA_HEADER, ASSIGNED, DOTTED, MEMBER_KINDS, KEYWORDS;
|
|
9403
9427
|
var init_gdscript_source = __esm(() => {
|
|
9404
9428
|
IDENTIFIER_START = /[\p{L}_]/u;
|
|
9405
9429
|
IDENTIFIER_PART = /[\p{L}\p{N}_]/u;
|
|
@@ -9411,6 +9435,7 @@ var init_gdscript_source = __esm(() => {
|
|
|
9411
9435
|
FUNC_LINE = new RegExp(`^[ \\t]*${ANNOTATIONS}(?:static\\s+)?func\\s+(${NAME})\\s*\\(`, "u");
|
|
9412
9436
|
SUPER_CALL = /(?<![\p{L}\p{N}_.])super\s*[.(]/u;
|
|
9413
9437
|
LAMBDA_HEADER = new RegExp(`(?<![\\p{L}\\p{N}_])func(?:\\s+${NAME})?\\s*\\(`, "u");
|
|
9438
|
+
ASSIGNED = /^(?:\*\*|<<|>>|[-+*/%&|^])?=(?!=)/;
|
|
9414
9439
|
DOTTED = `${NAME}(?:\\.${NAME})*`;
|
|
9415
9440
|
MEMBER_KINDS = new Set(["var", "const", "signal", "enum", "enumValue"]);
|
|
9416
9441
|
KEYWORDS = new Set([
|
|
@@ -18355,7 +18380,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
18355
18380
|
type: "integer",
|
|
18356
18381
|
minimum: 0,
|
|
18357
18382
|
ops: ["start"],
|
|
18358
|
-
description: "start: how long to wait for the game to announce its runtime before answering. Default 5000, or half as long again as the last
|
|
18383
|
+
description: "start: how long to wait for the game to announce its runtime before answering. Default 5000, or half as long again as the slowest of the last five boots of the same scene when that is more, up to 60000: the server notes how long each game took to announce, per scene and for the main scene when none is named, since a boot moves with the load on the machine, so a scene that boots slowly is waited for from its second start on without being asked to, and a harness scene that announces late sets nothing for the main scene. What it waits for is the first frame, so everything the game does before drawing one is inside it, including work the args just asked for: a flag that simulates six years of game time before anything is drawn makes the announcement that late, on a project that announces promptly without it. Raise it for such a run rather than reading runtime listening false as a fault. 0 does not wait at all, which is the one to pass for a scene that announces nothing by construction, such as a bench that prints and quits."
|
|
18359
18384
|
},
|
|
18360
18385
|
frames: {
|
|
18361
18386
|
type: "integer",
|
|
@@ -18377,7 +18402,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
18377
18402
|
callersOf: {
|
|
18378
18403
|
type: "string",
|
|
18379
18404
|
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. 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'."
|
|
18405
|
+
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, each saying instead of called whether it writes the property (access write), which runs the set block, or reads it (access read), which runs the get block, and the rows are both blocks'. Inside the property's own blocks its name is the value stored, which the engine reads and writes without running them, so those lines are not places."
|
|
18381
18406
|
},
|
|
18382
18407
|
limit: {
|
|
18383
18408
|
type: "integer",
|
|
@@ -34419,19 +34444,33 @@ import { dirname as dirname9, join as join14 } from "node:path";
|
|
|
34419
34444
|
function bootNotePath(projectPath) {
|
|
34420
34445
|
return join14(projectPath, ".godot", "gdharness-boot.json");
|
|
34421
34446
|
}
|
|
34422
|
-
function
|
|
34447
|
+
function readScenes(projectPath) {
|
|
34423
34448
|
try {
|
|
34424
|
-
|
|
34425
|
-
return noted !== undefined && Number.isFinite(noted) && noted > 0 ? noted : null;
|
|
34449
|
+
return asParams(asParams(JSON.parse(readFileSync12(bootNotePath(projectPath), "utf8")))["scenes"]);
|
|
34426
34450
|
} catch {
|
|
34427
|
-
return
|
|
34451
|
+
return {};
|
|
34428
34452
|
}
|
|
34429
34453
|
}
|
|
34430
|
-
function
|
|
34454
|
+
function bootsOf(projectPath, scene) {
|
|
34455
|
+
const kept = asParams(readScenes(projectPath)[scene])["boots"];
|
|
34456
|
+
return Array.isArray(kept) ? kept.filter((one) => typeof one === "number" && Number.isFinite(one) && one > 0) : [];
|
|
34457
|
+
}
|
|
34458
|
+
function readBootNote(projectPath, scene) {
|
|
34459
|
+
const boots = bootsOf(projectPath, scene);
|
|
34460
|
+
return boots.length === 0 ? null : Math.max(...boots);
|
|
34461
|
+
}
|
|
34462
|
+
function writeBootNote(projectPath, scene, announcedAfterMs) {
|
|
34431
34463
|
try {
|
|
34432
34464
|
const path = bootNotePath(projectPath);
|
|
34433
34465
|
mkdirSync6(dirname9(path), { recursive: true });
|
|
34434
|
-
|
|
34466
|
+
const scenes = {
|
|
34467
|
+
...readScenes(projectPath),
|
|
34468
|
+
[scene]: {
|
|
34469
|
+
boots: [...bootsOf(projectPath, scene), Math.round(announcedAfterMs)].slice(-BOOTS_KEPT),
|
|
34470
|
+
at: new Date().toISOString()
|
|
34471
|
+
}
|
|
34472
|
+
};
|
|
34473
|
+
writeFileSync8(path, JSON.stringify({ scenes }, null, 2), "utf8");
|
|
34435
34474
|
} catch {}
|
|
34436
34475
|
}
|
|
34437
34476
|
function halfAsLongAgain(lastBootMs) {
|
|
@@ -34443,7 +34482,7 @@ function waitSizedTo(lastBootMs, usual = ANNOUNCE_BUDGET_MS) {
|
|
|
34443
34482
|
}
|
|
34444
34483
|
return Math.min(LONGEST_SIZED_WAIT_MS, Math.max(usual, halfAsLongAgain(lastBootMs)));
|
|
34445
34484
|
}
|
|
34446
|
-
var LONGEST_SIZED_WAIT_MS = 60000;
|
|
34485
|
+
var MAIN_SCENE = "", BOOTS_KEPT = 5, LONGEST_SIZED_WAIT_MS = 60000;
|
|
34447
34486
|
var init_boot_note = __esm(() => {
|
|
34448
34487
|
init_runtime_client();
|
|
34449
34488
|
});
|
|
@@ -42695,7 +42734,8 @@ function classNamesOf(source) {
|
|
|
42695
42734
|
function callersOf(totals, name, scripts) {
|
|
42696
42735
|
const listed = [...scripts];
|
|
42697
42736
|
const sources = new Map(listed);
|
|
42698
|
-
const
|
|
42737
|
+
const perCallUs = perCallCostOf(totals);
|
|
42738
|
+
const rows = rowsOf(totals, (script) => sources.get(script) ?? null).map((row) => withOverhead(row, perCallUs));
|
|
42699
42739
|
const asked = name.includes(".") ? name.slice(0, name.lastIndexOf(".")) : null;
|
|
42700
42740
|
const bare = propertyOf(name) ?? bareName(name);
|
|
42701
42741
|
const ownerOf = (fn) => fn.includes(".") ? fn.slice(0, fn.lastIndexOf(".")) : null;
|
|
@@ -42728,7 +42768,12 @@ function callersOf(totals, name, scripts) {
|
|
|
42728
42768
|
}
|
|
42729
42769
|
}
|
|
42730
42770
|
sites.sort((a, b) => (b.withinTotalMs ?? -1) - (a.withinTotalMs ?? -1) || a.script.localeCompare(b.script) || a.line - b.line);
|
|
42731
|
-
return {
|
|
42771
|
+
return {
|
|
42772
|
+
target,
|
|
42773
|
+
sites: sites.slice(0, CALL_SITES),
|
|
42774
|
+
omitted: Math.max(0, sites.length - CALL_SITES),
|
|
42775
|
+
property
|
|
42776
|
+
};
|
|
42732
42777
|
}
|
|
42733
42778
|
var QUEUE_SETTING = "limits/debugger/max_queued_messages", QUEUE_DEFAULT = 2048, QUEUE_HEADROOM = 512, UNNAMED_PREFIX = "#", PROFILE_FROM_START = "GDHARNESS_PROFILE_FROM_START", ms = (seconds) => Math.round(seconds * 1e6) / 1000, RUNTIME_SCRIPT = "gdharness_runtime/runtime_autoload.gd", PROFILER_PROBE = "_profiler_probe", PROFILER_CALIBRATOR = "_calibrate_the_profiler", CALL_SITES = 40;
|
|
42734
42779
|
var init_script_profile = __esm(() => {
|
|
@@ -43108,7 +43153,7 @@ function runtimeVerdict(endpoint, after) {
|
|
|
43108
43153
|
};
|
|
43109
43154
|
}
|
|
43110
43155
|
const state = after.playingSeen === false ? "the editor has not yet reported the game as playing, which a restarted editor does not until its scan is over" : "the game is still running";
|
|
43111
|
-
const sized = after.sizedToMs === undefined ? "" : halfAsLongAgain(after.sizedToMs) > after.budgetMs ? `, the longest a start waits unasked though its last
|
|
43156
|
+
const sized = after.sizedToMs === undefined ? "" : halfAsLongAgain(after.sizedToMs) > after.budgetMs ? `, the longest a start waits unasked though the slowest of its last boots took ${after.sizedToMs}ms to announce,` : `, half as long again as the ${after.sizedToMs}ms the slowest of its last boots took,`;
|
|
43112
43157
|
return {
|
|
43113
43158
|
listening: false,
|
|
43114
43159
|
mayYetAnnounce: after.running,
|
|
@@ -43583,10 +43628,12 @@ function overheadRowsReturned(table, one) {
|
|
|
43583
43628
|
if (shown === table.overheadDominated) {
|
|
43584
43629
|
return one ? " Its row carries that share as overheadMs." : " Their rows carry that share as overheadMs.";
|
|
43585
43630
|
}
|
|
43631
|
+
const theReturned = returned === 1 ? "the one row returned" : `the ${returned} rows returned`;
|
|
43586
43632
|
if (shown === 0) {
|
|
43587
|
-
return one ? ` Its row, which carries that share as overheadMs,
|
|
43633
|
+
return one ? ` Its row, which carries that share as overheadMs, comes after ${theReturned}; limit ${table.overheadReach} returns it.` : ` Their rows, which carry that share as overheadMs, come after ${theReturned}; limit ${table.overheadReach} returns them all.`;
|
|
43588
43634
|
}
|
|
43589
|
-
|
|
43635
|
+
const among = returned === 1 ? "the one row returned is one of them" : `${shown} of them ${shown === 1 ? "is" : "are"} among ${theReturned}`;
|
|
43636
|
+
return ` Their rows carry that share as overheadMs, and ${among}; limit ${table.overheadReach} returns them all.`;
|
|
43590
43637
|
}
|
|
43591
43638
|
function namedFew(names) {
|
|
43592
43639
|
const shown = names.slice(0, 5);
|
|
@@ -43632,12 +43679,13 @@ function uncountedCallee(totals, name) {
|
|
|
43632
43679
|
}
|
|
43633
43680
|
function callersAnswer(totals, asked) {
|
|
43634
43681
|
const found = callersOf(totals, asked.name, asked.scripts());
|
|
43682
|
+
const byName = found.target.length === 0 ? `${CALLERS_FOUND_BY_NAME} ${uncountedCallee(totals, asked.name)}` : CALLERS_FOUND_BY_NAME;
|
|
43635
43683
|
return {
|
|
43636
43684
|
of: asked.name,
|
|
43637
43685
|
rows: found.target,
|
|
43638
43686
|
sites: found.sites,
|
|
43639
43687
|
omitted: found.omitted > 0 ? found.omitted : undefined,
|
|
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.
|
|
43688
|
+
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.property ? `${byName} ${PROPERTY_PLACES}` : byName
|
|
43641
43689
|
};
|
|
43642
43690
|
}
|
|
43643
43691
|
function foldedEntriesAnswer(log) {
|
|
@@ -46448,7 +46496,7 @@ class GodotServer {
|
|
|
46448
46496
|
platform: process.platform,
|
|
46449
46497
|
variables: process.env
|
|
46450
46498
|
});
|
|
46451
|
-
const runtimeWaitMs = this.announceBudget(args, project.value.path);
|
|
46499
|
+
const runtimeWaitMs = this.announceBudget(args, project.value.path, sceneArgument);
|
|
46452
46500
|
const editorWouldPlay = !headless || editorPlaysHeadless(project.value.file);
|
|
46453
46501
|
const silent = resolveSilent(args["silent"], process.env) && !headless;
|
|
46454
46502
|
const profiled = readBoolean(args, "profile") === true;
|
|
@@ -46470,6 +46518,7 @@ class GodotServer {
|
|
|
46470
46518
|
]);
|
|
46471
46519
|
}
|
|
46472
46520
|
started.announcedBefore = alreadyPlaying;
|
|
46521
|
+
started.bootScene = runtimeWaitMs.scene;
|
|
46473
46522
|
this.activeProcess = started;
|
|
46474
46523
|
const spawnedInstead = this.godotBridge.isConnected() && editorWouldPlay && given.value.length > 0 ? " The editor cannot be handed arguments for a game it plays, so this one was started " + "here: the debug_* tools answer only for a game the editor is playing." : this.godotBridge.isConnected() && editorWouldPlay && needsItsOwnProcess(asked.value) ? " The editor plays a game with its own environment, so a run given one was started " + "here: the debug_* tools answer only for a game the editor is playing." : this.godotBridge.isConnected() && editorWouldPlay && silent ? " The editor cannot play a game silent, which takes an engine flag, so this one was " + `started here${args["silent"] === true ? "" : ", silent because GDHARNESS_SILENT says so"}: the debug_* tools answer only for a game the editor is playing, and silent: false plays it through the editor.` : "";
|
|
46475
46524
|
const endedForThis = endedToStartThis(ended);
|
|
@@ -46559,17 +46608,18 @@ class GodotServer {
|
|
|
46559
46608
|
...going?.throughEditor === true ? { playingSeen: going.seenPlaying === true } : {}
|
|
46560
46609
|
});
|
|
46561
46610
|
}
|
|
46562
|
-
announceBudget(args, projectPath) {
|
|
46611
|
+
announceBudget(args, projectPath, scene) {
|
|
46612
|
+
const key = scene ?? MAIN_SCENE;
|
|
46563
46613
|
const asked = readNonNegativeNumber(args, "runtimeWaitMs");
|
|
46564
46614
|
if (asked !== undefined) {
|
|
46565
|
-
return { ms: asked };
|
|
46615
|
+
return { ms: asked, scene: key };
|
|
46566
46616
|
}
|
|
46567
|
-
const lastBoot = readBootNote(projectPath);
|
|
46617
|
+
const lastBoot = readBootNote(projectPath, key);
|
|
46568
46618
|
const ms = waitSizedTo(lastBoot);
|
|
46569
|
-
return lastBoot !== null && ms > ANNOUNCE_BUDGET_MS ? { ms, sizedToMs: lastBoot } : { ms };
|
|
46619
|
+
return lastBoot !== null && ms > ANNOUNCE_BUDGET_MS ? { ms, sizedToMs: lastBoot, scene: key } : { ms, scene: key };
|
|
46570
46620
|
}
|
|
46571
46621
|
noteBootOf(run, endpoint) {
|
|
46572
|
-
if (run.projectPath === null) {
|
|
46622
|
+
if (run.projectPath === null || run.bootScene === undefined) {
|
|
46573
46623
|
return;
|
|
46574
46624
|
}
|
|
46575
46625
|
let announcedAt;
|
|
@@ -46581,7 +46631,7 @@ class GodotServer {
|
|
|
46581
46631
|
const took = announcedAt - run.startedAt;
|
|
46582
46632
|
this.logDebug(`boot of pid ${endpoint.pid} announced ${Math.round(took)}ms after the start of ${run.projectPath}`);
|
|
46583
46633
|
if (took > 0 && took <= LONGEST_BOOT_NOTED_MS) {
|
|
46584
|
-
writeBootNote(run.projectPath, took);
|
|
46634
|
+
writeBootNote(run.projectPath, run.bootScene, took);
|
|
46585
46635
|
}
|
|
46586
46636
|
}
|
|
46587
46637
|
async playThroughEditor(scene, refreshedClasses, scanned, projectPath, alreadyPlaying, runtimeWaitMs, ended, profiled) {
|
|
@@ -46640,7 +46690,8 @@ class GodotServer {
|
|
|
46640
46690
|
brokeOn: null,
|
|
46641
46691
|
announcedBefore: alreadyPlaying,
|
|
46642
46692
|
seenPlaying: readBoolean(playAnswer, "playing") === true,
|
|
46643
|
-
...which === null ? {} : { play: which }
|
|
46693
|
+
...which === null ? {} : { play: which },
|
|
46694
|
+
bootScene: runtimeWaitMs.scene
|
|
46644
46695
|
};
|
|
46645
46696
|
this.activeProcess = played;
|
|
46646
46697
|
this.noteTheLatestRun({ projectPath, pid: null, startedAt });
|
|
@@ -46810,6 +46861,7 @@ class GodotServer {
|
|
|
46810
46861
|
}
|
|
46811
46862
|
running.endedHere = reason;
|
|
46812
46863
|
const announced = this.announcedPidOf(running);
|
|
46864
|
+
await this.sendTheProfileFirst(running, announced);
|
|
46813
46865
|
if (running.throughEditor) {
|
|
46814
46866
|
const asked = await this.handleViaBridge("stop_playing", {});
|
|
46815
46867
|
if (asked.isError === true) {
|
|
@@ -46880,6 +46932,25 @@ class GodotServer {
|
|
|
46880
46932
|
running.log.record(gone ? "info" : "warning", declined !== null ? `gdharness could not signal pid ${running.pid}: the operating system refused it (${declined}). It had described that process as ${described}.` : !gone ? `gdharness signalled pid ${running.pid}, which the operating system described as ${described}, and it was still running ${stopWaitMs() / 1000} seconds later.` : landed ? `gdharness ended pid ${running.pid}, which the operating system described as ${described}.` : `gdharness signalled pid ${running.pid} and it was already gone; the operating system had described it as ${described}.`);
|
|
46881
46933
|
return gone ? "gone" : "lingering";
|
|
46882
46934
|
}
|
|
46935
|
+
async sendTheProfileFirst(run, announced) {
|
|
46936
|
+
if (announced === undefined) {
|
|
46937
|
+
return;
|
|
46938
|
+
}
|
|
46939
|
+
const profiled = run.throughEditor ? await this.theEditorIsProfiling() : run.profile !== undefined;
|
|
46940
|
+
const endpoint = profiled ? runtimesAnnounced().running.find((one) => one.pid === announced) : undefined;
|
|
46941
|
+
if (endpoint === undefined) {
|
|
46942
|
+
return;
|
|
46943
|
+
}
|
|
46944
|
+
await runtimeRequest(endpoint, "send_profile", {}, PROFILE_SEND_MS);
|
|
46945
|
+
}
|
|
46946
|
+
async theEditorIsProfiling() {
|
|
46947
|
+
const read = await this.handleViaBridge("profile_read", {});
|
|
46948
|
+
if (read.isError === true) {
|
|
46949
|
+
return false;
|
|
46950
|
+
}
|
|
46951
|
+
const held = asParams(JSON.parse(read.content[0]?.text ?? "{}"));
|
|
46952
|
+
return readBoolean(held, "profiled") === true && readBoolean(held, "complete") !== true;
|
|
46953
|
+
}
|
|
46883
46954
|
async endThroughTheKeeper(run, pid, keeper) {
|
|
46884
46955
|
const asked = await askTheKeeperToStop(keeper, KEEPER_ANSWER_MS);
|
|
46885
46956
|
if (typeof asked !== "string") {
|
|
@@ -48555,7 +48626,7 @@ class GodotServer {
|
|
|
48555
48626
|
return null;
|
|
48556
48627
|
}
|
|
48557
48628
|
const which = run.throughEditor ? "the game the editor is playing for it" : `pid ${run.pid ?? "unknown"}`;
|
|
48558
|
-
const comingUpUntil = run.startedAt + waitSizedTo(readBootNote(project)) + budgetMs;
|
|
48629
|
+
const comingUpUntil = run.startedAt + waitSizedTo(readBootNote(project, run.bootScene ?? MAIN_SCENE)) + budgetMs;
|
|
48559
48630
|
const remaining = comingUpUntil - Date.now();
|
|
48560
48631
|
if (remaining <= 0) {
|
|
48561
48632
|
const seconds = Math.round((Date.now() - run.startedAt) / 1000);
|
|
@@ -48773,7 +48844,7 @@ async function runGodotServer() {
|
|
|
48773
48844
|
const server = new GodotServer;
|
|
48774
48845
|
await server.run();
|
|
48775
48846
|
}
|
|
48776
|
-
var UPDATE_NOTICE_EVERY = 500, FEEDBACK_NOTICE_EVERY = 250, run4, __dirname2, EDITOR_SERVERS_START_MS = 20000, HOLD_PING_MS = 2000, SLOWEST_FRAME_RATE = 20, PLAY_STARTS_WITHIN_MS = 30000, KEEPER_ANSWER_MS = 15000, EXIT_REPORTED_WITHIN_MS = 2000, CONSOLE_WRAPPER, LONGEST_BOOT_NOTED_MS, MOST_GROUPS = 20, VALUE_NAMES, BRIDGE_RETRY_MS = 2000, SUCCESSOR_CHECK_MS = 1e4, CONSOLE_WATCH_MS = 250, BOUNDARY_WAIT_MS = 2000, HANDOVER_MS, HANDOVER_POLL_MS = 250, CACHE_WRITE_MS = 2000, STREAMS_GRACE_MS = 2000, PLAYING_STATUS_MS = 1000, SCAN_START_MS = 2000, SCAN_SETTLED_MS = 500, REIMPORT_TIMEOUT_MS = 300000, AUDIO_BUS_OPS, STALLED_AFTER_MS = 30000, TEST_HEARTBEAT_MS = 20000, DEPENDED_FAILED = "Failed to compile depended scripts", WAIT_FOR_RUN_MS = 600000, RUN_POLL_MS = 250, PATH_SOLUTIONS, PROJECT_FILE_ARGUMENTS, DEBUG_STATE_CALLS, PROJECT_INFO_SECTIONS2, RUNNER_DIRECTORY = "addons/gdUnit4/", PROFILE_FUNCTIONS = 30, PROFILER_ON_MS = 3000, CALLERS_FOUND_BY_NAME = "Found by name in the project's scripts, since the engine's profiler counts calls and time per function and records nothing about who made them: a method of the same name on another class is listed too, unless one class was asked about and it is called through another class by name or through a variable declared as one. Each place carries the profile's numbers for the function it is written in.", MOST_ENGINE_ENTRIES = 200, ERROR_ASSERT = "GdUnitGodotErrorAssertImpl.gd", EXTERNAL_MEMBER = 'Could not resolve external class member "';
|
|
48847
|
+
var UPDATE_NOTICE_EVERY = 500, FEEDBACK_NOTICE_EVERY = 250, run4, __dirname2, EDITOR_SERVERS_START_MS = 20000, HOLD_PING_MS = 2000, SLOWEST_FRAME_RATE = 20, PLAY_STARTS_WITHIN_MS = 30000, KEEPER_ANSWER_MS = 15000, EXIT_REPORTED_WITHIN_MS = 2000, CONSOLE_WRAPPER, LONGEST_BOOT_NOTED_MS, PROFILE_SEND_MS = 5000, MOST_GROUPS = 20, VALUE_NAMES, BRIDGE_RETRY_MS = 2000, SUCCESSOR_CHECK_MS = 1e4, CONSOLE_WATCH_MS = 250, BOUNDARY_WAIT_MS = 2000, HANDOVER_MS, HANDOVER_POLL_MS = 250, CACHE_WRITE_MS = 2000, STREAMS_GRACE_MS = 2000, PLAYING_STATUS_MS = 1000, SCAN_START_MS = 2000, SCAN_SETTLED_MS = 500, REIMPORT_TIMEOUT_MS = 300000, AUDIO_BUS_OPS, STALLED_AFTER_MS = 30000, TEST_HEARTBEAT_MS = 20000, DEPENDED_FAILED = "Failed to compile depended scripts", WAIT_FOR_RUN_MS = 600000, RUN_POLL_MS = 250, PATH_SOLUTIONS, PROJECT_FILE_ARGUMENTS, DEBUG_STATE_CALLS, PROJECT_INFO_SECTIONS2, RUNNER_DIRECTORY = "addons/gdUnit4/", PROFILE_FUNCTIONS = 30, PROFILER_ON_MS = 3000, PROPERTY_PLACES = "It is a property, so each place says whether it writes it (access write), which runs its set block, or reads it (access read), which runs its get block: withinCalls on a read says nothing of the set block, nor on a write of the get block unless the write is a compound one such as +=. Inside the property's own blocks its name is the value stored, which the engine reads and writes without running them, so those lines are not listed.", CALLERS_FOUND_BY_NAME = "Found by name in the project's scripts, since the engine's profiler counts calls and time per function and records nothing about who made them: a method of the same name on another class is listed too, unless one class was asked about and it is called through another class by name or through a variable declared as one. Each place carries the profile's numbers for the function it is written in.", MOST_ENGINE_ENTRIES = 200, ERROR_ASSERT = "GdUnitGodotErrorAssertImpl.gd", EXTERNAL_MEMBER = 'Could not resolve external class member "';
|
|
48777
48848
|
var init_server3 = __esm(() => {
|
|
48778
48849
|
init_mcp();
|
|
48779
48850
|
init_stdio2();
|
|
@@ -140,6 +140,7 @@ func _init() -> void:
|
|
|
140
140
|
"wait_frames": _waits.wait_frames,
|
|
141
141
|
"wait_signal": _waits.wait_signal,
|
|
142
142
|
"wait_until": _waits.wait_until,
|
|
143
|
+
"send_profile": _send_profile_now,
|
|
143
144
|
}
|
|
144
145
|
_wait_for_the_profiler()
|
|
145
146
|
|
|
@@ -236,6 +237,16 @@ func _send_the_profile() -> void:
|
|
|
236
237
|
)
|
|
237
238
|
|
|
238
239
|
|
|
240
|
+
## The totals sent now, for a stop that is about to end the game without letting it quit: ended by
|
|
241
|
+
## its process, a game takes the totals with it, and every stopped profile came back as the frames
|
|
242
|
+
## summed. Nothing of the game's own runs, which quitting the tree would have done.
|
|
243
|
+
func _send_profile_now(_params: Dictionary) -> Dictionary:
|
|
244
|
+
if not (EngineDebugger.is_active() and EngineDebugger.is_profiling("servers")):
|
|
245
|
+
return {"sent": false}
|
|
246
|
+
_send_the_profile()
|
|
247
|
+
return {"sent": true, "confirmed": _profile_received}
|
|
248
|
+
|
|
249
|
+
|
|
239
250
|
## What the script profiler costs per call in this game, measured once each time it is switched on.
|
|
240
251
|
##
|
|
241
252
|
## The profiler times every call it counts, and that bookkeeping lands in the self time of the
|
package/build/index.js
CHANGED
|
@@ -26036,19 +26036,35 @@ function runtimeRequest(endpoint, command, params, timeoutMs, keepListening = fa
|
|
|
26036
26036
|
function bootNotePath(projectPath) {
|
|
26037
26037
|
return join3(projectPath, ".godot", "gdharness-boot.json");
|
|
26038
26038
|
}
|
|
26039
|
-
|
|
26039
|
+
var MAIN_SCENE = "";
|
|
26040
|
+
function readScenes(projectPath) {
|
|
26040
26041
|
try {
|
|
26041
|
-
|
|
26042
|
-
return noted !== undefined && Number.isFinite(noted) && noted > 0 ? noted : null;
|
|
26042
|
+
return asParams(asParams(JSON.parse(readFileSync4(bootNotePath(projectPath), "utf8")))["scenes"]);
|
|
26043
26043
|
} catch {
|
|
26044
|
-
return
|
|
26044
|
+
return {};
|
|
26045
26045
|
}
|
|
26046
26046
|
}
|
|
26047
|
-
|
|
26047
|
+
var BOOTS_KEPT = 5;
|
|
26048
|
+
function bootsOf(projectPath, scene) {
|
|
26049
|
+
const kept = asParams(readScenes(projectPath)[scene])["boots"];
|
|
26050
|
+
return Array.isArray(kept) ? kept.filter((one) => typeof one === "number" && Number.isFinite(one) && one > 0) : [];
|
|
26051
|
+
}
|
|
26052
|
+
function readBootNote(projectPath, scene) {
|
|
26053
|
+
const boots = bootsOf(projectPath, scene);
|
|
26054
|
+
return boots.length === 0 ? null : Math.max(...boots);
|
|
26055
|
+
}
|
|
26056
|
+
function writeBootNote(projectPath, scene, announcedAfterMs) {
|
|
26048
26057
|
try {
|
|
26049
26058
|
const path = bootNotePath(projectPath);
|
|
26050
26059
|
mkdirSync2(dirname2(path), { recursive: true });
|
|
26051
|
-
|
|
26060
|
+
const scenes = {
|
|
26061
|
+
...readScenes(projectPath),
|
|
26062
|
+
[scene]: {
|
|
26063
|
+
boots: [...bootsOf(projectPath, scene), Math.round(announcedAfterMs)].slice(-BOOTS_KEPT),
|
|
26064
|
+
at: new Date().toISOString()
|
|
26065
|
+
}
|
|
26066
|
+
};
|
|
26067
|
+
writeFileSync2(path, JSON.stringify({ scenes }, null, 2), "utf8");
|
|
26052
26068
|
} catch {}
|
|
26053
26069
|
}
|
|
26054
26070
|
var LONGEST_SIZED_WAIT_MS = 60000;
|
|
@@ -26613,6 +26629,7 @@ function bodyCallsSuper(code, line, lambda) {
|
|
|
26613
26629
|
}
|
|
26614
26630
|
return false;
|
|
26615
26631
|
}
|
|
26632
|
+
var ASSIGNED = /^(?:\*\*|<<|>>|[-+*/%&|^])?=(?!=)/;
|
|
26616
26633
|
function parametersOf(text, from) {
|
|
26617
26634
|
const open = text.indexOf("(", from);
|
|
26618
26635
|
if (open === -1) {
|
|
@@ -26731,8 +26748,31 @@ function callSitesOf(source, name, otherClasses = new Set, property = false) {
|
|
|
26731
26748
|
const declared = bodyHolding(shape.body, at).declarations.find((one) => one.name === member && (one.kind === "var" || one.kind === "const"));
|
|
26732
26749
|
return declared === undefined ? null : declaredClass(codeLines[lines.positionOf(declared.offset).line] ?? "", member);
|
|
26733
26750
|
};
|
|
26751
|
+
const blockUnder = (declared) => {
|
|
26752
|
+
const indent = (codeLines[declared] ?? "").search(/\S|$/);
|
|
26753
|
+
let last = declared;
|
|
26754
|
+
for (let index = declared + 1;index < codeLines.length; index += 1) {
|
|
26755
|
+
const text = codeLines[index] ?? "";
|
|
26756
|
+
if (text.trim() === "") {
|
|
26757
|
+
continue;
|
|
26758
|
+
}
|
|
26759
|
+
if (text.search(/\S/) <= indent) {
|
|
26760
|
+
break;
|
|
26761
|
+
}
|
|
26762
|
+
last = index;
|
|
26763
|
+
}
|
|
26764
|
+
return [declared, last];
|
|
26765
|
+
};
|
|
26766
|
+
const accessorsIn = (body) => [
|
|
26767
|
+
...body.declarations.filter((one) => one.kind === "var" && one.name === name).map((one) => blockUnder(lines.positionOf(one.offset).line)),
|
|
26768
|
+
...body.inner.flatMap(accessorsIn)
|
|
26769
|
+
];
|
|
26770
|
+
const accessors = property ? accessorsIn(shape.body) : [];
|
|
26734
26771
|
return occurrencesOf(source, name, regions).filter((one) => one.kind === "code" && one.declaredAs === null).filter((one) => {
|
|
26735
26772
|
const index = lines.positionOf(one.offset).line;
|
|
26773
|
+
if (!one.afterDot && accessors.some(([declared, last]) => index > declared && index <= last)) {
|
|
26774
|
+
return false;
|
|
26775
|
+
}
|
|
26736
26776
|
if (one.afterDot) {
|
|
26737
26777
|
const receiver = receiverBefore(code, one.offset);
|
|
26738
26778
|
if (receiver === null || otherClasses.size === 0) {
|
|
@@ -26754,7 +26794,8 @@ function callSitesOf(source, name, otherClasses = new Set, property = false) {
|
|
|
26754
26794
|
}).map((one) => {
|
|
26755
26795
|
const line = lines.positionOf(one.offset).line + 1;
|
|
26756
26796
|
const after = code.slice(one.offset + name.length).trimStart();
|
|
26757
|
-
|
|
26797
|
+
const within = enclosingFunction(source, line, code);
|
|
26798
|
+
return property ? { line, within, access: ASSIGNED.test(after) ? "write" : "read" } : { line, within, called: after.startsWith("(") };
|
|
26758
26799
|
});
|
|
26759
26800
|
}
|
|
26760
26801
|
function collectEnumValues(text, from, into) {
|
|
@@ -33491,7 +33532,8 @@ var CALL_SITES = 40;
|
|
|
33491
33532
|
function callersOf(totals, name, scripts) {
|
|
33492
33533
|
const listed = [...scripts];
|
|
33493
33534
|
const sources = new Map(listed);
|
|
33494
|
-
const
|
|
33535
|
+
const perCallUs = perCallCostOf(totals);
|
|
33536
|
+
const rows = rowsOf(totals, (script) => sources.get(script) ?? null).map((row) => withOverhead(row, perCallUs));
|
|
33495
33537
|
const asked = name.includes(".") ? name.slice(0, name.lastIndexOf(".")) : null;
|
|
33496
33538
|
const bare = propertyOf(name) ?? bareName(name);
|
|
33497
33539
|
const ownerOf = (fn) => fn.includes(".") ? fn.slice(0, fn.lastIndexOf(".")) : null;
|
|
@@ -33524,7 +33566,12 @@ function callersOf(totals, name, scripts) {
|
|
|
33524
33566
|
}
|
|
33525
33567
|
}
|
|
33526
33568
|
sites.sort((a, b) => (b.withinTotalMs ?? -1) - (a.withinTotalMs ?? -1) || a.script.localeCompare(b.script) || a.line - b.line);
|
|
33527
|
-
return {
|
|
33569
|
+
return {
|
|
33570
|
+
target,
|
|
33571
|
+
sites: sites.slice(0, CALL_SITES),
|
|
33572
|
+
omitted: Math.max(0, sites.length - CALL_SITES),
|
|
33573
|
+
property
|
|
33574
|
+
};
|
|
33528
33575
|
}
|
|
33529
33576
|
|
|
33530
33577
|
// src/setup.ts
|
|
@@ -34492,7 +34539,7 @@ var TOOL_SPECS = [
|
|
|
34492
34539
|
type: "integer",
|
|
34493
34540
|
minimum: 0,
|
|
34494
34541
|
ops: ["start"],
|
|
34495
|
-
description: "start: how long to wait for the game to announce its runtime before answering. Default 5000, or half as long again as the last
|
|
34542
|
+
description: "start: how long to wait for the game to announce its runtime before answering. Default 5000, or half as long again as the slowest of the last five boots of the same scene when that is more, up to 60000: the server notes how long each game took to announce, per scene and for the main scene when none is named, since a boot moves with the load on the machine, so a scene that boots slowly is waited for from its second start on without being asked to, and a harness scene that announces late sets nothing for the main scene. What it waits for is the first frame, so everything the game does before drawing one is inside it, including work the args just asked for: a flag that simulates six years of game time before anything is drawn makes the announcement that late, on a project that announces promptly without it. Raise it for such a run rather than reading runtime listening false as a fault. 0 does not wait at all, which is the one to pass for a scene that announces nothing by construction, such as a bench that prints and quits."
|
|
34496
34543
|
},
|
|
34497
34544
|
frames: {
|
|
34498
34545
|
type: "integer",
|
|
@@ -34514,7 +34561,7 @@ var TOOL_SPECS = [
|
|
|
34514
34561
|
callersOf: {
|
|
34515
34562
|
type: "string",
|
|
34516
34563
|
ops: ["profile"],
|
|
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'."
|
|
34564
|
+
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, each saying instead of called whether it writes the property (access write), which runs the set block, or reads it (access read), which runs the get block, and the rows are both blocks'. Inside the property's own blocks its name is the value stored, which the engine reads and writes without running them, so those lines are not places."
|
|
34518
34565
|
},
|
|
34519
34566
|
limit: {
|
|
34520
34567
|
type: "integer",
|
|
@@ -35521,6 +35568,7 @@ function endedToStartThis(ended) {
|
|
|
35521
35568
|
return ` ${which} was ended to start this one; its output is no longer what editor_output answers about.`;
|
|
35522
35569
|
}
|
|
35523
35570
|
var LONGEST_BOOT_NOTED_MS = 5 * 60000;
|
|
35571
|
+
var PROFILE_SEND_MS = 5000;
|
|
35524
35572
|
var MOST_GROUPS = 20;
|
|
35525
35573
|
function runtimeVerdict(endpoint, after) {
|
|
35526
35574
|
if (endpoint !== null) {
|
|
@@ -35542,7 +35590,7 @@ function runtimeVerdict(endpoint, after) {
|
|
|
35542
35590
|
};
|
|
35543
35591
|
}
|
|
35544
35592
|
const state = after.playingSeen === false ? "the editor has not yet reported the game as playing, which a restarted editor does not until its scan is over" : "the game is still running";
|
|
35545
|
-
const sized = after.sizedToMs === undefined ? "" : halfAsLongAgain(after.sizedToMs) > after.budgetMs ? `, the longest a start waits unasked though its last
|
|
35593
|
+
const sized = after.sizedToMs === undefined ? "" : halfAsLongAgain(after.sizedToMs) > after.budgetMs ? `, the longest a start waits unasked though the slowest of its last boots took ${after.sizedToMs}ms to announce,` : `, half as long again as the ${after.sizedToMs}ms the slowest of its last boots took,`;
|
|
35546
35594
|
return {
|
|
35547
35595
|
listening: false,
|
|
35548
35596
|
mayYetAnnounce: after.running,
|
|
@@ -35992,6 +36040,7 @@ function playedCap(held) {
|
|
|
35992
36040
|
totalCapped: readBoolean(held, "totalCapped") === true
|
|
35993
36041
|
};
|
|
35994
36042
|
}
|
|
36043
|
+
var PROPERTY_PLACES = "It is a property, so each place says whether it writes it (access write), which runs its set block, or reads it (access read), which runs its get block: withinCalls on a read says nothing of the set block, nor on a write of the get block unless the write is a compound one such as +=. Inside the property's own blocks its name is the value stored, which the engine reads and writes without running them, so those lines are not listed.";
|
|
35995
36044
|
var CALLERS_FOUND_BY_NAME = "Found by name in the project's scripts, since the engine's profiler counts calls and time per function and records nothing about who made them: a method of the same name on another class is listed too, unless one class was asked about and it is called through another class by name or through a variable declared as one. Each place carries the profile's numbers for the function it is written in.";
|
|
35996
36045
|
function projectSourceOf(projectPath) {
|
|
35997
36046
|
const read = new Map;
|
|
@@ -36078,10 +36127,12 @@ function overheadRowsReturned(table, one) {
|
|
|
36078
36127
|
if (shown === table.overheadDominated) {
|
|
36079
36128
|
return one ? " Its row carries that share as overheadMs." : " Their rows carry that share as overheadMs.";
|
|
36080
36129
|
}
|
|
36130
|
+
const theReturned = returned === 1 ? "the one row returned" : `the ${returned} rows returned`;
|
|
36081
36131
|
if (shown === 0) {
|
|
36082
|
-
return one ? ` Its row, which carries that share as overheadMs,
|
|
36132
|
+
return one ? ` Its row, which carries that share as overheadMs, comes after ${theReturned}; limit ${table.overheadReach} returns it.` : ` Their rows, which carry that share as overheadMs, come after ${theReturned}; limit ${table.overheadReach} returns them all.`;
|
|
36083
36133
|
}
|
|
36084
|
-
|
|
36134
|
+
const among = returned === 1 ? "the one row returned is one of them" : `${shown} of them ${shown === 1 ? "is" : "are"} among ${theReturned}`;
|
|
36135
|
+
return ` Their rows carry that share as overheadMs, and ${among}; limit ${table.overheadReach} returns them all.`;
|
|
36085
36136
|
}
|
|
36086
36137
|
function namedFew(names) {
|
|
36087
36138
|
const shown = names.slice(0, 5);
|
|
@@ -36127,12 +36178,13 @@ function uncountedCallee(totals, name) {
|
|
|
36127
36178
|
}
|
|
36128
36179
|
function callersAnswer(totals, asked) {
|
|
36129
36180
|
const found = callersOf(totals, asked.name, asked.scripts());
|
|
36181
|
+
const byName = found.target.length === 0 ? `${CALLERS_FOUND_BY_NAME} ${uncountedCallee(totals, asked.name)}` : CALLERS_FOUND_BY_NAME;
|
|
36130
36182
|
return {
|
|
36131
36183
|
of: asked.name,
|
|
36132
36184
|
rows: found.target,
|
|
36133
36185
|
sites: found.sites,
|
|
36134
36186
|
omitted: found.omitted > 0 ? found.omitted : undefined,
|
|
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.
|
|
36187
|
+
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.property ? `${byName} ${PROPERTY_PLACES}` : byName
|
|
36136
36188
|
};
|
|
36137
36189
|
}
|
|
36138
36190
|
var MOST_ENGINE_ENTRIES = 200;
|
|
@@ -38946,7 +38998,7 @@ class GodotServer {
|
|
|
38946
38998
|
platform: process.platform,
|
|
38947
38999
|
variables: process.env
|
|
38948
39000
|
});
|
|
38949
|
-
const runtimeWaitMs = this.announceBudget(args, project.value.path);
|
|
39001
|
+
const runtimeWaitMs = this.announceBudget(args, project.value.path, sceneArgument);
|
|
38950
39002
|
const editorWouldPlay = !headless || editorPlaysHeadless(project.value.file);
|
|
38951
39003
|
const silent = resolveSilent(args["silent"], process.env) && !headless;
|
|
38952
39004
|
const profiled = readBoolean(args, "profile") === true;
|
|
@@ -38968,6 +39020,7 @@ class GodotServer {
|
|
|
38968
39020
|
]);
|
|
38969
39021
|
}
|
|
38970
39022
|
started.announcedBefore = alreadyPlaying;
|
|
39023
|
+
started.bootScene = runtimeWaitMs.scene;
|
|
38971
39024
|
this.activeProcess = started;
|
|
38972
39025
|
const spawnedInstead = this.godotBridge.isConnected() && editorWouldPlay && given.value.length > 0 ? " The editor cannot be handed arguments for a game it plays, so this one was started " + "here: the debug_* tools answer only for a game the editor is playing." : this.godotBridge.isConnected() && editorWouldPlay && needsItsOwnProcess(asked.value) ? " The editor plays a game with its own environment, so a run given one was started " + "here: the debug_* tools answer only for a game the editor is playing." : this.godotBridge.isConnected() && editorWouldPlay && silent ? " The editor cannot play a game silent, which takes an engine flag, so this one was " + `started here${args["silent"] === true ? "" : ", silent because GDHARNESS_SILENT says so"}: the debug_* tools answer only for a game the editor is playing, and silent: false plays it through the editor.` : "";
|
|
38973
39026
|
const endedForThis = endedToStartThis(ended);
|
|
@@ -39057,17 +39110,18 @@ class GodotServer {
|
|
|
39057
39110
|
...going?.throughEditor === true ? { playingSeen: going.seenPlaying === true } : {}
|
|
39058
39111
|
});
|
|
39059
39112
|
}
|
|
39060
|
-
announceBudget(args, projectPath) {
|
|
39113
|
+
announceBudget(args, projectPath, scene) {
|
|
39114
|
+
const key = scene ?? MAIN_SCENE;
|
|
39061
39115
|
const asked = readNonNegativeNumber(args, "runtimeWaitMs");
|
|
39062
39116
|
if (asked !== undefined) {
|
|
39063
|
-
return { ms: asked };
|
|
39117
|
+
return { ms: asked, scene: key };
|
|
39064
39118
|
}
|
|
39065
|
-
const lastBoot = readBootNote(projectPath);
|
|
39119
|
+
const lastBoot = readBootNote(projectPath, key);
|
|
39066
39120
|
const ms = waitSizedTo(lastBoot);
|
|
39067
|
-
return lastBoot !== null && ms > ANNOUNCE_BUDGET_MS ? { ms, sizedToMs: lastBoot } : { ms };
|
|
39121
|
+
return lastBoot !== null && ms > ANNOUNCE_BUDGET_MS ? { ms, sizedToMs: lastBoot, scene: key } : { ms, scene: key };
|
|
39068
39122
|
}
|
|
39069
39123
|
noteBootOf(run, endpoint) {
|
|
39070
|
-
if (run.projectPath === null) {
|
|
39124
|
+
if (run.projectPath === null || run.bootScene === undefined) {
|
|
39071
39125
|
return;
|
|
39072
39126
|
}
|
|
39073
39127
|
let announcedAt;
|
|
@@ -39079,7 +39133,7 @@ class GodotServer {
|
|
|
39079
39133
|
const took = announcedAt - run.startedAt;
|
|
39080
39134
|
this.logDebug(`boot of pid ${endpoint.pid} announced ${Math.round(took)}ms after the start of ${run.projectPath}`);
|
|
39081
39135
|
if (took > 0 && took <= LONGEST_BOOT_NOTED_MS) {
|
|
39082
|
-
writeBootNote(run.projectPath, took);
|
|
39136
|
+
writeBootNote(run.projectPath, run.bootScene, took);
|
|
39083
39137
|
}
|
|
39084
39138
|
}
|
|
39085
39139
|
async playThroughEditor(scene, refreshedClasses, scanned, projectPath, alreadyPlaying, runtimeWaitMs, ended, profiled) {
|
|
@@ -39138,7 +39192,8 @@ class GodotServer {
|
|
|
39138
39192
|
brokeOn: null,
|
|
39139
39193
|
announcedBefore: alreadyPlaying,
|
|
39140
39194
|
seenPlaying: readBoolean(playAnswer, "playing") === true,
|
|
39141
|
-
...which === null ? {} : { play: which }
|
|
39195
|
+
...which === null ? {} : { play: which },
|
|
39196
|
+
bootScene: runtimeWaitMs.scene
|
|
39142
39197
|
};
|
|
39143
39198
|
this.activeProcess = played;
|
|
39144
39199
|
this.noteTheLatestRun({ projectPath, pid: null, startedAt });
|
|
@@ -39308,6 +39363,7 @@ class GodotServer {
|
|
|
39308
39363
|
}
|
|
39309
39364
|
running.endedHere = reason;
|
|
39310
39365
|
const announced = this.announcedPidOf(running);
|
|
39366
|
+
await this.sendTheProfileFirst(running, announced);
|
|
39311
39367
|
if (running.throughEditor) {
|
|
39312
39368
|
const asked = await this.handleViaBridge("stop_playing", {});
|
|
39313
39369
|
if (asked.isError === true) {
|
|
@@ -39378,6 +39434,25 @@ class GodotServer {
|
|
|
39378
39434
|
running.log.record(gone ? "info" : "warning", declined !== null ? `gdharness could not signal pid ${running.pid}: the operating system refused it (${declined}). It had described that process as ${described}.` : !gone ? `gdharness signalled pid ${running.pid}, which the operating system described as ${described}, and it was still running ${stopWaitMs() / 1000} seconds later.` : landed ? `gdharness ended pid ${running.pid}, which the operating system described as ${described}.` : `gdharness signalled pid ${running.pid} and it was already gone; the operating system had described it as ${described}.`);
|
|
39379
39435
|
return gone ? "gone" : "lingering";
|
|
39380
39436
|
}
|
|
39437
|
+
async sendTheProfileFirst(run, announced) {
|
|
39438
|
+
if (announced === undefined) {
|
|
39439
|
+
return;
|
|
39440
|
+
}
|
|
39441
|
+
const profiled = run.throughEditor ? await this.theEditorIsProfiling() : run.profile !== undefined;
|
|
39442
|
+
const endpoint = profiled ? runtimesAnnounced().running.find((one) => one.pid === announced) : undefined;
|
|
39443
|
+
if (endpoint === undefined) {
|
|
39444
|
+
return;
|
|
39445
|
+
}
|
|
39446
|
+
await runtimeRequest(endpoint, "send_profile", {}, PROFILE_SEND_MS);
|
|
39447
|
+
}
|
|
39448
|
+
async theEditorIsProfiling() {
|
|
39449
|
+
const read = await this.handleViaBridge("profile_read", {});
|
|
39450
|
+
if (read.isError === true) {
|
|
39451
|
+
return false;
|
|
39452
|
+
}
|
|
39453
|
+
const held = asParams(JSON.parse(read.content[0]?.text ?? "{}"));
|
|
39454
|
+
return readBoolean(held, "profiled") === true && readBoolean(held, "complete") !== true;
|
|
39455
|
+
}
|
|
39381
39456
|
async endThroughTheKeeper(run, pid, keeper) {
|
|
39382
39457
|
const asked = await askTheKeeperToStop(keeper, KEEPER_ANSWER_MS);
|
|
39383
39458
|
if (typeof asked !== "string") {
|
|
@@ -41053,7 +41128,7 @@ class GodotServer {
|
|
|
41053
41128
|
return null;
|
|
41054
41129
|
}
|
|
41055
41130
|
const which = run.throughEditor ? "the game the editor is playing for it" : `pid ${run.pid ?? "unknown"}`;
|
|
41056
|
-
const comingUpUntil = run.startedAt + waitSizedTo(readBootNote(project)) + budgetMs;
|
|
41131
|
+
const comingUpUntil = run.startedAt + waitSizedTo(readBootNote(project, run.bootScene ?? MAIN_SCENE)) + budgetMs;
|
|
41057
41132
|
const remaining = comingUpUntil - Date.now();
|
|
41058
41133
|
if (remaining <= 0) {
|
|
41059
41134
|
const seconds = Math.round((Date.now() - run.startedAt) / 1000);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gdharness",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.10",
|
|
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",
|