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 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: { summary: "frame time, memory, draw calls and the rest", requires: [] }
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, and timeoutMs waits longer next time. A game held at a breakpoint instead is one debug_state stack answers about.`
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 rows = rowsOf(totals);
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 scripts) {
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 ? {} : { withinCalls: within.calls, withinSelfMs: within.selfMs, withinTotalMs: within.totalMs }
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
- const override = Number.parseInt(envValue("GDHARNESS_EDITOR_RESTART_TIMEOUT_MS") ?? "", 10);
42399
- return Number.isInteger(override) && override > 0 ? override : 90000;
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
- async function untilGone(pids, withinMs = STOP_WAIT_MS) {
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} ${STOP_WAIT_MS / 1000} seconds later, so it has not stopped. editor_status says when it has gone, and editor_run stop asks again.`,
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 ${STOP_WAIT_MS / 1000} seconds later, so it is running beside this one: editor_status says when it has gone.`;
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 ${SCAN_WAIT_MS / 1000} seconds, so`;
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
- if (totals.complete) {} else if (reading.running && totals.frames === 0) {
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.${addonNote}`);
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, so what the game did after the last of them is not in it: the engine sends its totals only when the profiler is switched off, and stops sending as it exits without emptying what it has queued.${addonNote}`);
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() + GREETING_WAIT_MS);
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 >= SCAN_WAIT_MS) {
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 ${GREETING_WAIT_MS / 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"]);
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", { arm: true });
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 = STOP_WAIT_MS) {
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 ${STOP_WAIT_MS / 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}.`);
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 ${STOP_WAIT_MS / 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.`);
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 ${STOP_WAIT_MS / 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)}`
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, timeoutMs = this.runtimeTimeoutMs()) {
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, GREETING_WAIT_MS = 1e4, HOLD_PING_MS = 2000, SLOWEST_FRAME_RATE = 20, PLAY_STARTS_WITHIN_MS = 30000, STOP_WAIT_MS = 1e4, 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, SCAN_WAIT_MS = 30000, 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 "';
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.