gdharness 1.1.58 → 1.1.60
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 +210 -50
- package/build/godot/addons/gdharness_editor/tools/play_tools.gd +9 -0
- package/build/godot/addons/gdharness_editor/tools/profile_tools.gd +47 -13
- package/build/godot/addons/gdharness_runtime/runtime_autoload.gd +61 -10
- package/build/godot/addons/gdharness_runtime/runtime_queries.gd +55 -1
- package/build/godot/addons/gdharness_runtime/runtime_waits.gd +8 -1
- package/build/index.js +210 -50
- package/build/keeper.js +6911 -25
- package/package.json +1 -1
package/build/cli.js
CHANGED
|
@@ -9183,6 +9183,65 @@ function enclosingFunction(source, line, code = codeOf(source)) {
|
|
|
9183
9183
|
}
|
|
9184
9184
|
return null;
|
|
9185
9185
|
}
|
|
9186
|
+
function afterHeader(text, from) {
|
|
9187
|
+
let depth = 0;
|
|
9188
|
+
for (let at = text.indexOf("(", from);at !== -1 && at < text.length; at += 1) {
|
|
9189
|
+
if (text[at] === "(") {
|
|
9190
|
+
depth += 1;
|
|
9191
|
+
} else if (text[at] === ")") {
|
|
9192
|
+
depth -= 1;
|
|
9193
|
+
if (depth === 0) {
|
|
9194
|
+
const colon = text.indexOf(":", at);
|
|
9195
|
+
return colon === -1 ? -1 : colon + 1;
|
|
9196
|
+
}
|
|
9197
|
+
}
|
|
9198
|
+
}
|
|
9199
|
+
return -1;
|
|
9200
|
+
}
|
|
9201
|
+
function bodyCallsSuper(code, line, lambda) {
|
|
9202
|
+
const lines = code.split(`
|
|
9203
|
+
`);
|
|
9204
|
+
const first = lines[line - 1];
|
|
9205
|
+
if (first === undefined) {
|
|
9206
|
+
return false;
|
|
9207
|
+
}
|
|
9208
|
+
const header = lambda ? LAMBDA_HEADER.exec(first) : FUNC_LINE.exec(first);
|
|
9209
|
+
if (header !== null) {
|
|
9210
|
+
const body = afterHeader(first, header.index);
|
|
9211
|
+
if (body !== -1 && first.slice(body).trim() !== "") {
|
|
9212
|
+
return SUPER_CALL.test(first.slice(body));
|
|
9213
|
+
}
|
|
9214
|
+
}
|
|
9215
|
+
const indent = indentOf(first);
|
|
9216
|
+
let lambdaIndent = null;
|
|
9217
|
+
for (let index = line - 1;index < lines.length; index += 1) {
|
|
9218
|
+
const text = lines[index] ?? "";
|
|
9219
|
+
if (text.trim() === "") {
|
|
9220
|
+
continue;
|
|
9221
|
+
}
|
|
9222
|
+
const at = indentOf(text);
|
|
9223
|
+
if (at < indent) {
|
|
9224
|
+
break;
|
|
9225
|
+
}
|
|
9226
|
+
if (lambdaIndent !== null) {
|
|
9227
|
+
if (at > lambdaIndent) {
|
|
9228
|
+
continue;
|
|
9229
|
+
}
|
|
9230
|
+
lambdaIndent = null;
|
|
9231
|
+
}
|
|
9232
|
+
const inner = LAMBDA_HEADER.exec(text);
|
|
9233
|
+
if (SUPER_CALL.test(inner === null ? text : text.slice(0, inner.index))) {
|
|
9234
|
+
return true;
|
|
9235
|
+
}
|
|
9236
|
+
if (inner !== null) {
|
|
9237
|
+
const body = afterHeader(text, inner.index);
|
|
9238
|
+
if (body !== -1 && text.slice(body).trim() === "") {
|
|
9239
|
+
lambdaIndent = at;
|
|
9240
|
+
}
|
|
9241
|
+
}
|
|
9242
|
+
}
|
|
9243
|
+
return false;
|
|
9244
|
+
}
|
|
9186
9245
|
function callSitesOf(source, name) {
|
|
9187
9246
|
const regions = regionsOf(source);
|
|
9188
9247
|
const code = codeOf(source, regions);
|
|
@@ -9208,7 +9267,7 @@ function collectEnumValues(text, from, into) {
|
|
|
9208
9267
|
function isIdentifier(name) {
|
|
9209
9268
|
return new RegExp(`^${NAME}$`, "u").test(name) && !KEYWORDS.has(name);
|
|
9210
9269
|
}
|
|
9211
|
-
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, KEYWORDS;
|
|
9270
|
+
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, KEYWORDS;
|
|
9212
9271
|
var init_gdscript_source = __esm(() => {
|
|
9213
9272
|
IDENTIFIER_START = /[\p{L}_]/u;
|
|
9214
9273
|
IDENTIFIER_PART = /[\p{L}\p{N}_]/u;
|
|
@@ -9218,6 +9277,8 @@ var init_gdscript_source = __esm(() => {
|
|
|
9218
9277
|
EXTENDS_LINE = new RegExp(`^${ANNOTATIONS}(?:class_name\\s+${NAME}\\s+)?extends\\s`, "u");
|
|
9219
9278
|
EXTENDS_CLAUSE = /\bextends(?=\s)/u;
|
|
9220
9279
|
FUNC_LINE = new RegExp(`^[ \\t]*${ANNOTATIONS}(?:static\\s+)?func\\s+(${NAME})\\s*\\(`, "u");
|
|
9280
|
+
SUPER_CALL = /(?<![\p{L}\p{N}_.])super\s*[.(]/u;
|
|
9281
|
+
LAMBDA_HEADER = new RegExp(`(?<![\\p{L}\\p{N}_])func(?:\\s+${NAME})?\\s*\\(`, "u");
|
|
9221
9282
|
KEYWORDS = new Set([
|
|
9222
9283
|
"and",
|
|
9223
9284
|
"as",
|
|
@@ -18218,7 +18279,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
18218
18279
|
},
|
|
18219
18280
|
wait: { summary: "wait for the run to end, then answer as editor_output does", requires: [] },
|
|
18220
18281
|
profile: {
|
|
18221
|
-
summary: "where the run spends its time by GDScript function, while it runs or after it ended: per function its script, the function (Class.name when the script declares a class_name), the line its body's first statement is on, for a lambda the named function it is written in (within), calls, and self and total milliseconds, sorted by self time, with functionsCounted, scriptMs (the self time of them all) and coveredMs. complete is true when the engine's own totals arrived, which it sends only when the profiler is switched off: the runtime addon does that as a game quits, so a scene that does its work in _ready and quits is covered whole; otherwise the answer is the frames summed so far and the note says what that misses. Self time counts a call into the engine's own methods as the caller's, and a function the profiler started inside is left out, since its time would be counted from the engine's start. The milliseconds are time passed while the function ran, not processor time given to it, so they grow when other processes compete for the processor: compare two profiles taken under the same load. A run started with profile: true is profiled from its start. A game the editor plays that was not is profiled from the moment this is asked, and the answer says so; a run this server started without it is refused, since its debugger is fixed as it starts",
|
|
18282
|
+
summary: "where the run spends its time by GDScript function, while it runs or after it ended: per function its script, the function (Class.name when the script declares a class_name), the line its body's first statement is on, for a lambda the named function it is written in (within), calls, and self and total milliseconds, sorted by self time, with functionsCounted, scriptMs (the self time of them all) and coveredMs. complete is true when the engine's own totals arrived, which it sends only when the profiler is switched off: the runtime addon does that as a game quits, so a scene that does its work in _ready and quits is covered whole; otherwise the answer is the frames summed so far and the note says what that misses. Self time counts a call into the engine's own methods as the caller's, and a call through super too, while the overridden function's row counts it again: such a row carries selfIncludesSuper, so self times along an override chain overlap. A function the profiler started inside is left out, since its time would be counted from the engine's start. The milliseconds are time passed while the function ran, not processor time given to it, so they grow when other processes compete for the processor: compare two profiles taken under the same load. A run started with profile: true is profiled from its start. A game the editor plays that was not is profiled from the moment this is asked, and the answer says so; a run this server started without it is refused, since its debugger is fixed as it starts",
|
|
18222
18283
|
requires: []
|
|
18223
18284
|
}
|
|
18224
18285
|
},
|
|
@@ -18436,7 +18497,10 @@ var init_tool_definitions = __esm(() => {
|
|
|
18436
18497
|
summary: "what one property reads on a node, or through the objects it holds, refusing a name nothing along the way has",
|
|
18437
18498
|
requires: ["nodePath", "property"]
|
|
18438
18499
|
},
|
|
18439
|
-
metrics: {
|
|
18500
|
+
metrics: {
|
|
18501
|
+
summary: "frame time, memory, draw calls and the rest. frame_time is the mean wall time of a frame over the last frames_timed frames (up to 120, timed by the game itself, physics and the wait for the display included), frame_time_max the slowest of them, both in seconds and null before a frame has passed; vsync says whether a frame waits for the display, and max_fps is the game's frame-rate cap, 0 for none. fps, process_time_max and physics_time are the engine's monitors, which it sets once a second: the frames counted in the last second, and the slowest process and physics step in it, so they trail a change by up to a second and the last two are a worst case rather than the frame. The memory, object and render counts are read as asked",
|
|
18502
|
+
requires: []
|
|
18503
|
+
}
|
|
18440
18504
|
},
|
|
18441
18505
|
defaultOperation: "tree"
|
|
18442
18506
|
},
|
|
@@ -34128,7 +34192,7 @@ function runtimeRequest(endpoint, command, params, timeoutMs, keepListening = fa
|
|
|
34128
34192
|
ok: false,
|
|
34129
34193
|
reason: "busy",
|
|
34130
34194
|
requestId: id,
|
|
34131
|
-
message: `${still} Nothing was cancelled: a call that takes longer than the wait goes on and does everything it was asked, so read the state back rather than repeating it. runtime_invoke op result with requestId ${id} answers with its reply once it comes
|
|
34195
|
+
message: `${still} Nothing was cancelled: a call that takes longer than the wait goes on and does everything it was asked, so read the state back rather than repeating it. runtime_invoke op result with requestId ${id} answers with its reply once it comes. A game held at a breakpoint instead is one debug_state stack answers about.`
|
|
34132
34196
|
});
|
|
34133
34197
|
}, timeoutMs);
|
|
34134
34198
|
socket.setEncoding("utf8");
|
|
@@ -42305,6 +42369,13 @@ function readProfileFile(path) {
|
|
|
42305
42369
|
return null;
|
|
42306
42370
|
}
|
|
42307
42371
|
}
|
|
42372
|
+
function frameFunctionsFor(queued) {
|
|
42373
|
+
return Math.max(64, queued - QUEUE_HEADROOM);
|
|
42374
|
+
}
|
|
42375
|
+
function queuedMessagesOf(projectGodot) {
|
|
42376
|
+
const set = projectGodot === null ? undefined : parseProjectGodot(projectGodot)["network"]?.[QUEUE_SETTING];
|
|
42377
|
+
return typeof set === "number" && Number.isInteger(set) && set > 0 ? set : QUEUE_DEFAULT;
|
|
42378
|
+
}
|
|
42308
42379
|
function signatureParts(signature) {
|
|
42309
42380
|
const parts = signature.split("::");
|
|
42310
42381
|
if (parts.length < 3) {
|
|
@@ -42315,25 +42386,36 @@ function signatureParts(signature) {
|
|
|
42315
42386
|
return { script: parts.slice(0, -2).join("::"), line: Number.isFinite(line) ? line : 0, function: name };
|
|
42316
42387
|
}
|
|
42317
42388
|
function profiledFunctions(totals, limit, sourceOf = () => null) {
|
|
42318
|
-
const rows = rowsOf(totals);
|
|
42389
|
+
const rows = rowsOf(totals, sourceOf);
|
|
42319
42390
|
const scriptMs = ms(Object.values(totals.functions).reduce((sum, one) => sum + (one.calls > 0 ? one.selfSeconds : 0), 0));
|
|
42320
42391
|
return {
|
|
42321
42392
|
functions: rows.slice(0, limit).map((row) => placedLambda(row, sourceOf)),
|
|
42322
42393
|
omitted: Math.max(0, rows.length - limit),
|
|
42323
42394
|
scriptMs,
|
|
42324
|
-
counted: rows.length
|
|
42395
|
+
counted: rows.length,
|
|
42396
|
+
superCallers: rows.filter((row) => row.selfIncludesSuper === true).length
|
|
42325
42397
|
};
|
|
42326
42398
|
}
|
|
42327
|
-
function rowsOf(totals) {
|
|
42399
|
+
function rowsOf(totals, sourceOf) {
|
|
42400
|
+
const codes = new Map;
|
|
42401
|
+
const codeOfScript = (script) => {
|
|
42402
|
+
if (!codes.has(script)) {
|
|
42403
|
+
const source = sourceOf(script);
|
|
42404
|
+
codes.set(script, source === null ? null : codeOf(source));
|
|
42405
|
+
}
|
|
42406
|
+
return codes.get(script) ?? null;
|
|
42407
|
+
};
|
|
42328
42408
|
return Object.entries(totals.functions).filter(([, sum]) => sum.calls > 0).map(([signature, sum]) => {
|
|
42329
42409
|
const parts = signatureParts(signature);
|
|
42410
|
+
const code = codeOfScript(parts.script);
|
|
42330
42411
|
return {
|
|
42331
42412
|
script: parts.script,
|
|
42332
42413
|
function: parts.function,
|
|
42333
42414
|
line: parts.line,
|
|
42334
42415
|
calls: sum.calls,
|
|
42335
42416
|
selfMs: ms(sum.selfSeconds),
|
|
42336
|
-
totalMs: ms(sum.totalSeconds)
|
|
42417
|
+
totalMs: ms(sum.totalSeconds),
|
|
42418
|
+
...code !== null && bodyCallsSuper(code, parts.line, parts.function.endsWith("(lambda)")) ? { selfIncludesSuper: true } : {}
|
|
42337
42419
|
};
|
|
42338
42420
|
}).sort((a, b) => b.selfMs - a.selfMs || b.totalMs - a.totalMs || a.function.localeCompare(b.function));
|
|
42339
42421
|
}
|
|
@@ -42349,27 +42431,35 @@ function placedLambda(row, sourceOf) {
|
|
|
42349
42431
|
return within === null ? row : { ...row, within };
|
|
42350
42432
|
}
|
|
42351
42433
|
function callersOf(totals, name, scripts) {
|
|
42352
|
-
const
|
|
42434
|
+
const listed = [...scripts];
|
|
42435
|
+
const sources = new Map(listed);
|
|
42436
|
+
const rows = rowsOf(totals, (script) => sources.get(script) ?? null);
|
|
42353
42437
|
const bare = bareName(name);
|
|
42354
42438
|
const target = rows.filter((row) => name.includes(".") ? row.function === name : bareName(row.function) === bare);
|
|
42355
42439
|
const sites = [];
|
|
42356
|
-
for (const [script, source] of
|
|
42440
|
+
for (const [script, source] of listed) {
|
|
42357
42441
|
for (const site of callSitesOf(source, bare)) {
|
|
42358
42442
|
const within = site.within === null ? undefined : rows.find((row) => row.script === script && bareName(row.function) === site.within);
|
|
42359
42443
|
sites.push({
|
|
42360
42444
|
script,
|
|
42361
42445
|
...site,
|
|
42362
|
-
...within === undefined ? {} : {
|
|
42446
|
+
...within === undefined ? {} : {
|
|
42447
|
+
withinCalls: within.calls,
|
|
42448
|
+
withinSelfMs: within.selfMs,
|
|
42449
|
+
withinTotalMs: within.totalMs,
|
|
42450
|
+
...within.selfIncludesSuper === true ? { withinSelfIncludesSuper: true } : {}
|
|
42451
|
+
}
|
|
42363
42452
|
});
|
|
42364
42453
|
}
|
|
42365
42454
|
}
|
|
42366
42455
|
sites.sort((a, b) => (b.withinTotalMs ?? -1) - (a.withinTotalMs ?? -1) || a.script.localeCompare(b.script) || a.line - b.line);
|
|
42367
42456
|
return { target, sites: sites.slice(0, CALL_SITES), omitted: Math.max(0, sites.length - CALL_SITES) };
|
|
42368
42457
|
}
|
|
42369
|
-
var ms = (seconds) => Math.round(seconds * 1e6) / 1000, CALL_SITES = 40;
|
|
42458
|
+
var QUEUE_SETTING = "limits/debugger/max_queued_messages", QUEUE_DEFAULT = 2048, QUEUE_HEADROOM = 512, UNNAMED_PREFIX = "#", ms = (seconds) => Math.round(seconds * 1e6) / 1000, CALL_SITES = 40;
|
|
42370
42459
|
var init_script_profile = __esm(() => {
|
|
42371
42460
|
init_gdscript_source();
|
|
42372
42461
|
init_godot_variant();
|
|
42462
|
+
init_resources();
|
|
42373
42463
|
});
|
|
42374
42464
|
|
|
42375
42465
|
// src/server.ts
|
|
@@ -42395,8 +42485,14 @@ import { setTimeout as delay4 } from "node:timers/promises";
|
|
|
42395
42485
|
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
42396
42486
|
import { promisify as promisify5 } from "node:util";
|
|
42397
42487
|
function editorRestartTimeoutMs() {
|
|
42398
|
-
|
|
42399
|
-
|
|
42488
|
+
return waitFromEnv("GDHARNESS_EDITOR_RESTART_TIMEOUT_MS", 90000);
|
|
42489
|
+
}
|
|
42490
|
+
function waitFromEnv(variable, fallback) {
|
|
42491
|
+
const override = Number.parseInt(envValue(variable) ?? "", 10);
|
|
42492
|
+
return Number.isInteger(override) && override > 0 ? override : fallback;
|
|
42493
|
+
}
|
|
42494
|
+
function greetingWaitMs() {
|
|
42495
|
+
return waitFromEnv("GDHARNESS_GREETING_WAIT_MS", 1e4);
|
|
42400
42496
|
}
|
|
42401
42497
|
function restartUnderway(note, now = Date.now()) {
|
|
42402
42498
|
return alive(note.byPid) && now - Date.parse(note.quitAt) < editorRestartTimeoutMs() + 30000;
|
|
@@ -42447,6 +42543,14 @@ function captureDetail(payload) {
|
|
|
42447
42543
|
function patienceForFrames(frames, atLeast) {
|
|
42448
42544
|
return Math.max(atLeast, frames / SLOWEST_FRAME_RATE * 1000 + atLeast);
|
|
42449
42545
|
}
|
|
42546
|
+
function longerWaitNote(tool, op, waitedMs) {
|
|
42547
|
+
const spec = TOOL_SPECS.find((one) => one.name === tool);
|
|
42548
|
+
if (spec !== undefined && argumentsOf(spec, op).includes("timeoutMs")) {
|
|
42549
|
+
return "timeoutMs waits longer next time.";
|
|
42550
|
+
}
|
|
42551
|
+
const how = tool === "runtime_wait" && op === "frames" ? `the wait is counted from the number of frames, at ${SLOWEST_FRAME_RATE} a second on top of GDHARNESS_RUNTIME_TIMEOUT_MS, and this game fell below that rate, so ask for fewer frames at a time` : "the wait is GDHARNESS_RUNTIME_TIMEOUT_MS, which is set in the environment the server is started with";
|
|
42552
|
+
return `${tool} ${op} takes no timeoutMs: it was given ${waitedMs}ms, and ${how}.`;
|
|
42553
|
+
}
|
|
42450
42554
|
function projectSpelling(projectPath, absolutePath) {
|
|
42451
42555
|
return `res://${relative5(realPathOr(projectPath), realPathOr(absolutePath)).replaceAll("\\", "/")}`;
|
|
42452
42556
|
}
|
|
@@ -42526,7 +42630,10 @@ function runIsUp(run, editorSays, editorPlay = null, now = Date.now()) {
|
|
|
42526
42630
|
function noCodeWillCome(run) {
|
|
42527
42631
|
return !exited(run);
|
|
42528
42632
|
}
|
|
42529
|
-
|
|
42633
|
+
function stopWaitMs() {
|
|
42634
|
+
return waitFromEnv("GDHARNESS_STOP_WAIT_MS", 1e4);
|
|
42635
|
+
}
|
|
42636
|
+
async function untilGone(pids, withinMs = stopWaitMs()) {
|
|
42530
42637
|
const gone = () => pids.every((pid) => !alive(pid));
|
|
42531
42638
|
const deadline = Date.now() + withinMs;
|
|
42532
42639
|
while (!gone() && Date.now() < deadline) {
|
|
@@ -42581,7 +42688,7 @@ function stopVerdict(stop) {
|
|
|
42581
42688
|
return {
|
|
42582
42689
|
stopped: false,
|
|
42583
42690
|
stillGoing: stop.endedPid,
|
|
42584
|
-
note: `${told} ${
|
|
42691
|
+
note: `${told} ${stopWaitMs() / 1000} seconds later, so it has not stopped. editor_status says when it has gone, and editor_run stop asks again.`,
|
|
42585
42692
|
withChildren: true
|
|
42586
42693
|
};
|
|
42587
42694
|
}
|
|
@@ -42699,7 +42806,7 @@ function endedToStartThis(ended) {
|
|
|
42699
42806
|
return ` ${which} was not ended here: the editor could not be asked to stop it. The editor stops a play before starting the next, so it is not running beside this one if this start reached the editor.`;
|
|
42700
42807
|
}
|
|
42701
42808
|
if (ended.ending === "lingering") {
|
|
42702
|
-
return ` ${which} was told to end to start this one and was still running ${
|
|
42809
|
+
return ` ${which} was told to end to start this one and was still running ${stopWaitMs() / 1000} seconds later, so it is running beside this one: editor_status says when it has gone.`;
|
|
42703
42810
|
}
|
|
42704
42811
|
return ` ${which} was ended to start this one; its output is no longer what editor_output answers about.`;
|
|
42705
42812
|
}
|
|
@@ -42816,6 +42923,9 @@ function outsideTheirRanges(spec, args) {
|
|
|
42816
42923
|
}
|
|
42817
42924
|
return complaints;
|
|
42818
42925
|
}
|
|
42926
|
+
function scanWaitMs() {
|
|
42927
|
+
return waitFromEnv("GDHARNESS_SCAN_WAIT_MS", 30000);
|
|
42928
|
+
}
|
|
42819
42929
|
function scanWaitIn(payload) {
|
|
42820
42930
|
const waited = readNumber(payload, "waitedForEditorScanMs");
|
|
42821
42931
|
const note = readString(payload, "scanNote");
|
|
@@ -42874,7 +42984,7 @@ function scanWaitAnswer(scanned, started = "game") {
|
|
|
42874
42984
|
if (scanned.waitedMs === 0) {
|
|
42875
42985
|
return {};
|
|
42876
42986
|
}
|
|
42877
|
-
const still = `The editor was still scanning the project after ${
|
|
42987
|
+
const still = `The editor was still scanning the project after ${scanWaitMs() / 1000} seconds, so`;
|
|
42878
42988
|
return {
|
|
42879
42989
|
waitedForEditorScanMs: scanned.waitedMs,
|
|
42880
42990
|
scanNote: !scanned.stillScanning ? undefined : started === "game" ? `${still} this game started during the scan and may have read a class cache being rewritten. If ` + 'editor_output shows "Could not find type" for a class that is declared, editor_rescan ' + "waits for the scan to finish, and a start after it reads the finished cache." : `${still} the engine answering this started during the scan and may have read a class cache being ` + "rewritten, and a class cache it wrote can be written over when the scan finishes. If " + 'engine_messages shows "Could not find type" for a class that is declared, editor_rescan waits ' + "for the scan to finish, and the same call after it reads the finished cache."
|
|
@@ -43072,6 +43182,26 @@ function profileOnStart(profiled, through, runtimeAddon) {
|
|
|
43072
43182
|
totals: `On from the game's start, through ${debuggerOf}. editor_run profile answers at any time: each frame is added as it ends, so a scene still inside a long _ready shows nothing until that returns, and the engine's own totals replace the sum when the game quits` + (runtimeAddon ? "." : ", which takes the runtime addon: without it the frames that arrived before the exit are what is kept.")
|
|
43073
43183
|
};
|
|
43074
43184
|
}
|
|
43185
|
+
function profileFrameFunctions(projectPath) {
|
|
43186
|
+
let projectGodot = null;
|
|
43187
|
+
if (projectPath !== null) {
|
|
43188
|
+
try {
|
|
43189
|
+
projectGodot = readFileSync23(join26(projectPath, "project.godot"), "utf8");
|
|
43190
|
+
} catch {}
|
|
43191
|
+
}
|
|
43192
|
+
return frameFunctionsFor(queuedMessagesOf(projectGodot));
|
|
43193
|
+
}
|
|
43194
|
+
function playedCap(held) {
|
|
43195
|
+
const frameFunctions = readPositiveNumber(held, "frameFunctions");
|
|
43196
|
+
if (frameFunctions === undefined) {
|
|
43197
|
+
return {};
|
|
43198
|
+
}
|
|
43199
|
+
return {
|
|
43200
|
+
frameFunctions,
|
|
43201
|
+
cappedFrames: readNonNegativeNumber(held, "cappedFrames") ?? 0,
|
|
43202
|
+
totalCapped: readBoolean(held, "totalCapped") === true
|
|
43203
|
+
};
|
|
43204
|
+
}
|
|
43075
43205
|
function projectSourceOf(projectPath) {
|
|
43076
43206
|
const read = new Map;
|
|
43077
43207
|
return (script) => {
|
|
@@ -43109,15 +43239,22 @@ function profileAnswer(reading, limit) {
|
|
|
43109
43239
|
const table = profiledFunctions(totals, limit, reading.sourceOf);
|
|
43110
43240
|
const notes = [];
|
|
43111
43241
|
const addonNote = reading.runtimeAddon ? "" : " This project has no gdharness runtime addon, which switches the profiler off as the game quits so the engine sends its totals: setup installs it.";
|
|
43112
|
-
|
|
43242
|
+
const totalsWithheld = reading.runtimeAddon ? " The runtime addon switches the profiler off as the game quits, and did not here: either it was no longer running in this game by then, which a project can arrange for some scenes; or the game ended without taking its tree down, as a crash or a kill does; or the totals did not reach the debugger before the game exited, which the game then says in its output." : addonNote;
|
|
43243
|
+
if (totals.complete && reading.running) {
|
|
43244
|
+
notes.push(`The engine's totals arrived while the game is still running, which it sends only when the profiler is switched off, so ${reading.through === "editor" ? "the editor's Profiler tab or the game itself, through EngineDebugger," : "the game itself, through EngineDebugger,"} switched it off before the game ended. They cover the run up to then and nothing after.`);
|
|
43245
|
+
} else if (totals.complete) {} else if (reading.running && totals.frames === 0) {
|
|
43113
43246
|
notes.push(`The profiler is on and the game has not finished its first frame yet: a frame is sent as it ends, so a scene doing its work in _ready shows nothing until that returns, and the engine's own totals arrive when the game quits.${addonNote}`);
|
|
43114
43247
|
} else if (reading.running) {
|
|
43115
43248
|
notes.push(`The run is still going, so these are the ${totals.frames === 1 ? "one frame" : `${totals.frames} frames`} summed so far${reading.through === "gdharness" ? ", written at most half a second behind" : ""}.`);
|
|
43116
43249
|
} else if (totals.frames === 0) {
|
|
43117
|
-
notes.push(`The game connected and ended before a frame of the profile reached ${reading.through === "editor" ? "the editor" : "this server"}: the engine sends its totals only when the profiler is switched off, and stops sending as it exits without emptying what it has queued.${
|
|
43250
|
+
notes.push(`The game connected and ended before a frame of the profile reached ${reading.through === "editor" ? "the editor" : "this server"}: the engine sends its totals only when the profiler is switched off, and stops sending as it exits without emptying what it has queued.${totalsWithheld}`);
|
|
43118
43251
|
} else {
|
|
43119
|
-
notes.push(`These are the ${totals.frames === 1 ? "one frame" : `${totals.frames} frames`} that arrived, summed, without the engine's own totals,
|
|
43252
|
+
notes.push(`These are the ${totals.frames === 1 ? "one frame" : `${totals.frames} frames`} that arrived, summed, without the engine's own totals, which it sends only when the profiler is switched off.${totalsWithheld} Not in them is any script time after the last frame sent: a frame still queued as the game exited, since the engine stops sending without emptying its queue, and what scripts did as it quit, such as in _exit_tree. That can be nothing: a scene that does its work in _ready and quits has all of it in its first frame.`);
|
|
43120
43253
|
}
|
|
43254
|
+
if (table.superCallers > 0) {
|
|
43255
|
+
notes.push(`${table.superCallers === 1 ? "One function calls" : `${table.superCallers} functions call`} through super, marked selfIncludesSuper: the engine takes every other call out of the caller's self time, and not a super call, so ${table.superCallers === 1 ? "its" : "their"} selfMs holds the overridden function's time, which that function's own row counts again. Self time therefore cannot be added along an override chain, and scriptMs counts the time under a super call once more for each level above it.`);
|
|
43256
|
+
}
|
|
43257
|
+
notes.push(...cappedNotes(totals));
|
|
43121
43258
|
if (totals.unreadable > 0) {
|
|
43122
43259
|
notes.push(`${totals.unreadable} message${totals.unreadable === 1 ? "" : "s"} from the game could not be read, so the totals have that many gaps.`);
|
|
43123
43260
|
}
|
|
@@ -43136,6 +43273,22 @@ function profileAnswer(reading, limit) {
|
|
|
43136
43273
|
note: notes.length > 0 ? notes.join(" ") : undefined
|
|
43137
43274
|
};
|
|
43138
43275
|
}
|
|
43276
|
+
function cappedNotes(totals) {
|
|
43277
|
+
const notes = [];
|
|
43278
|
+
const most = totals.frameFunctions;
|
|
43279
|
+
const why = `${most} is kept below the game's debugger queue (network/limits/debugger/max_queued_messages), which drops the message naming a function when one frame names more than it holds, and a function whose name is dropped is never named afterwards.`;
|
|
43280
|
+
if (most !== undefined && totals.complete && totals.totalCapped === true) {
|
|
43281
|
+
notes.push(`The engine's totals carried the most functions the profiler asks for, ${most}, so the functions with the least time are not in them: ${why}`);
|
|
43282
|
+
} else if (most !== undefined && !totals.complete && (totals.cappedFrames ?? 0) > 0) {
|
|
43283
|
+
const capped = totals.cappedFrames ?? 0;
|
|
43284
|
+
notes.push(`${capped === totals.frames ? capped === 1 ? "The one frame" : `All ${capped} frames` : `${capped} of the ${totals.frames} frames`} carried the most functions the profiler asks for in one, ${most}, so the functions with the least time in ${capped === 1 ? "it" : "them"} are not counted there: ${why}`);
|
|
43285
|
+
}
|
|
43286
|
+
const unnamed = Object.entries(totals.functions).filter(([name, sum]) => name.startsWith(UNNAMED_PREFIX) && sum.calls > 0).length;
|
|
43287
|
+
if (unnamed > 0) {
|
|
43288
|
+
notes.push(`${unnamed === 1 ? "One function" : `${unnamed} functions`} arrived without ${unnamed === 1 ? "its name" : "their names"}, shown as ${UNNAMED_PREFIX} and the number the engine gave ${unnamed === 1 ? "it" : "them"}: the engine names a function once, as it first appears, and the game's debugger queue was full when that message was sent. They are script functions of the game; which ones cannot be recovered from this profile.`);
|
|
43289
|
+
}
|
|
43290
|
+
return notes;
|
|
43291
|
+
}
|
|
43139
43292
|
function callersAnswer(totals, asked) {
|
|
43140
43293
|
const found = callersOf(totals, asked.name, asked.scripts());
|
|
43141
43294
|
return {
|
|
@@ -43836,7 +43989,7 @@ class GodotServer {
|
|
|
43836
43989
|
}
|
|
43837
43990
|
async dispatch(tool, op, args) {
|
|
43838
43991
|
if (tool !== "editor_status" && this.godotBridge.isArriving()) {
|
|
43839
|
-
await this.waitForBridge(() => !this.godotBridge.isArriving(), Date.now() +
|
|
43992
|
+
await this.waitForBridge(() => !this.godotBridge.isArriving(), Date.now() + greetingWaitMs());
|
|
43840
43993
|
}
|
|
43841
43994
|
if (ENGINE_PASSES[tool]?.[op] !== undefined) {
|
|
43842
43995
|
return op === "reimport" ? await this.handleReimport(args) : await this.handleRefreshUids(args);
|
|
@@ -43996,7 +44149,7 @@ class GodotServer {
|
|
|
43996
44149
|
depth: depth ?? 3,
|
|
43997
44150
|
include_properties: readBoolean(args, "includeProperties") ?? false,
|
|
43998
44151
|
properties: readArray(args, "properties") ?? []
|
|
43999
|
-
}, patience);
|
|
44152
|
+
}, { tool: "runtime_inspect", op, patience });
|
|
44000
44153
|
}
|
|
44001
44154
|
case "find":
|
|
44002
44155
|
return await this.handleFindRuntimeNodes(args, patience);
|
|
@@ -44006,17 +44159,17 @@ class GodotServer {
|
|
|
44006
44159
|
root: readNonEmptyString(args, "nodePath") ?? "/root",
|
|
44007
44160
|
include_hidden: readBoolean(args, "includeHidden") ?? false,
|
|
44008
44161
|
limit: readPositiveNumber(args, "limit") ?? 500
|
|
44009
|
-
}, patience);
|
|
44162
|
+
}, { tool: "runtime_inspect", op, patience });
|
|
44010
44163
|
case "rect":
|
|
44011
|
-
return await this.handleRuntimeCommand("get_rect", { ...whichGame(args), path: readNonEmptyString(args, "nodePath") ?? "" }, patience);
|
|
44164
|
+
return await this.handleRuntimeCommand("get_rect", { ...whichGame(args), path: readNonEmptyString(args, "nodePath") ?? "" }, { tool: "runtime_inspect", op, patience });
|
|
44012
44165
|
case "property":
|
|
44013
44166
|
return await this.handleRuntimeCommand("get_property", {
|
|
44014
44167
|
...whichGame(args),
|
|
44015
44168
|
path: readNonEmptyString(args, "nodePath") ?? "",
|
|
44016
44169
|
property: readNonEmptyString(args, "property") ?? ""
|
|
44017
|
-
}, patience);
|
|
44170
|
+
}, { tool: "runtime_inspect", op, patience });
|
|
44018
44171
|
default:
|
|
44019
|
-
return await this.handleRuntimeCommand("get_metrics", { ...whichGame(args), metrics: readArray(args, "metrics") ?? [] }, patience);
|
|
44172
|
+
return await this.handleRuntimeCommand("get_metrics", { ...whichGame(args), metrics: readArray(args, "metrics") ?? [] }, { tool: "runtime_inspect", op, patience });
|
|
44020
44173
|
}
|
|
44021
44174
|
}
|
|
44022
44175
|
case "runtime_invoke": {
|
|
@@ -44029,16 +44182,16 @@ class GodotServer {
|
|
|
44029
44182
|
path: readNonEmptyString(args, "nodePath") ?? "",
|
|
44030
44183
|
property: readString(args, "property") ?? "",
|
|
44031
44184
|
value: args["value"]
|
|
44032
|
-
}, patience) : await this.handleRuntimeCommand("call_method", {
|
|
44185
|
+
}, { tool: "runtime_invoke", op, patience }) : await this.handleRuntimeCommand("call_method", {
|
|
44033
44186
|
...whichGame(args),
|
|
44034
44187
|
path: readNonEmptyString(args, "nodePath") ?? "",
|
|
44035
44188
|
method: readString(args, "method") ?? "",
|
|
44036
44189
|
args: readArray(args, "args") ?? [],
|
|
44037
44190
|
...args["properties"] === undefined ? {} : { properties: args["properties"] }
|
|
44038
|
-
}, patience);
|
|
44191
|
+
}, { tool: "runtime_invoke", op, patience });
|
|
44039
44192
|
}
|
|
44040
44193
|
case "runtime_capture":
|
|
44041
|
-
return await this.handleRuntimeCommand(op === "screenshot" ? "capture_screenshot" : "capture_viewport", args);
|
|
44194
|
+
return await this.handleRuntimeCommand(op === "screenshot" ? "capture_screenshot" : "capture_viewport", args, { tool: "runtime_capture", op });
|
|
44042
44195
|
case "runtime_input":
|
|
44043
44196
|
if (op === "click") {
|
|
44044
44197
|
if (readNonEmptyString(args, "nodePath") === undefined && readNonEmptyString(args, "says") === undefined) {
|
|
@@ -44051,7 +44204,7 @@ class GodotServer {
|
|
|
44051
44204
|
double: readBoolean(args, "doubleClick") ?? false,
|
|
44052
44205
|
...readNonEmptyString(args, "says") === undefined ? {} : { says: readNonEmptyString(args, "says") },
|
|
44053
44206
|
...args["index"] === undefined ? {} : { index: args["index"] }
|
|
44054
|
-
});
|
|
44207
|
+
}, { tool: "runtime_input", op });
|
|
44055
44208
|
}
|
|
44056
44209
|
if (op === "choose") {
|
|
44057
44210
|
return await this.handleRuntimeCommand("choose", {
|
|
@@ -44059,9 +44212,9 @@ class GodotServer {
|
|
|
44059
44212
|
path: readNonEmptyString(args, "nodePath") ?? "",
|
|
44060
44213
|
text: readString(args, "text") ?? "",
|
|
44061
44214
|
...args["index"] === undefined ? {} : { index: args["index"] }
|
|
44062
|
-
});
|
|
44215
|
+
}, { tool: "runtime_input", op });
|
|
44063
44216
|
}
|
|
44064
|
-
return await this.handleRuntimeCommand(`inject_${op}`, args);
|
|
44217
|
+
return await this.handleRuntimeCommand(`inject_${op}`, args, { tool: "runtime_input", op });
|
|
44065
44218
|
case "runtime_wait":
|
|
44066
44219
|
return await this.handleRuntimeWait(op, args);
|
|
44067
44220
|
case "debug_breakpoint": {
|
|
@@ -44290,7 +44443,7 @@ class GodotServer {
|
|
|
44290
44443
|
const answered = Date.now();
|
|
44291
44444
|
if (status["scanning"] === true || status["importing"] === true || status["pending"] === true) {
|
|
44292
44445
|
sawAScan = true;
|
|
44293
|
-
if (Date.now() - started >=
|
|
44446
|
+
if (Date.now() - started >= scanWaitMs()) {
|
|
44294
44447
|
return { waitedMs: Date.now() - started, stillScanning: true };
|
|
44295
44448
|
}
|
|
44296
44449
|
await new Promise((settle) => setTimeout(settle, 100));
|
|
@@ -45637,7 +45790,7 @@ class GodotServer {
|
|
|
45637
45790
|
]);
|
|
45638
45791
|
}
|
|
45639
45792
|
if (!hasSaidWhoItIs(before)) {
|
|
45640
|
-
return this.createErrorResponse(`The editor has connected and has not said who it is within ${
|
|
45793
|
+
return this.createErrorResponse(`The editor has connected and has not said who it is within ${greetingWaitMs() / 1000}s, so whether this server or the editor itself restarts it cannot be told.`, ["editor_status says when it has, under greeting while it has not", "Then ask for the restart again"]);
|
|
45641
45794
|
}
|
|
45642
45795
|
const mine = before.openedByAServer === true && before.projectPath !== undefined;
|
|
45643
45796
|
if (!mine && args["hidden"] === true) {
|
|
@@ -46082,7 +46235,10 @@ class GodotServer {
|
|
|
46082
46235
|
const breakpoints = await this.dapHoldingBreakpointsOf(projectPath).reapplyBreakpoints();
|
|
46083
46236
|
this.logDebug(`Breakpoints were sent again after ${Date.now() - sendingFrom}ms`);
|
|
46084
46237
|
if (profiled) {
|
|
46085
|
-
const armed = await this.handleViaBridge("profile_start", {
|
|
46238
|
+
const armed = await this.handleViaBridge("profile_start", {
|
|
46239
|
+
arm: true,
|
|
46240
|
+
frameFunctions: profileFrameFunctions(projectPath)
|
|
46241
|
+
});
|
|
46086
46242
|
if (armed.isError === true) {
|
|
46087
46243
|
return this.profilerRefusal(armed);
|
|
46088
46244
|
}
|
|
@@ -46249,7 +46405,7 @@ class GodotServer {
|
|
|
46249
46405
|
game.brokeOn = halt.text;
|
|
46250
46406
|
game.log.record("error", halt.text);
|
|
46251
46407
|
}
|
|
46252
|
-
async untilTheEditorStopsPlaying(withinMs =
|
|
46408
|
+
async untilTheEditorStopsPlaying(withinMs = stopWaitMs()) {
|
|
46253
46409
|
const deadline = Date.now() + withinMs;
|
|
46254
46410
|
for (;; ) {
|
|
46255
46411
|
if ((await this.editorPlayingState())?.playing === false) {
|
|
@@ -46343,7 +46499,7 @@ class GodotServer {
|
|
|
46343
46499
|
running.endedThrough = "signal";
|
|
46344
46500
|
}
|
|
46345
46501
|
clearTheRecord();
|
|
46346
|
-
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 ${
|
|
46502
|
+
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}.`);
|
|
46347
46503
|
return gone ? "gone" : "lingering";
|
|
46348
46504
|
}
|
|
46349
46505
|
async endThroughTheKeeper(run, pid, keeper) {
|
|
@@ -46363,7 +46519,7 @@ class GodotServer {
|
|
|
46363
46519
|
run.exitCode = null;
|
|
46364
46520
|
run.exitSignal = "SIGTERM";
|
|
46365
46521
|
}
|
|
46366
|
-
run.log.record(gone ? "info" : "warning", !gone ? `gdharness had the keeper holding pid ${pid} end it, and it was still running ${
|
|
46522
|
+
run.log.record(gone ? "info" : "warning", !gone ? `gdharness had the keeper holding pid ${pid} end it, and it was still running ${stopWaitMs() / 1000} seconds later.` : asked === "signalled" ? `gdharness ended pid ${pid} through the keeper holding it.` : `gdharness asked the keeper holding pid ${pid} to end it, and it had already gone.`);
|
|
46367
46523
|
return gone ? "gone" : "lingering";
|
|
46368
46524
|
}
|
|
46369
46525
|
spawnGame(godotPath, cmdArgs, env) {
|
|
@@ -46865,7 +47021,7 @@ class GodotServer {
|
|
|
46865
47021
|
...callersName === undefined ? {} : { callersOf: { name: callersName, scripts: () => projectScripts(projectPath) } }
|
|
46866
47022
|
};
|
|
46867
47023
|
if (run.throughEditor) {
|
|
46868
|
-
return await this.profileOfThePlayedGame(going, fromProject, limit);
|
|
47024
|
+
return await this.profileOfThePlayedGame(going, projectPath, fromProject, limit);
|
|
46869
47025
|
}
|
|
46870
47026
|
const record = run.projectPath === null ? null : readRunRecord(run.projectPath);
|
|
46871
47027
|
const path = record !== null && record.pid === run.pid ? record.profile : undefined;
|
|
@@ -46887,7 +47043,7 @@ class GodotServer {
|
|
|
46887
47043
|
...fromProject
|
|
46888
47044
|
}, limit));
|
|
46889
47045
|
}
|
|
46890
|
-
async profileOfThePlayedGame(going, fromProject, limit) {
|
|
47046
|
+
async profileOfThePlayedGame(going, projectPath, fromProject, limit) {
|
|
46891
47047
|
const read = await this.handleViaBridge("profile_read", {});
|
|
46892
47048
|
if (read.isError === true) {
|
|
46893
47049
|
return this.profilerRefusal(read);
|
|
@@ -46899,7 +47055,9 @@ class GodotServer {
|
|
|
46899
47055
|
"editor_run start with profile: true plays it again with the profiler on from its start"
|
|
46900
47056
|
]);
|
|
46901
47057
|
}
|
|
46902
|
-
const started = await this.handleViaBridge("profile_start", {
|
|
47058
|
+
const started = await this.handleViaBridge("profile_start", {
|
|
47059
|
+
frameFunctions: profileFrameFunctions(projectPath)
|
|
47060
|
+
});
|
|
46903
47061
|
if (started.isError === true) {
|
|
46904
47062
|
return this.profilerRefusal(started);
|
|
46905
47063
|
}
|
|
@@ -46922,6 +47080,7 @@ class GodotServer {
|
|
|
46922
47080
|
frames: readNonNegativeNumber(held, "frames") ?? 0,
|
|
46923
47081
|
complete: readBoolean(held, "complete") === true,
|
|
46924
47082
|
unreadable: 0,
|
|
47083
|
+
...playedCap(held),
|
|
46925
47084
|
functions: Object.fromEntries(Object.entries(functions).map(([signature, sum]) => {
|
|
46926
47085
|
const [calls, self, total] = Array.isArray(sum) ? sum : [];
|
|
46927
47086
|
return [
|
|
@@ -47285,7 +47444,7 @@ class GodotServer {
|
|
|
47285
47444
|
...landed ? { endedThrough: "signal" } : {},
|
|
47286
47445
|
...childrenFields(ended),
|
|
47287
47446
|
exitedBeforeStop: !landed,
|
|
47288
|
-
note: `${!gone ? `pid ${pid} was told to end and was still running ${
|
|
47447
|
+
note: `${!gone ? `pid ${pid} was told to end and was still running ${stopWaitMs() / 1000} seconds later, so it has not stopped: a game of this project that no server here was holding,` : landed ? `pid ${pid} was ended: a game of this project that no server here was holding,` : `pid ${pid} had already gone when it was signalled: a game of this project that no server here was holding,`} identified by its announcement and by its command line, ${read.executable} run with --path ${read.projectPath ?? ""}. Nothing here read what it printed or holds its exit code, so neither is in this answer.${gone ? "" : " editor_status says when it has gone."} ${aboutTheChildren(ended, false)}`
|
|
47289
47448
|
});
|
|
47290
47449
|
}
|
|
47291
47450
|
async whatTheRunStarted(run) {
|
|
@@ -48021,7 +48180,8 @@ class GodotServer {
|
|
|
48021
48180
|
}
|
|
48022
48181
|
return `The game this server started (${which}) is still coming up and has not announced its runtime after a wait of ${Math.min(budgetMs, remaining)}ms, so there is nothing to talk to yet. It is still running: ask again in a moment rather than starting another.`;
|
|
48023
48182
|
}
|
|
48024
|
-
async handleRuntimeCommand(command, args,
|
|
48183
|
+
async handleRuntimeCommand(command, args, asked) {
|
|
48184
|
+
const timeoutMs = asked.patience ?? this.runtimeTimeoutMs();
|
|
48025
48185
|
const { op: _op, projectPath, pid, outputPath, ...params } = asParams(args);
|
|
48026
48186
|
const waited = typeof pid === "number" ? null : await this.ownRunOnItsWay(typeof projectPath === "string" ? projectPath : this.ownProject, timeoutMs);
|
|
48027
48187
|
if (waited !== null) {
|
|
@@ -48074,7 +48234,7 @@ ${staleNote}`);
|
|
|
48074
48234
|
requestId: reply.requestId,
|
|
48075
48235
|
command,
|
|
48076
48236
|
waitedMs: timeoutMs,
|
|
48077
|
-
note: reply.message,
|
|
48237
|
+
note: `${reply.message} ${longerWaitNote(asked.tool, asked.op, timeoutMs)}`,
|
|
48078
48238
|
...answeredBy,
|
|
48079
48239
|
...stale
|
|
48080
48240
|
});
|
|
@@ -48174,13 +48334,13 @@ ${staleNote}`}`
|
|
|
48174
48334
|
limit: readPositiveNumber(args, "limit") ?? 100,
|
|
48175
48335
|
...property === undefined ? {} : { property },
|
|
48176
48336
|
...includeHidden === undefined ? {} : { include_hidden: includeHidden }
|
|
48177
|
-
}, patience);
|
|
48337
|
+
}, { tool: "runtime_inspect", op: "find", patience });
|
|
48178
48338
|
}
|
|
48179
48339
|
async handleRuntimeWait(op, args) {
|
|
48180
48340
|
if (op === "frames") {
|
|
48181
48341
|
const frames = readPositiveNumber(args, "frames") ?? 1;
|
|
48182
48342
|
const waited = patienceForFrames(frames, this.runtimeTimeoutMs());
|
|
48183
|
-
return await this.handleRuntimeCommand("wait_frames", { ...whichGame(args), frames }, waited);
|
|
48343
|
+
return await this.handleRuntimeCommand("wait_frames", { ...whichGame(args), frames }, { tool: "runtime_wait", op, patience: waited });
|
|
48184
48344
|
}
|
|
48185
48345
|
const timeoutMs = readPositiveNumber(args, "timeoutMs") ?? 5000;
|
|
48186
48346
|
const asked = readNonEmptyString(args, "nodePath");
|
|
@@ -48194,7 +48354,7 @@ ${staleNote}`}`
|
|
|
48194
48354
|
path: nodePath,
|
|
48195
48355
|
signal: readString(args, "signal") ?? "",
|
|
48196
48356
|
timeout_ms: timeoutMs
|
|
48197
|
-
}, patience) : await this.handleRuntimeCommand("wait_until", {
|
|
48357
|
+
}, { tool: "runtime_wait", op, patience }) : await this.handleRuntimeCommand("wait_until", {
|
|
48198
48358
|
...whichGame(args),
|
|
48199
48359
|
path: nodePath,
|
|
48200
48360
|
property: readString(args, "property") ?? "",
|
|
@@ -48202,7 +48362,7 @@ ${staleNote}`}`
|
|
|
48202
48362
|
...readNonEmptyString(args, "says") === undefined ? {} : { says: readNonEmptyString(args, "says") },
|
|
48203
48363
|
...readBoolean(args, "includeHidden") === undefined ? {} : { include_hidden: readBoolean(args, "includeHidden") },
|
|
48204
48364
|
timeout_ms: timeoutMs
|
|
48205
|
-
}, patience);
|
|
48365
|
+
}, { tool: "runtime_wait", op, patience });
|
|
48206
48366
|
}
|
|
48207
48367
|
}
|
|
48208
48368
|
async function runGodotServer() {
|
|
@@ -48210,7 +48370,7 @@ async function runGodotServer() {
|
|
|
48210
48370
|
const server = new GodotServer;
|
|
48211
48371
|
await server.run();
|
|
48212
48372
|
}
|
|
48213
|
-
var UPDATE_NOTICE_EVERY = 500, FEEDBACK_NOTICE_EVERY = 250, run4, __dirname2,
|
|
48373
|
+
var UPDATE_NOTICE_EVERY = 500, FEEDBACK_NOTICE_EVERY = 250, run4, __dirname2, 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. 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 "';
|
|
48214
48374
|
var init_server3 = __esm(() => {
|
|
48215
48375
|
init_mcp();
|
|
48216
48376
|
init_stdio2();
|
|
@@ -34,6 +34,14 @@ func set_editor_plugin(plugin: EditorPlugin) -> void:
|
|
|
34
34
|
|
|
35
35
|
|
|
36
36
|
func _process(_delta: float) -> void:
|
|
37
|
+
_count_a_play_begun()
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
## Counts a play the editor has begun since the last look, whoever began it. Looked at every frame
|
|
41
|
+
## and before the count is read: read alone, a play begun in this frame went uncounted until this
|
|
42
|
+
## node's turn came round, and a server asking before then was told the earlier play's number with
|
|
43
|
+
## "playing", which it took for the earlier play still going.
|
|
44
|
+
func _count_a_play_begun() -> void:
|
|
37
45
|
var playing: bool = EditorInterface.is_playing_scene()
|
|
38
46
|
if playing and not _was_playing:
|
|
39
47
|
if _awaited:
|
|
@@ -231,6 +239,7 @@ static func _leave() -> void:
|
|
|
231
239
|
|
|
232
240
|
|
|
233
241
|
func playing_status(_args: Dictionary) -> Dictionary:
|
|
242
|
+
_count_a_play_begun()
|
|
234
243
|
var playing: bool = EditorInterface.is_playing_scene()
|
|
235
244
|
# The debugger's port with it, read now rather than remembered: it is taken again before every
|
|
236
245
|
# play, so the one from when this editor greeted the server is a number it has moved off.
|