gdharness 1.2.5 → 1.2.7
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
CHANGED
|
@@ -8129,7 +8129,7 @@ function alive(pid) {
|
|
|
8129
8129
|
var init_alive = () => {};
|
|
8130
8130
|
|
|
8131
8131
|
// src/scratch.ts
|
|
8132
|
-
import { existsSync as existsSync6, mkdtempSync, readdirSync as
|
|
8132
|
+
import { existsSync as existsSync6, mkdtempSync, readdirSync as readdirSync3, rmSync as rmSync4 } from "node:fs";
|
|
8133
8133
|
import { tmpdir } from "node:os";
|
|
8134
8134
|
import { join as join6 } from "node:path";
|
|
8135
8135
|
import process6 from "node:process";
|
|
@@ -8139,7 +8139,7 @@ function scratchDirectory(kind) {
|
|
|
8139
8139
|
function sweepAbandonedScratch(directory = tmpdir()) {
|
|
8140
8140
|
let entries;
|
|
8141
8141
|
try {
|
|
8142
|
-
entries =
|
|
8142
|
+
entries = readdirSync3(directory);
|
|
8143
8143
|
} catch {
|
|
8144
8144
|
return;
|
|
8145
8145
|
}
|
|
@@ -9461,7 +9461,7 @@ var init_gdscript_source = __esm(() => {
|
|
|
9461
9461
|
});
|
|
9462
9462
|
|
|
9463
9463
|
// src/class-cache.ts
|
|
9464
|
-
import { existsSync as existsSync9, readdirSync as
|
|
9464
|
+
import { existsSync as existsSync9, readdirSync as readdirSync4, readFileSync as readFileSync7, statSync as statSync4 } from "node:fs";
|
|
9465
9465
|
import { join as join9, posix } from "node:path";
|
|
9466
9466
|
function isToolScript(source) {
|
|
9467
9467
|
return TOOL.test(codeOf(source));
|
|
@@ -9481,7 +9481,7 @@ function eachScript(projectPath, each) {
|
|
|
9481
9481
|
if (existsSync9(join9(directory, ".gdignore"))) {
|
|
9482
9482
|
return;
|
|
9483
9483
|
}
|
|
9484
|
-
for (const entry of
|
|
9484
|
+
for (const entry of readdirSync4(directory, { withFileTypes: true })) {
|
|
9485
9485
|
if (entry.name.startsWith(".")) {
|
|
9486
9486
|
continue;
|
|
9487
9487
|
}
|
|
@@ -17266,7 +17266,7 @@ import {
|
|
|
17266
17266
|
cpSync,
|
|
17267
17267
|
existsSync as existsSync10,
|
|
17268
17268
|
mkdirSync as mkdirSync4,
|
|
17269
|
-
readdirSync as
|
|
17269
|
+
readdirSync as readdirSync5,
|
|
17270
17270
|
readFileSync as readFileSync9,
|
|
17271
17271
|
rmdirSync,
|
|
17272
17272
|
rmSync as rmSync5,
|
|
@@ -17284,7 +17284,7 @@ function runtimeAddonDigest(from = shippedAddonsDirectory()) {
|
|
|
17284
17284
|
function addonsDigest(names, from) {
|
|
17285
17285
|
const hash = createHash3("sha256");
|
|
17286
17286
|
const walk = (directory, relative) => {
|
|
17287
|
-
for (const name of
|
|
17287
|
+
for (const name of readdirSync5(directory).sort()) {
|
|
17288
17288
|
if (name === VERSION_MARKER || name === DIGEST_MARKER) {
|
|
17289
17289
|
continue;
|
|
17290
17290
|
}
|
|
@@ -17386,7 +17386,7 @@ function removeAddons(projectPath) {
|
|
|
17386
17386
|
}
|
|
17387
17387
|
}
|
|
17388
17388
|
const addons = join10(projectPath, "addons");
|
|
17389
|
-
if (removed.length > 0 && existsSync10(addons) &&
|
|
17389
|
+
if (removed.length > 0 && existsSync10(addons) && readdirSync5(addons).length === 0) {
|
|
17390
17390
|
rmdirSync(addons);
|
|
17391
17391
|
removed.push(addons);
|
|
17392
17392
|
}
|
|
@@ -33897,7 +33897,7 @@ var init_stdio2 = __esm(() => {
|
|
|
33897
33897
|
});
|
|
33898
33898
|
|
|
33899
33899
|
// src/runtime-client.ts
|
|
33900
|
-
import { existsSync as existsSync12, readdirSync as
|
|
33900
|
+
import { existsSync as existsSync12, readdirSync as readdirSync7, readFileSync as readFileSync11, statSync as statSync6, unlinkSync } from "node:fs";
|
|
33901
33901
|
import { createConnection } from "node:net";
|
|
33902
33902
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
33903
33903
|
import { dirname as dirname8, join as join13, resolve as resolve4 } from "node:path";
|
|
@@ -34067,7 +34067,7 @@ function announcedIn(directory) {
|
|
|
34067
34067
|
const found = [];
|
|
34068
34068
|
const candidates = [];
|
|
34069
34069
|
const entries = [];
|
|
34070
|
-
for (const entry of
|
|
34070
|
+
for (const entry of readdirSync7(directory)) {
|
|
34071
34071
|
const report = ERROR_REPORT_PATTERN.exec(entry);
|
|
34072
34072
|
if (report) {
|
|
34073
34073
|
sweepErrorReport(join13(directory, entry), Number.parseInt(report[1] ?? "", 10));
|
|
@@ -40554,7 +40554,7 @@ var init_process_time = __esm(() => {
|
|
|
40554
40554
|
});
|
|
40555
40555
|
|
|
40556
40556
|
// src/project-scan.ts
|
|
40557
|
-
import { existsSync as existsSync17, readdirSync as
|
|
40557
|
+
import { existsSync as existsSync17, readdirSync as readdirSync8, readFileSync as readFileSync18 } from "node:fs";
|
|
40558
40558
|
import { join as join21 } from "node:path";
|
|
40559
40559
|
function skipped(name) {
|
|
40560
40560
|
return name.startsWith(".") || SKIPPED.has(name);
|
|
@@ -40568,7 +40568,7 @@ function projectStructure(projectPath) {
|
|
|
40568
40568
|
if (steppedOver(directory)) {
|
|
40569
40569
|
return;
|
|
40570
40570
|
}
|
|
40571
|
-
for (const entry of
|
|
40571
|
+
for (const entry of readdirSync8(directory, { withFileTypes: true })) {
|
|
40572
40572
|
if (skipped(entry.name)) {
|
|
40573
40573
|
continue;
|
|
40574
40574
|
}
|
|
@@ -40597,7 +40597,7 @@ function scriptsWithoutUid(projectPath) {
|
|
|
40597
40597
|
if (steppedOver(directory)) {
|
|
40598
40598
|
return;
|
|
40599
40599
|
}
|
|
40600
|
-
for (const entry of
|
|
40600
|
+
for (const entry of readdirSync8(directory, { withFileTypes: true })) {
|
|
40601
40601
|
if (skipped(entry.name)) {
|
|
40602
40602
|
continue;
|
|
40603
40603
|
}
|
|
@@ -40627,7 +40627,7 @@ function projectTexts(projectPath) {
|
|
|
40627
40627
|
if (steppedOver(directory)) {
|
|
40628
40628
|
return;
|
|
40629
40629
|
}
|
|
40630
|
-
for (const entry of
|
|
40630
|
+
for (const entry of readdirSync8(directory, { withFileTypes: true })) {
|
|
40631
40631
|
if (skipped(entry.name)) {
|
|
40632
40632
|
continue;
|
|
40633
40633
|
}
|
|
@@ -40654,7 +40654,7 @@ function ignoredTexts(projectPath) {
|
|
|
40654
40654
|
const found = [];
|
|
40655
40655
|
const visit = (directory, prefix, ignored) => {
|
|
40656
40656
|
const under = ignored || steppedOver(directory);
|
|
40657
|
-
for (const entry of
|
|
40657
|
+
for (const entry of readdirSync8(directory, { withFileTypes: true })) {
|
|
40658
40658
|
if (skipped(entry.name)) {
|
|
40659
40659
|
continue;
|
|
40660
40660
|
}
|
|
@@ -40691,7 +40691,7 @@ function searchProject(projectPath, options) {
|
|
|
40691
40691
|
if (steppedOver(directory)) {
|
|
40692
40692
|
return;
|
|
40693
40693
|
}
|
|
40694
|
-
for (const entry of
|
|
40694
|
+
for (const entry of readdirSync8(directory, { withFileTypes: true })) {
|
|
40695
40695
|
if (full()) {
|
|
40696
40696
|
return;
|
|
40697
40697
|
}
|
|
@@ -41550,7 +41550,7 @@ var init_rename = __esm(() => {
|
|
|
41550
41550
|
});
|
|
41551
41551
|
|
|
41552
41552
|
// src/rename-symbol.ts
|
|
41553
|
-
import { existsSync as existsSync19, mkdirSync as mkdirSync12, readdirSync as
|
|
41553
|
+
import { existsSync as existsSync19, mkdirSync as mkdirSync12, readdirSync as readdirSync9, realpathSync as realpathSync5, rmSync as rmSync10, writeFileSync as writeFileSync14 } from "node:fs";
|
|
41554
41554
|
import { basename as basename5, dirname as dirname16, join as join22, relative as relative4, resolve as resolve6 } from "node:path";
|
|
41555
41555
|
function resourcePathOf(projectPath, absolute) {
|
|
41556
41556
|
const inside = relative4(onDiskSpelling(projectPath), onDiskSpelling(absolute));
|
|
@@ -41998,7 +41998,7 @@ function writeLeftAloneDetail(projectPath, symbol, detail) {
|
|
|
41998
41998
|
const path = join22(directory, `${String(Date.now())}-${String(detailWrites).padStart(6, "0")}-${String(process.pid)}-${symbol}.json`);
|
|
41999
41999
|
writeFileSync14(path, `${JSON.stringify(detail, null, 2)}
|
|
42000
42000
|
`, "utf8");
|
|
42001
|
-
const kept =
|
|
42001
|
+
const kept = readdirSync9(directory).filter((name) => name.endsWith(".json")).sort().reverse();
|
|
42002
42002
|
for (const old of kept.slice(10)) {
|
|
42003
42003
|
rmSync10(join22(directory, old), { force: true });
|
|
42004
42004
|
}
|
|
@@ -42087,7 +42087,7 @@ import { createHash as createHash4 } from "node:crypto";
|
|
|
42087
42087
|
import {
|
|
42088
42088
|
mkdirSync as mkdirSync14,
|
|
42089
42089
|
openSync as openSync2,
|
|
42090
|
-
readdirSync as
|
|
42090
|
+
readdirSync as readdirSync10,
|
|
42091
42091
|
readFileSync as readFileSync21,
|
|
42092
42092
|
realpathSync as realpathSync6,
|
|
42093
42093
|
rmSync as rmSync12,
|
|
@@ -42213,7 +42213,7 @@ function everyRunRecord() {
|
|
|
42213
42213
|
for (const directory of runtimeDirectories()) {
|
|
42214
42214
|
let entries;
|
|
42215
42215
|
try {
|
|
42216
|
-
entries =
|
|
42216
|
+
entries = readdirSync10(join24(directory, "runs"));
|
|
42217
42217
|
} catch {
|
|
42218
42218
|
continue;
|
|
42219
42219
|
}
|
|
@@ -42454,7 +42454,7 @@ function sweepTranscripts(keepMs = 24 * 60 * 60 * 1000, now = Date.now()) {
|
|
|
42454
42454
|
]));
|
|
42455
42455
|
let entries;
|
|
42456
42456
|
try {
|
|
42457
|
-
entries =
|
|
42457
|
+
entries = readdirSync10(directory);
|
|
42458
42458
|
} catch {
|
|
42459
42459
|
return;
|
|
42460
42460
|
}
|
|
@@ -42721,7 +42721,7 @@ function callersOf(totals, name, scripts) {
|
|
|
42721
42721
|
sites.sort((a, b) => (b.withinTotalMs ?? -1) - (a.withinTotalMs ?? -1) || a.script.localeCompare(b.script) || a.line - b.line);
|
|
42722
42722
|
return { target, sites: sites.slice(0, CALL_SITES), omitted: Math.max(0, sites.length - CALL_SITES) };
|
|
42723
42723
|
}
|
|
42724
|
-
var QUEUE_SETTING = "limits/debugger/max_queued_messages", QUEUE_DEFAULT = 2048, QUEUE_HEADROOM = 512, UNNAMED_PREFIX = "#", 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;
|
|
42724
|
+
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;
|
|
42725
42725
|
var init_script_profile = __esm(() => {
|
|
42726
42726
|
init_gdscript_source();
|
|
42727
42727
|
init_godot_variant();
|
|
@@ -42740,7 +42740,7 @@ import {
|
|
|
42740
42740
|
fstatSync,
|
|
42741
42741
|
mkdirSync as mkdirSync15,
|
|
42742
42742
|
openSync as openSync3,
|
|
42743
|
-
readdirSync as
|
|
42743
|
+
readdirSync as readdirSync11,
|
|
42744
42744
|
readFileSync as readFileSync23,
|
|
42745
42745
|
readSync as readSync2,
|
|
42746
42746
|
rmdirSync as rmdirSync2,
|
|
@@ -43624,7 +43624,7 @@ function elsewhereIn(projectPath, asked) {
|
|
|
43624
43624
|
const root = (wanted.split("/")[0] ?? "").toLowerCase();
|
|
43625
43625
|
let near = [];
|
|
43626
43626
|
try {
|
|
43627
|
-
near =
|
|
43627
|
+
near = readdirSync11(projectPath, { withFileTypes: true }).filter((entry) => entry.isDirectory() && entry.name.toLowerCase() !== root).map((entry) => entry.name).filter((name) => name.toLowerCase().startsWith("test") || root.startsWith(name.toLowerCase()));
|
|
43628
43628
|
} catch {
|
|
43629
43629
|
return "";
|
|
43630
43630
|
}
|
|
@@ -43718,7 +43718,7 @@ function scriptsUnder(directory, prefix) {
|
|
|
43718
43718
|
return [];
|
|
43719
43719
|
}
|
|
43720
43720
|
const found = [];
|
|
43721
|
-
for (const entry of
|
|
43721
|
+
for (const entry of readdirSync11(directory, { withFileTypes: true }).sort((a, b) => a.name < b.name ? -1 : 1)) {
|
|
43722
43722
|
if (entry.name.startsWith(".")) {
|
|
43723
43723
|
continue;
|
|
43724
43724
|
}
|
|
@@ -45393,7 +45393,7 @@ class GodotServer {
|
|
|
45393
45393
|
let report = null;
|
|
45394
45394
|
let reportProblem = null;
|
|
45395
45395
|
try {
|
|
45396
|
-
const written = existsSync20(reportsDir) ?
|
|
45396
|
+
const written = existsSync20(reportsDir) ? readdirSync11(reportsDir).filter((name) => name.startsWith("report_")).sort((a, b) => Number(a.slice("report_".length)) - Number(b.slice("report_".length))) : [];
|
|
45397
45397
|
const newest = written.at(-1);
|
|
45398
45398
|
if (newest !== undefined) {
|
|
45399
45399
|
report = parseJUnit(readFileSync23(join26(reportsDir, newest, "results.xml"), "utf8"));
|
|
@@ -46929,10 +46929,11 @@ class GodotServer {
|
|
|
46929
46929
|
const startedAt = Date.now();
|
|
46930
46930
|
const transcript = openTranscript(startedAt);
|
|
46931
46931
|
closeSync2(transcript.fd);
|
|
46932
|
+
const gameEnv = profiled ? { ...env ?? process.env, [PROFILE_FROM_START]: "1" } : env;
|
|
46932
46933
|
const launched = await launchOutsideTheTree({
|
|
46933
46934
|
command: godotPath,
|
|
46934
46935
|
args: cmdArgs,
|
|
46935
|
-
...
|
|
46936
|
+
...gameEnv === undefined ? {} : { env: gameEnv },
|
|
46936
46937
|
run: {
|
|
46937
46938
|
transcript: transcript.path,
|
|
46938
46939
|
startedAt,
|
|
@@ -48822,7 +48823,7 @@ import { existsSync as existsSync21 } from "node:fs";
|
|
|
48822
48823
|
import { join as join27, resolve as resolve8 } from "node:path";
|
|
48823
48824
|
|
|
48824
48825
|
// src/harnesses.ts
|
|
48825
|
-
import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync2, rmSync as rmSync2, statSync as statSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
48826
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync2, readdirSync as readdirSync2, readFileSync as readFileSync2, rmSync as rmSync2, statSync as statSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
48826
48827
|
import { homedir as homedir2 } from "node:os";
|
|
48827
48828
|
import { basename as basename2, dirname as dirname3, join as join4, relative as relative2, resolve as resolve2 } from "node:path";
|
|
48828
48829
|
import process4 from "node:process";
|
|
@@ -49569,6 +49570,30 @@ function mainCheckoutOf(projectPath) {
|
|
|
49569
49570
|
const main = join4(dirname3(common), relative2(top, resolve2(projectPath)));
|
|
49570
49571
|
return isSameDirectory(main, projectPath) || !existsSync4(join4(main, "project.godot")) ? null : main;
|
|
49571
49572
|
}
|
|
49573
|
+
function worktreeCheckoutsOf(projectPath) {
|
|
49574
|
+
let top = resolve2(projectPath);
|
|
49575
|
+
while (!existsSync4(join4(top, ".git"))) {
|
|
49576
|
+
const up = dirname3(top);
|
|
49577
|
+
if (up === top) {
|
|
49578
|
+
return [];
|
|
49579
|
+
}
|
|
49580
|
+
top = up;
|
|
49581
|
+
}
|
|
49582
|
+
const listed = join4(top, ".git", "worktrees");
|
|
49583
|
+
if (!statSync2(join4(top, ".git")).isDirectory() || !existsSync4(listed)) {
|
|
49584
|
+
return [];
|
|
49585
|
+
}
|
|
49586
|
+
const below = relative2(top, resolve2(projectPath));
|
|
49587
|
+
return readdirSync2(listed).flatMap((name) => {
|
|
49588
|
+
const pointer = join4(listed, name, "gitdir");
|
|
49589
|
+
if (!existsSync4(pointer)) {
|
|
49590
|
+
return [];
|
|
49591
|
+
}
|
|
49592
|
+
const checkout = dirname3(resolve2(listed, name, readFileSync2(pointer, "utf8").trim()));
|
|
49593
|
+
const project = join4(checkout, below);
|
|
49594
|
+
return existsSync4(join4(project, "project.godot")) ? [project] : [];
|
|
49595
|
+
});
|
|
49596
|
+
}
|
|
49572
49597
|
function entryEnvironment(harness, projectPath) {
|
|
49573
49598
|
if (harness.toml || harness.yaml || harness.snippet) {
|
|
49574
49599
|
return;
|
|
@@ -49760,7 +49785,7 @@ init_setup();
|
|
|
49760
49785
|
init_setup();
|
|
49761
49786
|
init_tool_definitions();
|
|
49762
49787
|
init_tool_reference();
|
|
49763
|
-
import { existsSync as existsSync11, mkdirSync as mkdirSync5, readdirSync as
|
|
49788
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync5, readdirSync as readdirSync6, rmSync as rmSync6, writeFileSync as writeFileSync7 } from "node:fs";
|
|
49764
49789
|
import { basename as basename4, dirname as dirname6, join as join11 } from "node:path";
|
|
49765
49790
|
var SKILL_NAME = "gdharness";
|
|
49766
49791
|
var SHARED_SKILLS = join11(".agents", "skills");
|
|
@@ -50007,7 +50032,7 @@ function pruneEmpty(directory, projectPath) {
|
|
|
50007
50032
|
const shared = join11(projectPath, ".agents");
|
|
50008
50033
|
let path = directory;
|
|
50009
50034
|
while (path !== projectPath && (basename4(path) === "skills" || path === shared)) {
|
|
50010
|
-
if (
|
|
50035
|
+
if (readdirSync6(path).length > 0) {
|
|
50011
50036
|
return;
|
|
50012
50037
|
}
|
|
50013
50038
|
rmSync6(path, { recursive: true, force: true });
|
|
@@ -50360,6 +50385,17 @@ On ${version}. What is left for you:`);
|
|
|
50360
50385
|
` + " editor_launch restart tool, which closes and reopens the window.");
|
|
50361
50386
|
const serving = await serverServing(projectPath);
|
|
50362
50387
|
console.log(harnessNote({ moved, written, byHand }, [launch.command, ...launch.args].join(" "), serving === null ? null : { server: serving, isThisVersion: serving.version === version }));
|
|
50388
|
+
const trailing = worktreeCheckoutsOf(projectPath).flatMap((path) => {
|
|
50389
|
+
const addon = inspectProject(path).addons.find((one) => one.installed);
|
|
50390
|
+
return addon === undefined || addon.version === version ? [] : [{ path, version: addon.version }];
|
|
50391
|
+
});
|
|
50392
|
+
if (trailing.length > 0) {
|
|
50393
|
+
console.log(`
|
|
50394
|
+
Git worktrees of this project keep untracked addons of their own, which this upgrade does not reach:`);
|
|
50395
|
+
for (const worktree of trailing) {
|
|
50396
|
+
console.log(` ${worktree.path}: ${worktree.version ?? "an unmarked copy"}. Run gdharness upgrade ${worktree.path}`);
|
|
50397
|
+
}
|
|
50398
|
+
}
|
|
50363
50399
|
}
|
|
50364
50400
|
function doctorReport(projectPath) {
|
|
50365
50401
|
const report = inspectProject(projectPath);
|
|
@@ -22,6 +22,9 @@ const DEBUGGER_CLASS: String = "ScriptEditorDebugger"
|
|
|
22
22
|
const PROFILE_CAPTURE: String = "gdharness"
|
|
23
23
|
const PROFILE_SENT: String = "gdharness:profile_sent"
|
|
24
24
|
const PROFILE_RECEIVED: String = "gdharness:profile_received"
|
|
25
|
+
## Set in the environment a played game inherits, when the play is to be profiled from its start.
|
|
26
|
+
## Kept in step with the runtime addon's runtime_autoload.gd.
|
|
27
|
+
const PROFILE_FROM_START: String = "GDHARNESS_PROFILE_FROM_START"
|
|
25
28
|
|
|
26
29
|
|
|
27
30
|
## The editor's debugger sessions, numbered as the editor numbers its debugger tabs.
|
|
@@ -106,6 +109,10 @@ func profile_start(args: Dictionary) -> Dictionary:
|
|
|
106
109
|
_frame_functions = asked if asked > 0 else FRAME_FUNCTIONS
|
|
107
110
|
if Read.as_bool(args.get("arm", false)):
|
|
108
111
|
_armed = true
|
|
112
|
+
# The game the editor starts next inherits the editor's environment, and this tells its
|
|
113
|
+
# runtime addon to hold its start until the profiler is on, which the session's start
|
|
114
|
+
# switches on with a message the game reads only on a later poll.
|
|
115
|
+
OS.set_environment(PROFILE_FROM_START, "1")
|
|
109
116
|
return {"ok": true, "started": false, "armed": true, "session": -1}
|
|
110
117
|
var active: int = _active_session()
|
|
111
118
|
if active < 0:
|
|
@@ -137,6 +144,7 @@ func profile_read(_args: Dictionary) -> Dictionary:
|
|
|
137
144
|
|
|
138
145
|
func _reset() -> void:
|
|
139
146
|
_armed = false
|
|
147
|
+
OS.unset_environment(PROFILE_FROM_START)
|
|
140
148
|
_session = -1
|
|
141
149
|
_frames = 0
|
|
142
150
|
_complete = false
|
|
@@ -172,6 +180,8 @@ func _on_session_started(session_id: int) -> void:
|
|
|
172
180
|
_reset()
|
|
173
181
|
return
|
|
174
182
|
_armed = false
|
|
183
|
+
# The game has started with it by now, and a later play must not.
|
|
184
|
+
OS.unset_environment(PROFILE_FROM_START)
|
|
175
185
|
if _hook() > 0:
|
|
176
186
|
_switch_on(session_id)
|
|
177
187
|
|
|
@@ -47,6 +47,13 @@ const PROFILER_PROBE_CALLS: int = 10000
|
|
|
47
47
|
## on every 2048th call and only counts the others.
|
|
48
48
|
const POLLS_PER_READ: int = 2048
|
|
49
49
|
|
|
50
|
+
## Set in a game's environment by whatever starts it to be profiled from its start: the server for
|
|
51
|
+
## a run it spawns, the editor for a play. Kept in step with `PROFILE_FROM_START` in the server.
|
|
52
|
+
const PROFILE_FROM_START: String = "GDHARNESS_PROFILE_FROM_START"
|
|
53
|
+
|
|
54
|
+
## How long such a game waits for its debugger to switch the profiler on before carrying on.
|
|
55
|
+
const PROFILER_WAIT_MS: int = 5000
|
|
56
|
+
|
|
50
57
|
## How the engine is told to run a script instead of the game: `godot -s thing.gd`.
|
|
51
58
|
const SCRIPT_FLAGS: PackedStringArray = ["-s", "--script"]
|
|
52
59
|
|
|
@@ -134,6 +141,37 @@ func _init() -> void:
|
|
|
134
141
|
"wait_signal": _waits.wait_signal,
|
|
135
142
|
"wait_until": _waits.wait_until,
|
|
136
143
|
}
|
|
144
|
+
_wait_for_the_profiler()
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
## Holds the game's start until the profiler is on, when whatever started the game asked for a
|
|
148
|
+
## profile from its start. The debugger switches the profiler on with a message the game reads only
|
|
149
|
+
## on a poll, which comes every [constant POLLS_PER_READ] lines of script, so the main scene's _ready
|
|
150
|
+
## could run first: a game played on a macOS runner counted 25 calls of a function called 26 times,
|
|
151
|
+
## the first of them from _ready. Run at the autoload's construction, before the main scene loads.
|
|
152
|
+
func _wait_for_the_profiler() -> void:
|
|
153
|
+
if OS.get_environment(PROFILE_FROM_START).is_empty():
|
|
154
|
+
return
|
|
155
|
+
# Read once: a process this game starts inherits its environment and must not wait as well.
|
|
156
|
+
OS.unset_environment(PROFILE_FROM_START)
|
|
157
|
+
if not EngineDebugger.is_active():
|
|
158
|
+
return
|
|
159
|
+
var deadline: int = Time.get_ticks_msec() + PROFILER_WAIT_MS
|
|
160
|
+
while not EngineDebugger.is_profiling("servers"):
|
|
161
|
+
if Time.get_ticks_msec() >= deadline:
|
|
162
|
+
push_warning(
|
|
163
|
+
(
|
|
164
|
+
(
|
|
165
|
+
"gdharness: this game was started to be profiled from its start, and the profiler"
|
|
166
|
+
+ " was not on %d ms later, so what ran before it is not counted"
|
|
167
|
+
)
|
|
168
|
+
% PROFILER_WAIT_MS
|
|
169
|
+
)
|
|
170
|
+
)
|
|
171
|
+
return
|
|
172
|
+
for _poll: int in POLLS_PER_READ:
|
|
173
|
+
EngineDebugger.line_poll()
|
|
174
|
+
OS.delay_msec(1)
|
|
137
175
|
|
|
138
176
|
|
|
139
177
|
func _ready() -> void:
|
package/build/index.js
CHANGED
|
@@ -33393,6 +33393,7 @@ function queuedMessagesOf(projectGodot) {
|
|
|
33393
33393
|
return typeof set === "number" && Number.isInteger(set) && set > 0 ? set : QUEUE_DEFAULT;
|
|
33394
33394
|
}
|
|
33395
33395
|
var UNNAMED_PREFIX = "#";
|
|
33396
|
+
var PROFILE_FROM_START = "GDHARNESS_PROFILE_FROM_START";
|
|
33396
33397
|
function signatureParts(signature) {
|
|
33397
33398
|
const parts = signature.split("::");
|
|
33398
33399
|
if (parts.length < 3) {
|
|
@@ -39426,10 +39427,11 @@ class GodotServer {
|
|
|
39426
39427
|
const startedAt = Date.now();
|
|
39427
39428
|
const transcript = openTranscript(startedAt);
|
|
39428
39429
|
closeSync2(transcript.fd);
|
|
39430
|
+
const gameEnv = profiled ? { ...env ?? process.env, [PROFILE_FROM_START]: "1" } : env;
|
|
39429
39431
|
const launched = await launchOutsideTheTree({
|
|
39430
39432
|
command: godotPath,
|
|
39431
39433
|
args: cmdArgs,
|
|
39432
|
-
...
|
|
39434
|
+
...gameEnv === undefined ? {} : { env: gameEnv },
|
|
39433
39435
|
run: {
|
|
39434
39436
|
transcript: transcript.path,
|
|
39435
39437
|
startedAt,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gdharness",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.7",
|
|
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",
|