gdharness 1.2.19 → 1.2.20
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 +46 -17
- package/build/index.js +47 -19
- package/build/keeper.js +68 -32
- package/package.json +1 -1
package/build/cli.js
CHANGED
|
@@ -11924,7 +11924,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
11924
11924
|
requires: []
|
|
11925
11925
|
}
|
|
11926
11926
|
},
|
|
11927
|
-
answer: "stop: endedPid is, for a run the editor plays, the number its game announced under, read at the stop rather than remembered, and the operating system hands a freed number to the next process, so it can equal the game before it. stopped is false, with the number under notSignalled, when that number no longer answers as the process the run was started as and nothing was signalled, the note naming which property it failed on, and false with the number under stillGoing when what was told to end was still running ten seconds later. endedThrough says how the stop ended a run that was going: keeper (the process that started the game ended it through the handle it holds), signal (its pid was signalled once shown to still be the run), or editor (the editor playing it was asked). A start that ends the run before it says how under endedPreviousRunThrough, with the same values, and one that refused to signal that run names the property it failed on under previousRunLeftBecause. check: hung is a run killed at timeoutMs, and errors and warnings count what it printed, which comes back under entries with repeats folded together. profile: per function its script, the function (Class.name when the script declares a class_name), the line its body's first statement is on, for a lambda the named function it is written in (within), calls, and self and total milliseconds, with functionsCounted, scriptMs (the self time of them all) and coveredMs. complete is true when the engine's own totals arrived, which it sends only when the profiler is switched off: the runtime addon does that as a game quits, so a scene that does its work in _ready and quits is covered whole; otherwise the answer is the frames summed so far and the note says what that misses. Self time counts a call into the engine's own methods as the caller's, and a call through super too, while the overridden function's row counts it again: such a row carries selfIncludesSuper, so self times along an override chain overlap. The profiler's own cost of timing a call lands in the self time of the function called; with the runtime addon it is measured each run on an empty function, as perCallOverheadUs, and a row with at least half its selfMs in it carries overheadMs, since a small function called often costs that much less without the profiler. A function the profiler started inside is left out, since its time would be counted from the engine's start. The milliseconds are time passed while the function ran, not processor time given to it, so they grow when other processes compete for the processor: compare two profiles taken under the same load. They are also summed over every thread: a function run by WorkerThreadPool or a Thread on several threads at once counts each thread's time, the engine does not say which thread a call ran on, and a function waiting for them has the wait in its own self time, so selfMs is not time taken from frames, and scriptMs can exceed coveredMs, which the note then says. A property's get and set blocks are named Class.@name_getter and Class.@name_setter, as the engine names them, and their rows carry property, the property's name. A game the editor plays that was not started with profile: true says, in the answer, that it was profiled from the moment it was asked.",
|
|
11927
|
+
answer: "stop: endedPid is, for a run the editor plays, the number its game announced under, read at the stop rather than remembered, and the operating system hands a freed number to the next process, so it can equal the game before it. stopped is false, with the number under notSignalled, when that number no longer answers as the process the run was started as and nothing was signalled, the note naming which property it failed on, and false with the number under stillGoing when what was told to end was still running ten seconds later. endedThrough says how the stop ended a run that was going: keeper (the process that started the game ended it through the handle it holds), signal (its pid was signalled once shown to still be the run), or editor (the editor playing it was asked). A start that ends the run before it says how under endedPreviousRunThrough, with the same values, and one that refused to signal that run names the property it failed on under previousRunLeftBecause. check: hung is a run killed at timeoutMs, and errors and warnings count what it printed, which comes back under entries with repeats folded together. arguments is the engine command line the check ran, as a start's is. profile: per function its script, the function (Class.name when the script declares a class_name), the line its body's first statement is on, for a lambda the named function it is written in (within), calls, and self and total milliseconds, with functionsCounted, scriptMs (the self time of them all) and coveredMs. complete is true when the engine's own totals arrived, which it sends only when the profiler is switched off: the runtime addon does that as a game quits, so a scene that does its work in _ready and quits is covered whole; otherwise the answer is the frames summed so far and the note says what that misses. Self time counts a call into the engine's own methods as the caller's, and a call through super too, while the overridden function's row counts it again: such a row carries selfIncludesSuper, so self times along an override chain overlap. The profiler's own cost of timing a call lands in the self time of the function called; with the runtime addon it is measured each run on an empty function, as perCallOverheadUs, and a row with at least half its selfMs in it carries overheadMs, since a small function called often costs that much less without the profiler. A function the profiler started inside is left out, since its time would be counted from the engine's start. The milliseconds are time passed while the function ran, not processor time given to it, so they grow when other processes compete for the processor: compare two profiles taken under the same load. They are also summed over every thread: a function run by WorkerThreadPool or a Thread on several threads at once counts each thread's time, the engine does not say which thread a call ran on, and a function waiting for them has the wait in its own self time, so selfMs is not time taken from frames, and scriptMs can exceed coveredMs, which the note then says. A property's get and set blocks are named Class.@name_getter and Class.@name_setter, as the engine names them, and their rows carry property, the property's name. A game the editor plays that was not started with profile: true says, in the answer, that it was profiled from the moment it was asked.",
|
|
11928
11928
|
defaultOperation: "start"
|
|
11929
11929
|
},
|
|
11930
11930
|
{
|
|
@@ -42480,19 +42480,41 @@ var init_restart_note = __esm(() => {
|
|
|
42480
42480
|
init_errors();
|
|
42481
42481
|
});
|
|
42482
42482
|
|
|
42483
|
+
// src/whole-file.ts
|
|
42484
|
+
import { renameSync as renameSync2, rmSync as rmSync12, writeFileSync as writeFileSync16 } from "node:fs";
|
|
42485
|
+
function writeWhole(path, text) {
|
|
42486
|
+
const partial = `${path}.${process.pid}.partial`;
|
|
42487
|
+
try {
|
|
42488
|
+
writeFileSync16(partial, text, "utf8");
|
|
42489
|
+
} catch {
|
|
42490
|
+
return false;
|
|
42491
|
+
}
|
|
42492
|
+
const until = Date.now() + HELD_FILE_MS;
|
|
42493
|
+
for (;; ) {
|
|
42494
|
+
try {
|
|
42495
|
+
renameSync2(partial, path);
|
|
42496
|
+
return true;
|
|
42497
|
+
} catch (error) {
|
|
42498
|
+
const code = error.code ?? "";
|
|
42499
|
+
if (!HELD_BY_A_READER.has(code) || Date.now() >= until) {
|
|
42500
|
+
try {
|
|
42501
|
+
rmSync12(partial, { force: true });
|
|
42502
|
+
} catch {}
|
|
42503
|
+
return false;
|
|
42504
|
+
}
|
|
42505
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, HELD_FILE_RETRY_MS);
|
|
42506
|
+
}
|
|
42507
|
+
}
|
|
42508
|
+
}
|
|
42509
|
+
var HELD_FILE_MS = 1000, HELD_FILE_RETRY_MS = 10, HELD_BY_A_READER;
|
|
42510
|
+
var init_whole_file = __esm(() => {
|
|
42511
|
+
HELD_BY_A_READER = new Set(["EPERM", "EACCES", "EBUSY"]);
|
|
42512
|
+
});
|
|
42513
|
+
|
|
42483
42514
|
// src/run-record.ts
|
|
42484
42515
|
import { execFile as execFile4 } from "node:child_process";
|
|
42485
42516
|
import { createHash as createHash4 } from "node:crypto";
|
|
42486
|
-
import {
|
|
42487
|
-
mkdirSync as mkdirSync14,
|
|
42488
|
-
openSync as openSync2,
|
|
42489
|
-
readdirSync as readdirSync10,
|
|
42490
|
-
readFileSync as readFileSync21,
|
|
42491
|
-
realpathSync as realpathSync6,
|
|
42492
|
-
rmSync as rmSync12,
|
|
42493
|
-
statSync as statSync8,
|
|
42494
|
-
writeFileSync as writeFileSync16
|
|
42495
|
-
} from "node:fs";
|
|
42517
|
+
import { mkdirSync as mkdirSync14, openSync as openSync2, readdirSync as readdirSync10, readFileSync as readFileSync21, realpathSync as realpathSync6, rmSync as rmSync13, statSync as statSync8 } from "node:fs";
|
|
42496
42518
|
import { basename as basename6, dirname as dirname18, join as join24, resolve as resolve7 } from "node:path";
|
|
42497
42519
|
import { promisify as promisify4 } from "node:util";
|
|
42498
42520
|
function runsDirectory() {
|
|
@@ -42528,7 +42550,7 @@ function latestRunPath(servedBy, directory = runtimeDirectory()) {
|
|
|
42528
42550
|
function writeLatestRun(servedBy, latest) {
|
|
42529
42551
|
try {
|
|
42530
42552
|
mkdirSync14(runsDirectory(), { recursive: true });
|
|
42531
|
-
|
|
42553
|
+
writeWhole(latestRunPath(servedBy), JSON.stringify(latest, null, 2));
|
|
42532
42554
|
} catch {}
|
|
42533
42555
|
}
|
|
42534
42556
|
function readLatestRun(servedBy, directory = runtimeDirectory()) {
|
|
@@ -42562,7 +42584,10 @@ function editorNotePath(projectPath, directory = runtimeDirectory()) {
|
|
|
42562
42584
|
}
|
|
42563
42585
|
function writeEditorRunNote(note) {
|
|
42564
42586
|
mkdirSync14(runsDirectory(), { recursive: true });
|
|
42565
|
-
|
|
42587
|
+
const path = editorNotePath(note.projectPath);
|
|
42588
|
+
if (!writeWhole(path, JSON.stringify(note, null, 2))) {
|
|
42589
|
+
throw new Error(`the editor run note could not be written at ${path}`);
|
|
42590
|
+
}
|
|
42566
42591
|
}
|
|
42567
42592
|
function readEditorRunNote(projectPath) {
|
|
42568
42593
|
for (const directory of runtimeDirectories()) {
|
|
@@ -42833,7 +42858,7 @@ function clearRunRecord(projectPath) {
|
|
|
42833
42858
|
const ours = sharedRecord !== null && sharedRecord.projectPath !== "" && isSameDirectory(sharedRecord.projectPath, projectPath);
|
|
42834
42859
|
for (const path of [runRecordPath(projectPath, directory), ...ours ? [shared] : []]) {
|
|
42835
42860
|
try {
|
|
42836
|
-
|
|
42861
|
+
rmSync13(path, { force: true });
|
|
42837
42862
|
} catch {}
|
|
42838
42863
|
}
|
|
42839
42864
|
}
|
|
@@ -42858,7 +42883,8 @@ function sweepTranscripts(keepMs = 24 * 60 * 60 * 1000, now = Date.now()) {
|
|
|
42858
42883
|
return;
|
|
42859
42884
|
}
|
|
42860
42885
|
for (const entry of entries) {
|
|
42861
|
-
|
|
42886
|
+
const transcript = entry.startsWith("run-") && (entry.endsWith(".log") || entry.endsWith(".profile.json"));
|
|
42887
|
+
if (!transcript && !entry.endsWith(".partial")) {
|
|
42862
42888
|
continue;
|
|
42863
42889
|
}
|
|
42864
42890
|
const path = join24(directory, entry);
|
|
@@ -42866,7 +42892,7 @@ function sweepTranscripts(keepMs = 24 * 60 * 60 * 1000, now = Date.now()) {
|
|
|
42866
42892
|
continue;
|
|
42867
42893
|
}
|
|
42868
42894
|
try {
|
|
42869
|
-
|
|
42895
|
+
rmSync13(path);
|
|
42870
42896
|
} catch {}
|
|
42871
42897
|
}
|
|
42872
42898
|
}
|
|
@@ -42876,6 +42902,7 @@ var init_run_record = __esm(() => {
|
|
|
42876
42902
|
init_paths();
|
|
42877
42903
|
init_process_children();
|
|
42878
42904
|
init_runtime_client();
|
|
42905
|
+
init_whole_file();
|
|
42879
42906
|
init_windows_ask();
|
|
42880
42907
|
execFileAsync = promisify4(execFile4);
|
|
42881
42908
|
});
|
|
@@ -42986,7 +43013,7 @@ var init_godot_variant = __esm(() => {
|
|
|
42986
43013
|
});
|
|
42987
43014
|
|
|
42988
43015
|
// src/script-profile.ts
|
|
42989
|
-
import { readFileSync as readFileSync22
|
|
43016
|
+
import { readFileSync as readFileSync22 } from "node:fs";
|
|
42990
43017
|
function readProfileFile(path) {
|
|
42991
43018
|
try {
|
|
42992
43019
|
const parsed = JSON.parse(readFileSync22(path, "utf8"));
|
|
@@ -43140,6 +43167,7 @@ var init_script_profile = __esm(() => {
|
|
|
43140
43167
|
init_gdscript_source();
|
|
43141
43168
|
init_godot_variant();
|
|
43142
43169
|
init_resources();
|
|
43170
|
+
init_whole_file();
|
|
43143
43171
|
});
|
|
43144
43172
|
|
|
43145
43173
|
// src/server-instructions.ts
|
|
@@ -47964,6 +47992,7 @@ class GodotServer {
|
|
|
47964
47992
|
frames,
|
|
47965
47993
|
errors,
|
|
47966
47994
|
warnings,
|
|
47995
|
+
arguments: cmdArgs,
|
|
47967
47996
|
argsNote: engineFlagsNote(userArgs),
|
|
47968
47997
|
...scanWaitAnswer(scanned),
|
|
47969
47998
|
savesNote: asked.savesIn !== undefined && savesStayPut() ? SAVES_NOT_MOVED_NOTE : undefined,
|
package/build/index.js
CHANGED
|
@@ -33221,18 +33221,41 @@ function restartSettled(projectPath) {
|
|
|
33221
33221
|
// src/run-record.ts
|
|
33222
33222
|
import { execFile as execFile4 } from "node:child_process";
|
|
33223
33223
|
import { createHash as createHash3 } from "node:crypto";
|
|
33224
|
-
import {
|
|
33225
|
-
mkdirSync as mkdirSync11,
|
|
33226
|
-
openSync as openSync2,
|
|
33227
|
-
readdirSync as readdirSync7,
|
|
33228
|
-
readFileSync as readFileSync18,
|
|
33229
|
-
realpathSync as realpathSync6,
|
|
33230
|
-
rmSync as rmSync9,
|
|
33231
|
-
statSync as statSync6,
|
|
33232
|
-
writeFileSync as writeFileSync13
|
|
33233
|
-
} from "node:fs";
|
|
33224
|
+
import { mkdirSync as mkdirSync11, openSync as openSync2, readdirSync as readdirSync7, readFileSync as readFileSync18, realpathSync as realpathSync6, rmSync as rmSync10, statSync as statSync6 } from "node:fs";
|
|
33234
33225
|
import { basename as basename4, dirname as dirname13, join as join19, resolve as resolve6 } from "node:path";
|
|
33235
33226
|
import { promisify as promisify4 } from "node:util";
|
|
33227
|
+
|
|
33228
|
+
// src/whole-file.ts
|
|
33229
|
+
import { renameSync as renameSync2, rmSync as rmSync9, writeFileSync as writeFileSync13 } from "node:fs";
|
|
33230
|
+
var HELD_FILE_MS = 1000;
|
|
33231
|
+
var HELD_FILE_RETRY_MS = 10;
|
|
33232
|
+
var HELD_BY_A_READER = new Set(["EPERM", "EACCES", "EBUSY"]);
|
|
33233
|
+
function writeWhole(path, text) {
|
|
33234
|
+
const partial = `${path}.${process.pid}.partial`;
|
|
33235
|
+
try {
|
|
33236
|
+
writeFileSync13(partial, text, "utf8");
|
|
33237
|
+
} catch {
|
|
33238
|
+
return false;
|
|
33239
|
+
}
|
|
33240
|
+
const until = Date.now() + HELD_FILE_MS;
|
|
33241
|
+
for (;; ) {
|
|
33242
|
+
try {
|
|
33243
|
+
renameSync2(partial, path);
|
|
33244
|
+
return true;
|
|
33245
|
+
} catch (error) {
|
|
33246
|
+
const code = error.code ?? "";
|
|
33247
|
+
if (!HELD_BY_A_READER.has(code) || Date.now() >= until) {
|
|
33248
|
+
try {
|
|
33249
|
+
rmSync9(partial, { force: true });
|
|
33250
|
+
} catch {}
|
|
33251
|
+
return false;
|
|
33252
|
+
}
|
|
33253
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, HELD_FILE_RETRY_MS);
|
|
33254
|
+
}
|
|
33255
|
+
}
|
|
33256
|
+
}
|
|
33257
|
+
|
|
33258
|
+
// src/run-record.ts
|
|
33236
33259
|
var execFileAsync = promisify4(execFile4);
|
|
33237
33260
|
function runsDirectory() {
|
|
33238
33261
|
return join19(runtimeDirectory(), "runs");
|
|
@@ -33267,7 +33290,7 @@ function latestRunPath(servedBy, directory = runtimeDirectory()) {
|
|
|
33267
33290
|
function writeLatestRun(servedBy, latest) {
|
|
33268
33291
|
try {
|
|
33269
33292
|
mkdirSync11(runsDirectory(), { recursive: true });
|
|
33270
|
-
|
|
33293
|
+
writeWhole(latestRunPath(servedBy), JSON.stringify(latest, null, 2));
|
|
33271
33294
|
} catch {}
|
|
33272
33295
|
}
|
|
33273
33296
|
function readLatestRun(servedBy, directory = runtimeDirectory()) {
|
|
@@ -33302,7 +33325,10 @@ function editorNotePath(projectPath, directory = runtimeDirectory()) {
|
|
|
33302
33325
|
}
|
|
33303
33326
|
function writeEditorRunNote(note) {
|
|
33304
33327
|
mkdirSync11(runsDirectory(), { recursive: true });
|
|
33305
|
-
|
|
33328
|
+
const path = editorNotePath(note.projectPath);
|
|
33329
|
+
if (!writeWhole(path, JSON.stringify(note, null, 2))) {
|
|
33330
|
+
throw new Error(`the editor run note could not be written at ${path}`);
|
|
33331
|
+
}
|
|
33306
33332
|
}
|
|
33307
33333
|
function readEditorRunNote(projectPath) {
|
|
33308
33334
|
for (const directory of runtimeDirectories()) {
|
|
@@ -33574,7 +33600,7 @@ function clearRunRecord(projectPath) {
|
|
|
33574
33600
|
const ours = sharedRecord !== null && sharedRecord.projectPath !== "" && isSameDirectory(sharedRecord.projectPath, projectPath);
|
|
33575
33601
|
for (const path of [runRecordPath(projectPath, directory), ...ours ? [shared] : []]) {
|
|
33576
33602
|
try {
|
|
33577
|
-
|
|
33603
|
+
rmSync10(path, { force: true });
|
|
33578
33604
|
} catch {}
|
|
33579
33605
|
}
|
|
33580
33606
|
}
|
|
@@ -33599,7 +33625,8 @@ function sweepTranscripts(keepMs = 24 * 60 * 60 * 1000, now = Date.now()) {
|
|
|
33599
33625
|
return;
|
|
33600
33626
|
}
|
|
33601
33627
|
for (const entry of entries) {
|
|
33602
|
-
|
|
33628
|
+
const transcript = entry.startsWith("run-") && (entry.endsWith(".log") || entry.endsWith(".profile.json"));
|
|
33629
|
+
if (!transcript && !entry.endsWith(".partial")) {
|
|
33603
33630
|
continue;
|
|
33604
33631
|
}
|
|
33605
33632
|
const path = join19(directory, entry);
|
|
@@ -33607,7 +33634,7 @@ function sweepTranscripts(keepMs = 24 * 60 * 60 * 1000, now = Date.now()) {
|
|
|
33607
33634
|
continue;
|
|
33608
33635
|
}
|
|
33609
33636
|
try {
|
|
33610
|
-
|
|
33637
|
+
rmSync10(path);
|
|
33611
33638
|
} catch {}
|
|
33612
33639
|
}
|
|
33613
33640
|
}
|
|
@@ -33706,7 +33733,7 @@ function withoutListed(entries, errors) {
|
|
|
33706
33733
|
}
|
|
33707
33734
|
|
|
33708
33735
|
// src/script-profile.ts
|
|
33709
|
-
import { readFileSync as readFileSync19
|
|
33736
|
+
import { readFileSync as readFileSync19 } from "node:fs";
|
|
33710
33737
|
|
|
33711
33738
|
// src/godot-variant.ts
|
|
33712
33739
|
var FLAG_64 = 1 << 16;
|
|
@@ -34929,7 +34956,7 @@ var TOOL_SPECS = [
|
|
|
34929
34956
|
requires: []
|
|
34930
34957
|
}
|
|
34931
34958
|
},
|
|
34932
|
-
answer: "stop: endedPid is, for a run the editor plays, the number its game announced under, read at the stop rather than remembered, and the operating system hands a freed number to the next process, so it can equal the game before it. stopped is false, with the number under notSignalled, when that number no longer answers as the process the run was started as and nothing was signalled, the note naming which property it failed on, and false with the number under stillGoing when what was told to end was still running ten seconds later. endedThrough says how the stop ended a run that was going: keeper (the process that started the game ended it through the handle it holds), signal (its pid was signalled once shown to still be the run), or editor (the editor playing it was asked). A start that ends the run before it says how under endedPreviousRunThrough, with the same values, and one that refused to signal that run names the property it failed on under previousRunLeftBecause. check: hung is a run killed at timeoutMs, and errors and warnings count what it printed, which comes back under entries with repeats folded together. profile: per function its script, the function (Class.name when the script declares a class_name), the line its body's first statement is on, for a lambda the named function it is written in (within), calls, and self and total milliseconds, with functionsCounted, scriptMs (the self time of them all) and coveredMs. complete is true when the engine's own totals arrived, which it sends only when the profiler is switched off: the runtime addon does that as a game quits, so a scene that does its work in _ready and quits is covered whole; otherwise the answer is the frames summed so far and the note says what that misses. Self time counts a call into the engine's own methods as the caller's, and a call through super too, while the overridden function's row counts it again: such a row carries selfIncludesSuper, so self times along an override chain overlap. The profiler's own cost of timing a call lands in the self time of the function called; with the runtime addon it is measured each run on an empty function, as perCallOverheadUs, and a row with at least half its selfMs in it carries overheadMs, since a small function called often costs that much less without the profiler. A function the profiler started inside is left out, since its time would be counted from the engine's start. The milliseconds are time passed while the function ran, not processor time given to it, so they grow when other processes compete for the processor: compare two profiles taken under the same load. They are also summed over every thread: a function run by WorkerThreadPool or a Thread on several threads at once counts each thread's time, the engine does not say which thread a call ran on, and a function waiting for them has the wait in its own self time, so selfMs is not time taken from frames, and scriptMs can exceed coveredMs, which the note then says. A property's get and set blocks are named Class.@name_getter and Class.@name_setter, as the engine names them, and their rows carry property, the property's name. A game the editor plays that was not started with profile: true says, in the answer, that it was profiled from the moment it was asked.",
|
|
34959
|
+
answer: "stop: endedPid is, for a run the editor plays, the number its game announced under, read at the stop rather than remembered, and the operating system hands a freed number to the next process, so it can equal the game before it. stopped is false, with the number under notSignalled, when that number no longer answers as the process the run was started as and nothing was signalled, the note naming which property it failed on, and false with the number under stillGoing when what was told to end was still running ten seconds later. endedThrough says how the stop ended a run that was going: keeper (the process that started the game ended it through the handle it holds), signal (its pid was signalled once shown to still be the run), or editor (the editor playing it was asked). A start that ends the run before it says how under endedPreviousRunThrough, with the same values, and one that refused to signal that run names the property it failed on under previousRunLeftBecause. check: hung is a run killed at timeoutMs, and errors and warnings count what it printed, which comes back under entries with repeats folded together. arguments is the engine command line the check ran, as a start's is. profile: per function its script, the function (Class.name when the script declares a class_name), the line its body's first statement is on, for a lambda the named function it is written in (within), calls, and self and total milliseconds, with functionsCounted, scriptMs (the self time of them all) and coveredMs. complete is true when the engine's own totals arrived, which it sends only when the profiler is switched off: the runtime addon does that as a game quits, so a scene that does its work in _ready and quits is covered whole; otherwise the answer is the frames summed so far and the note says what that misses. Self time counts a call into the engine's own methods as the caller's, and a call through super too, while the overridden function's row counts it again: such a row carries selfIncludesSuper, so self times along an override chain overlap. The profiler's own cost of timing a call lands in the self time of the function called; with the runtime addon it is measured each run on an empty function, as perCallOverheadUs, and a row with at least half its selfMs in it carries overheadMs, since a small function called often costs that much less without the profiler. A function the profiler started inside is left out, since its time would be counted from the engine's start. The milliseconds are time passed while the function ran, not processor time given to it, so they grow when other processes compete for the processor: compare two profiles taken under the same load. They are also summed over every thread: a function run by WorkerThreadPool or a Thread on several threads at once counts each thread's time, the engine does not say which thread a call ran on, and a function waiting for them has the wait in its own self time, so selfMs is not time taken from frames, and scriptMs can exceed coveredMs, which the note then says. A property's get and set blocks are named Class.@name_getter and Class.@name_setter, as the engine names them, and their rows carry property, the property's name. A game the editor plays that was not started with profile: true says, in the answer, that it was profiled from the moment it was asked.",
|
|
34933
34960
|
defaultOperation: "start"
|
|
34934
34961
|
},
|
|
34935
34962
|
{
|
|
@@ -35623,9 +35650,9 @@ import {
|
|
|
35623
35650
|
readdirSync as readdirSync8,
|
|
35624
35651
|
readFileSync as readFileSync20,
|
|
35625
35652
|
rmdirSync,
|
|
35626
|
-
rmSync as
|
|
35653
|
+
rmSync as rmSync11,
|
|
35627
35654
|
statSync as statSync7,
|
|
35628
|
-
writeFileSync as
|
|
35655
|
+
writeFileSync as writeFileSync14
|
|
35629
35656
|
} from "node:fs";
|
|
35630
35657
|
import { dirname as dirname14, join as join21 } from "node:path";
|
|
35631
35658
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
@@ -40709,6 +40736,7 @@ class GodotServer {
|
|
|
40709
40736
|
frames,
|
|
40710
40737
|
errors,
|
|
40711
40738
|
warnings,
|
|
40739
|
+
arguments: cmdArgs,
|
|
40712
40740
|
argsNote: engineFlagsNote(userArgs),
|
|
40713
40741
|
...scanWaitAnswer(scanned),
|
|
40714
40742
|
savesNote: asked.savesIn !== undefined && savesStayPut() ? SAVES_NOT_MOVED_NOTE : undefined,
|
package/build/keeper.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
// src/keeper.ts
|
|
4
4
|
import { spawn } from "node:child_process";
|
|
5
|
-
import { closeSync, existsSync as existsSync2, mkdtempSync as mkdtempSync3, openSync as openSync2, readFileSync as
|
|
5
|
+
import { closeSync, existsSync as existsSync2, mkdtempSync as mkdtempSync3, openSync as openSync2, readFileSync as readFileSync2, rmSync as rmSync5, writeFileSync as writeFileSync3 } from "node:fs";
|
|
6
6
|
import { tmpdir as tmpdir4 } from "node:os";
|
|
7
7
|
import { join as join5 } from "node:path";
|
|
8
8
|
import { setTimeout as delay } from "node:timers/promises";
|
|
@@ -230,6 +230,14 @@ import { connect, createServer } from "node:net";
|
|
|
230
230
|
import { tmpdir } from "node:os";
|
|
231
231
|
import { join } from "node:path";
|
|
232
232
|
import process2 from "node:process";
|
|
233
|
+
function endedOrWhyNot(end) {
|
|
234
|
+
try {
|
|
235
|
+
return end();
|
|
236
|
+
} catch (error) {
|
|
237
|
+
return `could not end it: ${error instanceof Error ? error.message : String(error)}`.replaceAll(`
|
|
238
|
+
`, " ");
|
|
239
|
+
}
|
|
240
|
+
}
|
|
233
241
|
async function listenForAStop(end) {
|
|
234
242
|
let directory = null;
|
|
235
243
|
try {
|
|
@@ -244,7 +252,7 @@ async function listenForAStop(end) {
|
|
|
244
252
|
if (asked.includes(`
|
|
245
253
|
`)) {
|
|
246
254
|
socket.end(asked.startsWith(`stop
|
|
247
|
-
`) ? `${end
|
|
255
|
+
`) ? `${endedOrWhyNot(end)}
|
|
248
256
|
` : `unknown
|
|
249
257
|
`);
|
|
250
258
|
}
|
|
@@ -315,16 +323,7 @@ function readLaunched(printed) {
|
|
|
315
323
|
// src/run-record.ts
|
|
316
324
|
import { execFile as execFile2 } from "node:child_process";
|
|
317
325
|
import { createHash as createHash2 } from "node:crypto";
|
|
318
|
-
import {
|
|
319
|
-
mkdirSync,
|
|
320
|
-
openSync,
|
|
321
|
-
readdirSync,
|
|
322
|
-
readFileSync,
|
|
323
|
-
realpathSync,
|
|
324
|
-
rmSync as rmSync2,
|
|
325
|
-
statSync,
|
|
326
|
-
writeFileSync as writeFileSync2
|
|
327
|
-
} from "node:fs";
|
|
326
|
+
import { mkdirSync, openSync, readdirSync, readFileSync, realpathSync, rmSync as rmSync3, statSync } from "node:fs";
|
|
328
327
|
import { basename, dirname as dirname2, join as join3, resolve as resolve2 } from "node:path";
|
|
329
328
|
import { promisify as promisify2 } from "node:util";
|
|
330
329
|
|
|
@@ -405,6 +404,36 @@ var verdicts = new Map;
|
|
|
405
404
|
var LATE_ANSWER_MS = 30 * 60000;
|
|
406
405
|
var lateAnswers = new Map;
|
|
407
406
|
|
|
407
|
+
// src/whole-file.ts
|
|
408
|
+
import { renameSync, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
409
|
+
var HELD_FILE_MS = 1000;
|
|
410
|
+
var HELD_FILE_RETRY_MS = 10;
|
|
411
|
+
var HELD_BY_A_READER = new Set(["EPERM", "EACCES", "EBUSY"]);
|
|
412
|
+
function writeWhole(path, text) {
|
|
413
|
+
const partial = `${path}.${process.pid}.partial`;
|
|
414
|
+
try {
|
|
415
|
+
writeFileSync2(partial, text, "utf8");
|
|
416
|
+
} catch {
|
|
417
|
+
return false;
|
|
418
|
+
}
|
|
419
|
+
const until = Date.now() + HELD_FILE_MS;
|
|
420
|
+
for (;; ) {
|
|
421
|
+
try {
|
|
422
|
+
renameSync(partial, path);
|
|
423
|
+
return true;
|
|
424
|
+
} catch (error) {
|
|
425
|
+
const code = error.code ?? "";
|
|
426
|
+
if (!HELD_BY_A_READER.has(code) || Date.now() >= until) {
|
|
427
|
+
try {
|
|
428
|
+
rmSync2(partial, { force: true });
|
|
429
|
+
} catch {}
|
|
430
|
+
return false;
|
|
431
|
+
}
|
|
432
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, HELD_FILE_RETRY_MS);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
408
437
|
// src/run-record.ts
|
|
409
438
|
var execFileAsync = promisify2(execFile2);
|
|
410
439
|
function runsDirectory() {
|
|
@@ -436,7 +465,10 @@ function runRecordPath(projectPath, directory = runtimeDirectory()) {
|
|
|
436
465
|
}
|
|
437
466
|
function writeRunRecord(record) {
|
|
438
467
|
mkdirSync(runsDirectory(), { recursive: true });
|
|
439
|
-
|
|
468
|
+
const path = runRecordPath(record.projectPath);
|
|
469
|
+
if (!writeWhole(path, JSON.stringify(record, null, 2))) {
|
|
470
|
+
throw new Error(`the run note could not be written at ${path}`);
|
|
471
|
+
}
|
|
440
472
|
}
|
|
441
473
|
function recordRunEnded(projectPath, pid, ending) {
|
|
442
474
|
const path = runRecordPath(projectPath);
|
|
@@ -450,9 +482,7 @@ function recordRunEnded(projectPath, pid, ending) {
|
|
|
450
482
|
...ending.exitSignal === null ? {} : { exitSignal: ending.exitSignal },
|
|
451
483
|
endedAt: Date.now()
|
|
452
484
|
};
|
|
453
|
-
|
|
454
|
-
writeFileSync2(path, JSON.stringify(written, null, 2), "utf8");
|
|
455
|
-
} catch {}
|
|
485
|
+
writeWhole(path, JSON.stringify(written, null, 2));
|
|
456
486
|
}
|
|
457
487
|
function recordAt(path) {
|
|
458
488
|
let parsed;
|
|
@@ -490,7 +520,7 @@ function recordAt(path) {
|
|
|
490
520
|
}
|
|
491
521
|
|
|
492
522
|
// src/scratch.ts
|
|
493
|
-
import { existsSync, mkdtempSync as mkdtempSync2, readdirSync as readdirSync2, rmSync as
|
|
523
|
+
import { existsSync, mkdtempSync as mkdtempSync2, readdirSync as readdirSync2, rmSync as rmSync4 } from "node:fs";
|
|
494
524
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
495
525
|
import { join as join4 } from "node:path";
|
|
496
526
|
import process3 from "node:process";
|
|
@@ -523,7 +553,7 @@ function discard(...paths) {
|
|
|
523
553
|
return left;
|
|
524
554
|
}
|
|
525
555
|
function remove(path) {
|
|
526
|
-
|
|
556
|
+
rmSync4(path, SWEPT);
|
|
527
557
|
}
|
|
528
558
|
function tryAgain(path, attempt) {
|
|
529
559
|
if (attempt === 0) {
|
|
@@ -567,7 +597,6 @@ function lastTryOnExit() {
|
|
|
567
597
|
}
|
|
568
598
|
|
|
569
599
|
// src/script-profile.ts
|
|
570
|
-
import { readFileSync as readFileSync2, renameSync, writeFileSync as writeFileSync3 } from "node:fs";
|
|
571
600
|
import { createServer as createServer2 } from "node:net";
|
|
572
601
|
|
|
573
602
|
// src/gdscript-source.ts
|
|
@@ -1099,9 +1128,7 @@ function parseIniLikeValue(value) {
|
|
|
1099
1128
|
|
|
1100
1129
|
// src/script-profile.ts
|
|
1101
1130
|
function writeProfileFile(path, file) {
|
|
1102
|
-
|
|
1103
|
-
writeFileSync3(partial, JSON.stringify(file), "utf8");
|
|
1104
|
-
renameSync(partial, path);
|
|
1131
|
+
return writeWhole(path, JSON.stringify(file));
|
|
1105
1132
|
}
|
|
1106
1133
|
var QUEUE_SETTING = "limits/debugger/max_queued_messages";
|
|
1107
1134
|
var QUEUE_DEFAULT = 2048;
|
|
@@ -1341,12 +1368,12 @@ async function startThroughHelper(spec, output, stopFile) {
|
|
|
1341
1368
|
exited.now = true;
|
|
1342
1369
|
});
|
|
1343
1370
|
for (let waited = 0;waited < DESKTOP_START_MS; waited += 50) {
|
|
1344
|
-
const pid = existsSync2(pidFile) ? Number(
|
|
1371
|
+
const pid = existsSync2(pidFile) ? Number(readFileSync2(pidFile, "utf8").trim()) : Number.NaN;
|
|
1345
1372
|
if (Number.isInteger(pid) && pid > 0) {
|
|
1346
1373
|
return { child: started.child, pid };
|
|
1347
1374
|
}
|
|
1348
1375
|
if (exited.now) {
|
|
1349
|
-
const why = existsSync2(errorFile) ?
|
|
1376
|
+
const why = existsSync2(errorFile) ? readFileSync2(errorFile, "utf8").trim() : "it exited saying nothing";
|
|
1350
1377
|
return { error: `it could not be started ${where}: ${why}` };
|
|
1351
1378
|
}
|
|
1352
1379
|
await delay(50);
|
|
@@ -1358,18 +1385,19 @@ async function startThroughHelper(spec, output, stopFile) {
|
|
|
1358
1385
|
}
|
|
1359
1386
|
}
|
|
1360
1387
|
var PROFILE_WRITE_MS = 500;
|
|
1388
|
+
var LAST_WRITE_TRIES = 5;
|
|
1361
1389
|
async function profiledBy(path, projectPath) {
|
|
1362
1390
|
let latest = null;
|
|
1363
1391
|
let timer = null;
|
|
1364
1392
|
const write = () => {
|
|
1365
1393
|
timer = null;
|
|
1366
|
-
if (latest !== null) {
|
|
1367
|
-
|
|
1394
|
+
if (latest !== null && !writeProfileFile(path, { ...latest, connected: true })) {
|
|
1395
|
+
timer = setTimeout(write, PROFILE_WRITE_MS);
|
|
1368
1396
|
}
|
|
1369
1397
|
};
|
|
1370
1398
|
let projectGodot = null;
|
|
1371
1399
|
try {
|
|
1372
|
-
projectGodot =
|
|
1400
|
+
projectGodot = readFileSync2(join5(projectPath, "project.godot"), "utf8");
|
|
1373
1401
|
} catch {}
|
|
1374
1402
|
const listener = await ProfileListener.open((totals) => {
|
|
1375
1403
|
const first = latest === null;
|
|
@@ -1388,7 +1416,10 @@ async function profiledBy(path, projectPath) {
|
|
|
1388
1416
|
clearTimeout(timer);
|
|
1389
1417
|
}
|
|
1390
1418
|
latest = listener.totals() ?? latest;
|
|
1391
|
-
|
|
1419
|
+
let tries = 0;
|
|
1420
|
+
while (latest !== null && !writeProfileFile(path, { ...latest, connected: true }) && tries < LAST_WRITE_TRIES) {
|
|
1421
|
+
tries += 1;
|
|
1422
|
+
}
|
|
1392
1423
|
listener.close();
|
|
1393
1424
|
}
|
|
1394
1425
|
};
|
|
@@ -1453,6 +1484,11 @@ async function launch() {
|
|
|
1453
1484
|
process.exit(0);
|
|
1454
1485
|
});
|
|
1455
1486
|
}
|
|
1487
|
+
function writeNoteIfItCan(record) {
|
|
1488
|
+
try {
|
|
1489
|
+
writeRunRecord(record);
|
|
1490
|
+
} catch {}
|
|
1491
|
+
}
|
|
1456
1492
|
async function keep() {
|
|
1457
1493
|
const spec = await receivedSpec();
|
|
1458
1494
|
const run = spec.run;
|
|
@@ -1493,7 +1529,7 @@ async function keep() {
|
|
|
1493
1529
|
command: spec.command
|
|
1494
1530
|
};
|
|
1495
1531
|
if (record !== null) {
|
|
1496
|
-
|
|
1532
|
+
writeNoteIfItCan(record);
|
|
1497
1533
|
}
|
|
1498
1534
|
let listening = null;
|
|
1499
1535
|
child.once("exit", (code, signal) => {
|
|
@@ -1503,20 +1539,20 @@ async function keep() {
|
|
|
1503
1539
|
profiled?.finish();
|
|
1504
1540
|
listening?.close();
|
|
1505
1541
|
if (own !== null) {
|
|
1506
|
-
|
|
1542
|
+
rmSync5(own, { recursive: true, force: true });
|
|
1507
1543
|
}
|
|
1508
1544
|
process.exit(0);
|
|
1509
1545
|
});
|
|
1510
1546
|
if (record !== null) {
|
|
1511
1547
|
listening = await listenForAStop(() => {
|
|
1512
1548
|
if (stopFile !== "") {
|
|
1513
|
-
|
|
1549
|
+
writeFileSync3(stopFile, "");
|
|
1514
1550
|
return "signalled";
|
|
1515
1551
|
}
|
|
1516
1552
|
return child.kill() ? "signalled" : "gone";
|
|
1517
1553
|
});
|
|
1518
1554
|
if (listening !== null) {
|
|
1519
|
-
|
|
1555
|
+
writeNoteIfItCan({ ...record, keeper: listening.address });
|
|
1520
1556
|
}
|
|
1521
1557
|
}
|
|
1522
1558
|
process.stdout.write(`pid ${pid}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gdharness",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.20",
|
|
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",
|