gdharness 1.1.60 → 1.1.62
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 +226 -10
- package/build/index.js +224 -9
- package/build/keeper.js +2 -0
- package/package.json +1 -1
package/build/cli.js
CHANGED
|
@@ -9242,11 +9242,143 @@ function bodyCallsSuper(code, line, lambda) {
|
|
|
9242
9242
|
}
|
|
9243
9243
|
return false;
|
|
9244
9244
|
}
|
|
9245
|
-
function
|
|
9245
|
+
function parametersOf(text, from) {
|
|
9246
|
+
const open = text.indexOf("(", from);
|
|
9247
|
+
if (open === -1) {
|
|
9248
|
+
return [];
|
|
9249
|
+
}
|
|
9250
|
+
const names = [];
|
|
9251
|
+
let depth = 0;
|
|
9252
|
+
let start = open + 1;
|
|
9253
|
+
for (let at = open;at < text.length; at += 1) {
|
|
9254
|
+
const here = text[at];
|
|
9255
|
+
if (here === "(" || here === "[" || here === "{") {
|
|
9256
|
+
depth += 1;
|
|
9257
|
+
} else if (here === ")" || here === "]" || here === "}") {
|
|
9258
|
+
depth -= 1;
|
|
9259
|
+
}
|
|
9260
|
+
if (here === "," && depth === 1 || depth === 0) {
|
|
9261
|
+
const named = new RegExp(`^\\s*(${NAME})`, "u").exec(text.slice(start, at));
|
|
9262
|
+
if (named?.[1] !== undefined) {
|
|
9263
|
+
names.push(named[1]);
|
|
9264
|
+
}
|
|
9265
|
+
start = at + 1;
|
|
9266
|
+
if (depth === 0) {
|
|
9267
|
+
break;
|
|
9268
|
+
}
|
|
9269
|
+
}
|
|
9270
|
+
}
|
|
9271
|
+
return names;
|
|
9272
|
+
}
|
|
9273
|
+
function declaredClass(text, name) {
|
|
9274
|
+
const word = `(?<![\\p{L}\\p{N}_.])${escapedForPattern(name)}`;
|
|
9275
|
+
const found = new RegExp(`${word}\\s*:\\s*(${DOTTED})`, "u").exec(text)?.[1] ?? new RegExp(`${word}\\s*:=\\s*(${DOTTED})\\.new\\s*\\(`, "u").exec(text)?.[1] ?? new RegExp(`${word}\\s*:?=[^\\n]*?\\bas\\s+(${DOTTED})\\s*$`, "u").exec(text)?.[1];
|
|
9276
|
+
return found === undefined ? null : found.slice(found.lastIndexOf(".") + 1);
|
|
9277
|
+
}
|
|
9278
|
+
function localAt(lines, index, name) {
|
|
9279
|
+
const word = escapedForPattern(name);
|
|
9280
|
+
const declares = new RegExp(`(?<![\\p{L}\\p{N}_.])(?:var|const)\\s+${word}(?![\\p{L}\\p{N}_])`, "u");
|
|
9281
|
+
const iterates = new RegExp(`^\\s*for\\s+${word}(?![\\p{L}\\p{N}_])`, "u");
|
|
9282
|
+
const local = (text) => ({ type: declaredClass(text, name) });
|
|
9283
|
+
const own = lines[index] ?? "";
|
|
9284
|
+
const headerOnOwnLine = FUNC_LINE.exec(own) ?? LAMBDA_HEADER.exec(own);
|
|
9285
|
+
if (headerOnOwnLine !== null && parametersOf(own, headerOnOwnLine.index).includes(name)) {
|
|
9286
|
+
return local(own);
|
|
9287
|
+
}
|
|
9288
|
+
if (FUNC_LINE.test(own)) {
|
|
9289
|
+
return null;
|
|
9290
|
+
}
|
|
9291
|
+
let depth = indentOf(own);
|
|
9292
|
+
for (let at = index - 1;at >= 0; at -= 1) {
|
|
9293
|
+
const text = lines[at] ?? "";
|
|
9294
|
+
if (text.trim() === "") {
|
|
9295
|
+
continue;
|
|
9296
|
+
}
|
|
9297
|
+
const indent = indentOf(text);
|
|
9298
|
+
if (indent > depth) {
|
|
9299
|
+
continue;
|
|
9300
|
+
}
|
|
9301
|
+
const named = FUNC_LINE.exec(text);
|
|
9302
|
+
if (indent < depth) {
|
|
9303
|
+
depth = indent;
|
|
9304
|
+
if (named !== null) {
|
|
9305
|
+
return parametersOf(text, named.index).includes(name) ? local(text) : null;
|
|
9306
|
+
}
|
|
9307
|
+
const lambda = LAMBDA_HEADER.exec(text);
|
|
9308
|
+
if (lambda !== null && parametersOf(text, lambda.index).includes(name) || iterates.test(text)) {
|
|
9309
|
+
return local(text);
|
|
9310
|
+
}
|
|
9311
|
+
}
|
|
9312
|
+
if (named !== null || depth === 0) {
|
|
9313
|
+
return null;
|
|
9314
|
+
}
|
|
9315
|
+
if (declares.test(text)) {
|
|
9316
|
+
return local(text);
|
|
9317
|
+
}
|
|
9318
|
+
}
|
|
9319
|
+
return null;
|
|
9320
|
+
}
|
|
9321
|
+
function receiverBefore(code, offset) {
|
|
9322
|
+
let at = offset - 1;
|
|
9323
|
+
while (at >= 0 && /\s/.test(code[at] ?? "")) {
|
|
9324
|
+
at -= 1;
|
|
9325
|
+
}
|
|
9326
|
+
if (code[at] !== ".") {
|
|
9327
|
+
return null;
|
|
9328
|
+
}
|
|
9329
|
+
at -= 1;
|
|
9330
|
+
while (at >= 0 && /\s/.test(code[at] ?? "")) {
|
|
9331
|
+
at -= 1;
|
|
9332
|
+
}
|
|
9333
|
+
let start = at + 1;
|
|
9334
|
+
while (start > 0 && isIdentifierPart(code[start - 1])) {
|
|
9335
|
+
start -= 1;
|
|
9336
|
+
}
|
|
9337
|
+
if (start > at) {
|
|
9338
|
+
return null;
|
|
9339
|
+
}
|
|
9340
|
+
let before = start - 1;
|
|
9341
|
+
while (before >= 0 && /\s/.test(code[before] ?? "")) {
|
|
9342
|
+
before -= 1;
|
|
9343
|
+
}
|
|
9344
|
+
return { name: code.slice(start, at + 1), dotted: code[before] === "." };
|
|
9345
|
+
}
|
|
9346
|
+
function bodyHolding(body, offset) {
|
|
9347
|
+
const inner = body.inner.find((one) => one.start <= offset && offset <= one.end);
|
|
9348
|
+
return inner === undefined ? body : bodyHolding(inner, offset);
|
|
9349
|
+
}
|
|
9350
|
+
function callSitesOf(source, name, otherClasses = new Set) {
|
|
9246
9351
|
const regions = regionsOf(source);
|
|
9247
9352
|
const code = codeOf(source, regions);
|
|
9353
|
+
const codeLines = code.split(`
|
|
9354
|
+
`);
|
|
9248
9355
|
const lines = new Lines(source);
|
|
9249
|
-
|
|
9356
|
+
const shape = shapeOf(source);
|
|
9357
|
+
const memberClass = (member, at) => {
|
|
9358
|
+
const declared = bodyHolding(shape.body, at).declarations.find((one) => one.name === member && (one.kind === "var" || one.kind === "const"));
|
|
9359
|
+
return declared === undefined ? null : declaredClass(codeLines[lines.positionOf(declared.offset).line] ?? "", member);
|
|
9360
|
+
};
|
|
9361
|
+
return occurrencesOf(source, name, regions).filter((one) => one.kind === "code" && one.declaredAs === null).filter((one) => {
|
|
9362
|
+
const index = lines.positionOf(one.offset).line;
|
|
9363
|
+
if (one.afterDot) {
|
|
9364
|
+
const receiver = receiverBefore(code, one.offset);
|
|
9365
|
+
if (receiver === null || otherClasses.size === 0) {
|
|
9366
|
+
return true;
|
|
9367
|
+
}
|
|
9368
|
+
if (otherClasses.has(receiver.name)) {
|
|
9369
|
+
return false;
|
|
9370
|
+
}
|
|
9371
|
+
if (receiver.dotted) {
|
|
9372
|
+
return true;
|
|
9373
|
+
}
|
|
9374
|
+
const typed = localAt(codeLines, index, receiver.name)?.type ?? memberClass(receiver.name, one.offset);
|
|
9375
|
+
return typed === null || !otherClasses.has(typed);
|
|
9376
|
+
}
|
|
9377
|
+
if (localAt(codeLines, index, name) !== null) {
|
|
9378
|
+
return false;
|
|
9379
|
+
}
|
|
9380
|
+
return !bodyHolding(shape.body, one.offset).declarations.some((declared) => declared.name === name && MEMBER_KINDS.has(declared.kind));
|
|
9381
|
+
}).map((one) => {
|
|
9250
9382
|
const line = lines.positionOf(one.offset).line + 1;
|
|
9251
9383
|
const after = code.slice(one.offset + name.length).trimStart();
|
|
9252
9384
|
return { line, within: enclosingFunction(source, line, code), called: after.startsWith("(") };
|
|
@@ -9267,7 +9399,7 @@ function collectEnumValues(text, from, into) {
|
|
|
9267
9399
|
function isIdentifier(name) {
|
|
9268
9400
|
return new RegExp(`^${NAME}$`, "u").test(name) && !KEYWORDS.has(name);
|
|
9269
9401
|
}
|
|
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;
|
|
9402
|
+
var IDENTIFIER_START, IDENTIFIER_PART, DECLARING, NAME = "[\\p{L}_][\\p{L}\\p{N}_]*", ANNOTATIONS = "(?:@[\\p{L}_][\\p{L}\\p{N}_]*(?:\\([^)]*\\))?\\s+)*", DECLARATION_LINE, CLASS_NAME_LINE, EXTENDS_LINE, EXTENDS_CLAUSE, FUNC_LINE, SUPER_CALL, LAMBDA_HEADER, DOTTED, MEMBER_KINDS, KEYWORDS;
|
|
9271
9403
|
var init_gdscript_source = __esm(() => {
|
|
9272
9404
|
IDENTIFIER_START = /[\p{L}_]/u;
|
|
9273
9405
|
IDENTIFIER_PART = /[\p{L}\p{N}_]/u;
|
|
@@ -9279,6 +9411,8 @@ var init_gdscript_source = __esm(() => {
|
|
|
9279
9411
|
FUNC_LINE = new RegExp(`^[ \\t]*${ANNOTATIONS}(?:static\\s+)?func\\s+(${NAME})\\s*\\(`, "u");
|
|
9280
9412
|
SUPER_CALL = /(?<![\p{L}\p{N}_.])super\s*[.(]/u;
|
|
9281
9413
|
LAMBDA_HEADER = new RegExp(`(?<![\\p{L}\\p{N}_])func(?:\\s+${NAME})?\\s*\\(`, "u");
|
|
9414
|
+
DOTTED = `${NAME}(?:\\.${NAME})*`;
|
|
9415
|
+
MEMBER_KINDS = new Set(["var", "const", "signal", "enum", "enumValue"]);
|
|
9282
9416
|
KEYWORDS = new Set([
|
|
9283
9417
|
"and",
|
|
9284
9418
|
"as",
|
|
@@ -18243,7 +18377,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
18243
18377
|
callersOf: {
|
|
18244
18378
|
type: "string",
|
|
18245
18379
|
ops: ["profile"],
|
|
18246
|
-
description: "profile: also answer, under callers, where the project's scripts name this function in code: its name, or Class.name for the functions of one class. Each place has its script, line, the named function it is written in (within), whether the function is called there or passed on as a Callable (called), and the profile's calls, self and total milliseconds for the function it is in, those taking the most time first, forty at most. Found by name in the source, since the engine's profiler records nothing about who made a call, so a method of the same name on another class is listed too and a call made through a string is not."
|
|
18380
|
+
description: "profile: also answer, under callers, where the project's scripts name this function in code: its name, or Class.name for the functions of one class. Each place has its script, line, the named function it is written in (within), whether the function is called there or passed on as a Callable (called), and the profile's calls, self and total milliseconds for the function it is in, those taking the most time first, forty at most. Found by name in the source, since the engine's profiler records nothing about who made a call, so a method of the same name on another class is listed too, except, when Class.name was asked, one called through another class of the project by name or through a parameter, local or class variable declared as one, and a call made through a string is not. A parameter, local or class variable of the same name is not the function and is not listed."
|
|
18247
18381
|
},
|
|
18248
18382
|
limit: {
|
|
18249
18383
|
type: "integer",
|
|
@@ -18335,7 +18469,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
18335
18469
|
},
|
|
18336
18470
|
{
|
|
18337
18471
|
name: "editor_status",
|
|
18338
|
-
description: "Whether the editor addon is connected, which Godot answers, whether the editor is playing something, and whether a game with the runtime addon is reachable: runtimeConnected counts the games of the project this server serves, and runtimes lists every game on the machine with ofThisProject saying which is which, since a game of another project is reached only by naming its projectPath. playingInEditor is null when the editor was not asked or did not answer, which is not the same as a no: with no editor connected there is nobody to ask, and an editor that does not serve the question is given a moment and then left. A game can be playing while this is null, so read it as unanswered rather than as nothing playing, and use processActive and runtimes, which come from elsewhere and do not depend on the editor answering. game.run names the run processActive is about, the one editor_output and editor_run stop answer for: its pid, its project, and adopted true when this server read it back from another server rather than starting it; null when there is
|
|
18472
|
+
description: "Whether the editor addon is connected, which Godot answers, whether the editor is playing something, and whether a game with the runtime addon is reachable: runtimeConnected counts the games of the project this server serves, and runtimes lists every game on the machine with ofThisProject saying which is which, since a game of another project is reached only by naming its projectPath. playingInEditor is null when the editor was not asked or did not answer, which is not the same as a no: with no editor connected there is nobody to ask, and an editor that does not serve the question is given a moment and then left. A game can be playing while this is null, so read it as unanswered rather than as nothing playing, and use processActive and runtimes, which come from elsewhere and do not depend on the editor answering. game.run names the run processActive is about, the one editor_output and editor_run stop answer for: its pid, its project, and adopted true when this server read it back from another server rather than starting it. It stays named after the run ends, beside processActive false, so editor_output can still read what that run printed; null when there is no run at all. editor.lspListening and editor.dapListening say whether anything accepts connections on the ports the editor names, lspPort and dapPort: Godot binds each once, as the editor starts, and reports a failure only in the log panel of the editor itself, so a false comes with serversNote, and editor_launch restart binds it again. editor.scan says whether the editor is scanning or importing and how many milliseconds ago its last scan finished (finishedMsAgo), or is null when it did not answer: the editor rewrites the class cache just after a scan, and editor_run waits that out before starting a game, saying so under waitedForEditorScanMs. Each game under runtimes is pinged with a short wait, so one held at a breakpoint is listed as unreachable rather than making this call wait out the runtime timeout; for the run this server is reporting on, heldAt says whether that is what it is, with the same three answers editor_output gives. A game the editor played announces the editor under editorPid, and so does whatever that game started for itself; a game some other server started announces none, which is how the two are told apart when both are running from one project. Each game also says which runtime addon it loaded, under addonVersion (null when it cannot say); addonIsStale is true when that code is not what this server ships, and staleNote says whether the project needs gdharness upgrade, the game a restart, or this server a reconnect. A second checkout of a project, such as a git worktree, carries addons of its own and is where this shows. Every runtime_* answer from such a game carries addonIsStale and staleNote too. When connected is false, mayYetConnect says whether that is final: the editor dials this server rather than the other way round and backs off between tries, so for the first half-minute after a server starts, false means \"not reached yet\" as often as it means \"no editor\". True is worth waiting out; false is an editor that is not there. False beside restartInterrupted is one that an editor_launch restart on a previous server asked to quit, where the launch half never ran because that server was ended in between, usually by the reconnect the restart was taken for; editor_launch open finishes it. True beside restartUnderway is a restart still in progress, its server alive and within the wait for the old editor to go: the editor is coming, and opening another would put two on one project. listeningSince is when this server took the bridge port, which is what that window is measured from and what a caller wanting its own window should read. An editor this server opened counts for as long as its process lives, however far past that window the import runs, and awaitingLaunchedEditor names its pid so the two reasons for true are told apart. Any editor of this project running and not yet connected, whoever opened it, makes it true as well and is named under editorsNotYetConnected with its pid and start time, so a restart a reconnect interrupted after its launch is not read as nothing being there. settingsDropped appears here once when the editor this server opened saved project.godot on its way in, which is the same reading editor_launch restart makes in its own answer. A game that has announced itself in a protocol this server does not speak is listed under unreadable rather than left out, with the protocol it speaks and whether the server or the addon is the older half. This happens during an upgrade that moves the runtime protocol, which most do not: installing replaces the addon on disk as soon as the pin moves, while a server that is already running stays on the version it started with, so the two speak different protocols until the server is reconnected. Those games are real and running, but no tool here can reach them until the halves match, so runtimes stays empty and runtimeConnected stays false. An upgrade that leaves the protocol alone opens no such window and this list stays empty through it. addonIsStale is decided by the code of the editor addons rather than their version, so an editor started before an upgrade that left that code alone reports the older addonVersion, is not stale, and addonNote says so: it needs no restart. port is the one the editor bridge is on; when that is not the one this server was configured with, portWanted names the configured one and portNote says why it moved and, when it was the server this one replaced, which pid kept it. A server replacing another of its project that holds the configured port waits for it to stand down and takes the port over, which takes a few seconds; bridgeHandover says so while it waits, and it moves only if that server has not let go within the wait. An editor that has connected and not yet greeted, which lasts a frame and longer while it imports, reports nothing about itself: greeting says so, addonIsStale is absent rather than guessed, and a restart asked then waits for the greeting to learn who opened it. startedByAnEditor is an editor another editor started, by Godot's own restart or the project manager, which has none of the arguments a server gave the one before it; startedByAnEditorNote says what that costs. An engine run that loads the editor addon to do one job and quit, such as a gate's godot --headless --import, an export or a script, is not taken as the editor: a process that greets without a server's mark has its command line read first, and while that is read identifying is true and connected false; one started with such an option is turned away, and turnedAway names the last one, its pid and the option. Every other call made while an editor is arriving, before its greeting or while it is looked up, waits up to ten seconds for it to settle rather than acting on connected as it reads in that window.",
|
|
18339
18473
|
parameters: {},
|
|
18340
18474
|
requires: []
|
|
18341
18475
|
},
|
|
@@ -40311,6 +40445,42 @@ var init_outside = __esm(() => {
|
|
|
40311
40445
|
KEEPER_SCRIPT = fileURLToPath4(new URL("./keeper.js", import.meta.url));
|
|
40312
40446
|
});
|
|
40313
40447
|
|
|
40448
|
+
// src/port-probe.ts
|
|
40449
|
+
import { connect as connect3 } from "node:net";
|
|
40450
|
+
function acceptsConnections(port, withinMs = 2000) {
|
|
40451
|
+
return new Promise((resolve) => {
|
|
40452
|
+
const socket = connect3({ host: "127.0.0.1", port });
|
|
40453
|
+
const settle = (answer) => {
|
|
40454
|
+
socket.destroy();
|
|
40455
|
+
resolve(answer);
|
|
40456
|
+
};
|
|
40457
|
+
socket.setTimeout(withinMs, () => {
|
|
40458
|
+
settle(null);
|
|
40459
|
+
});
|
|
40460
|
+
socket.once("connect", () => {
|
|
40461
|
+
settle(true);
|
|
40462
|
+
});
|
|
40463
|
+
socket.once("error", () => {
|
|
40464
|
+
settle(false);
|
|
40465
|
+
});
|
|
40466
|
+
});
|
|
40467
|
+
}
|
|
40468
|
+
function notListeningNote(servers, greetedAgoMs) {
|
|
40469
|
+
const down = [
|
|
40470
|
+
...servers.lsp.listening === false ? [{ name: "language server", port: servers.lsp.port }] : [],
|
|
40471
|
+
...servers.dap.listening === false ? [{ name: "debug adapter", port: servers.dap.port }] : []
|
|
40472
|
+
];
|
|
40473
|
+
if (down.length === 0) {
|
|
40474
|
+
return;
|
|
40475
|
+
}
|
|
40476
|
+
const named = down.map((one) => `its ${one.name} on port ${one.port}`).join(" and ");
|
|
40477
|
+
const needs = down.map((one) => one.name === "debug adapter" ? "played runs and the debug tools need the debug adapter" : "diagnostics, symbols and renames need the language server").join(", and ");
|
|
40478
|
+
const starting = greetedAgoMs !== null && greetedAgoMs < STARTING_MS ? ` The editor greeted ${Math.round(greetedAgoMs / 1000)}s ago, so ${down.length === 1 ? "it" : "they"} may still be starting: ask again in a few seconds.` : "";
|
|
40479
|
+
return `The editor names ${named}, and nothing accepts connections there. Godot binds each once, as the editor starts, and reports a failure only in the editor's own log panel; ${needs}. editor_launch restart binds ${down.length === 1 ? "it" : "them"} again.${starting}`;
|
|
40480
|
+
}
|
|
40481
|
+
var STARTING_MS = 20000;
|
|
40482
|
+
var init_port_probe = () => {};
|
|
40483
|
+
|
|
40314
40484
|
// src/process-time.ts
|
|
40315
40485
|
import { execFile as execFile3 } from "node:child_process";
|
|
40316
40486
|
import { promisify as promisify3 } from "node:util";
|
|
@@ -42430,15 +42600,31 @@ function placedLambda(row, sourceOf) {
|
|
|
42430
42600
|
const within = source === null ? null : enclosingFunction(source, row.line);
|
|
42431
42601
|
return within === null ? row : { ...row, within };
|
|
42432
42602
|
}
|
|
42603
|
+
function classNamesOf(source) {
|
|
42604
|
+
const shape = shapeOf(source);
|
|
42605
|
+
const inner = (body) => body.inner.flatMap((one) => [...one.name === null ? [] : [one.name], ...inner(one)]);
|
|
42606
|
+
return [...shape.className === null ? [] : [shape.className.name], ...inner(shape.body)];
|
|
42607
|
+
}
|
|
42433
42608
|
function callersOf(totals, name, scripts) {
|
|
42434
42609
|
const listed = [...scripts];
|
|
42435
42610
|
const sources = new Map(listed);
|
|
42436
42611
|
const rows = rowsOf(totals, (script) => sources.get(script) ?? null);
|
|
42437
42612
|
const bare = bareName(name);
|
|
42438
42613
|
const target = rows.filter((row) => name.includes(".") ? row.function === name : bareName(row.function) === bare);
|
|
42614
|
+
const asked = name.includes(".") ? name.slice(0, name.lastIndexOf(".")) : null;
|
|
42615
|
+
const otherClasses = new Set;
|
|
42616
|
+
if (asked !== null) {
|
|
42617
|
+
for (const [, source] of listed) {
|
|
42618
|
+
for (const declared of classNamesOf(source)) {
|
|
42619
|
+
if (declared !== asked) {
|
|
42620
|
+
otherClasses.add(declared);
|
|
42621
|
+
}
|
|
42622
|
+
}
|
|
42623
|
+
}
|
|
42624
|
+
}
|
|
42439
42625
|
const sites = [];
|
|
42440
42626
|
for (const [script, source] of listed) {
|
|
42441
|
-
for (const site of callSitesOf(source, bare)) {
|
|
42627
|
+
for (const site of callSitesOf(source, bare, otherClasses)) {
|
|
42442
42628
|
const within = site.within === null ? undefined : rows.find((row) => row.script === script && bareName(row.function) === site.within);
|
|
42443
42629
|
sites.push({
|
|
42444
42630
|
script,
|
|
@@ -42548,7 +42734,7 @@ function longerWaitNote(tool, op, waitedMs) {
|
|
|
42548
42734
|
if (spec !== undefined && argumentsOf(spec, op).includes("timeoutMs")) {
|
|
42549
42735
|
return "timeoutMs waits longer next time.";
|
|
42550
42736
|
}
|
|
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
|
|
42737
|
+
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: a game drawing frames more slowly than that is waited on in fewer frames at a time, and a game inside one long frame, such as a long _ready, answers no call however few frames are asked for` : "the wait is GDHARNESS_RUNTIME_TIMEOUT_MS, which is set in the environment the server is started with";
|
|
42552
42738
|
return `${tool} ${op} takes no timeoutMs: it was given ${waitedMs}ms, and ${how}.`;
|
|
42553
42739
|
}
|
|
42554
42740
|
function projectSpelling(projectPath, absolutePath) {
|
|
@@ -45737,15 +45923,17 @@ class GodotServer {
|
|
|
45737
45923
|
hold: current === null ? {} : await this.holdOf(current, active)
|
|
45738
45924
|
};
|
|
45739
45925
|
})();
|
|
45740
|
-
const [games, { playing, active, run, hold }, scan, editorStatus] = await Promise.all([
|
|
45926
|
+
const [games, { playing, active, run, hold }, scan, editorStatus, servers] = await Promise.all([
|
|
45741
45927
|
pinged,
|
|
45742
45928
|
asked,
|
|
45743
45929
|
this.editorScanState(),
|
|
45744
|
-
this.getEditorStatusPayload()
|
|
45930
|
+
this.getEditorStatusPayload(),
|
|
45931
|
+
this.editorServersListening()
|
|
45745
45932
|
]);
|
|
45746
45933
|
return this.jsonTextResponse({
|
|
45747
45934
|
editor: {
|
|
45748
45935
|
...editorStatus,
|
|
45936
|
+
...servers,
|
|
45749
45937
|
debugPort: playing?.debugPort ?? this.godotBridge.getStatus().debugPort,
|
|
45750
45938
|
scan
|
|
45751
45939
|
},
|
|
@@ -45771,6 +45959,30 @@ class GodotServer {
|
|
|
45771
45959
|
}
|
|
45772
45960
|
});
|
|
45773
45961
|
}
|
|
45962
|
+
async editorServersListening() {
|
|
45963
|
+
const status = this.godotBridge.getStatus();
|
|
45964
|
+
const { lspPort, dapPort } = status;
|
|
45965
|
+
if (!status.connected || lspPort === undefined || dapPort === undefined) {
|
|
45966
|
+
return {};
|
|
45967
|
+
}
|
|
45968
|
+
const [lsp, dap] = await Promise.all([acceptsConnections(lspPort), acceptsConnections(dapPort)]);
|
|
45969
|
+
const greetedAgoMs = status.connectedAt === undefined ? null : Date.now() - status.connectedAt.getTime();
|
|
45970
|
+
return {
|
|
45971
|
+
...lsp === null ? {} : { lspListening: lsp },
|
|
45972
|
+
...dap === null ? {} : { dapListening: dap },
|
|
45973
|
+
serversNote: notListeningNote({ lsp: { port: lspPort, listening: lsp }, dap: { port: dapPort, listening: dap } }, greetedAgoMs)
|
|
45974
|
+
};
|
|
45975
|
+
}
|
|
45976
|
+
async editorServersOnceStarted() {
|
|
45977
|
+
const deadline = Date.now() + EDITOR_SERVERS_START_MS;
|
|
45978
|
+
for (;; ) {
|
|
45979
|
+
const answer = await this.editorServersListening();
|
|
45980
|
+
if (answer.serversNote === undefined || Date.now() >= deadline) {
|
|
45981
|
+
return answer;
|
|
45982
|
+
}
|
|
45983
|
+
await delay4(500);
|
|
45984
|
+
}
|
|
45985
|
+
}
|
|
45774
45986
|
settingKeysOf(projectPath) {
|
|
45775
45987
|
if (projectPath === undefined) {
|
|
45776
45988
|
return null;
|
|
@@ -45814,6 +46026,7 @@ class GodotServer {
|
|
|
45814
46026
|
"editor_status says whether anything has reached the bridge since"
|
|
45815
46027
|
]);
|
|
45816
46028
|
}
|
|
46029
|
+
const servers = await this.editorServersOnceStarted();
|
|
45817
46030
|
const now = this.godotBridge.getStatus();
|
|
45818
46031
|
const after = this.settingKeysOf(before.projectPath);
|
|
45819
46032
|
return this.jsonTextResponse({
|
|
@@ -45824,6 +46037,7 @@ class GodotServer {
|
|
|
45824
46037
|
addonIsStale: editorIsStale(now.addonVersion, SERVER_VERSION, now.addonDigest, shippedEditorDigest()),
|
|
45825
46038
|
staleNote: addonMismatch(now.addonVersion, SERVER_VERSION, now.addonDigest, shippedEditorDigest(), this.editorCodeOnDisk()),
|
|
45826
46039
|
...this.whatTheEditorDropped(settingsBefore, after),
|
|
46040
|
+
...servers,
|
|
45827
46041
|
tookMs: Date.now() - began
|
|
45828
46042
|
});
|
|
45829
46043
|
}
|
|
@@ -46220,6 +46434,7 @@ class GodotServer {
|
|
|
46220
46434
|
} catch (error) {
|
|
46221
46435
|
return this.createErrorResponse(`The editor is connected but its debug adapter is not: ${errorMessage(error)}`, [
|
|
46222
46436
|
`This asked on port ${this.dap().port}, which is where the connected editor says it serves`,
|
|
46437
|
+
"Godot binds its debug adapter once, as the editor starts, and reports a failure only in the editor's own log panel: editor_launch restart binds it again",
|
|
46223
46438
|
"GDHARNESS_DAP_PORT points this server at another one"
|
|
46224
46439
|
]);
|
|
46225
46440
|
}
|
|
@@ -48370,7 +48585,7 @@ async function runGodotServer() {
|
|
|
48370
48585
|
const server = new GodotServer;
|
|
48371
48586
|
await server.run();
|
|
48372
48587
|
}
|
|
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 "';
|
|
48588
|
+
var UPDATE_NOTICE_EVERY = 500, FEEDBACK_NOTICE_EVERY = 250, run4, __dirname2, EDITOR_SERVERS_START_MS = 20000, HOLD_PING_MS = 2000, SLOWEST_FRAME_RATE = 20, PLAY_STARTS_WITHIN_MS = 30000, KEEPER_ANSWER_MS = 15000, EXIT_REPORTED_WITHIN_MS = 2000, CONSOLE_WRAPPER, LONGEST_BOOT_NOTED_MS, MOST_GROUPS = 20, VALUE_NAMES, BRIDGE_RETRY_MS = 2000, SUCCESSOR_CHECK_MS = 1e4, CONSOLE_WATCH_MS = 250, BOUNDARY_WAIT_MS = 2000, HANDOVER_MS, HANDOVER_POLL_MS = 250, CACHE_WRITE_MS = 2000, STREAMS_GRACE_MS = 2000, PLAYING_STATUS_MS = 1000, SCAN_START_MS = 2000, SCAN_SETTLED_MS = 500, REIMPORT_TIMEOUT_MS = 300000, AUDIO_BUS_OPS, STALLED_AFTER_MS = 30000, TEST_HEARTBEAT_MS = 20000, DEPENDED_FAILED = "Failed to compile depended scripts", WAIT_FOR_RUN_MS = 600000, RUN_POLL_MS = 250, PATH_SOLUTIONS, PROJECT_FILE_ARGUMENTS, DEBUG_STATE_CALLS, PROJECT_INFO_SECTIONS2, RUNNER_DIRECTORY = "addons/gdUnit4/", PROFILE_FUNCTIONS = 30, PROFILER_ON_MS = 3000, CALLERS_FOUND_BY_NAME = "Found by name in the project's scripts, since the engine's profiler counts calls and time per function and records nothing about who made them: a method of the same name on another class is listed too, unless one class was asked about and it is called through another class by name or through a variable declared as one. Each place carries the profile's numbers for the function it is written in.", MOST_ENGINE_ENTRIES = 200, ERROR_ASSERT = "GdUnitGodotErrorAssertImpl.gd", EXTERNAL_MEMBER = 'Could not resolve external class member "';
|
|
48374
48589
|
var init_server3 = __esm(() => {
|
|
48375
48590
|
init_mcp();
|
|
48376
48591
|
init_stdio2();
|
|
@@ -48402,6 +48617,7 @@ var init_server3 = __esm(() => {
|
|
|
48402
48617
|
init_lsp_client();
|
|
48403
48618
|
init_outside();
|
|
48404
48619
|
init_paths();
|
|
48620
|
+
init_port_probe();
|
|
48405
48621
|
init_ports();
|
|
48406
48622
|
init_process_children();
|
|
48407
48623
|
init_process_time();
|
package/build/index.js
CHANGED
|
@@ -26613,11 +26613,145 @@ function bodyCallsSuper(code, line, lambda) {
|
|
|
26613
26613
|
}
|
|
26614
26614
|
return false;
|
|
26615
26615
|
}
|
|
26616
|
-
function
|
|
26616
|
+
function parametersOf(text, from) {
|
|
26617
|
+
const open = text.indexOf("(", from);
|
|
26618
|
+
if (open === -1) {
|
|
26619
|
+
return [];
|
|
26620
|
+
}
|
|
26621
|
+
const names = [];
|
|
26622
|
+
let depth = 0;
|
|
26623
|
+
let start = open + 1;
|
|
26624
|
+
for (let at = open;at < text.length; at += 1) {
|
|
26625
|
+
const here = text[at];
|
|
26626
|
+
if (here === "(" || here === "[" || here === "{") {
|
|
26627
|
+
depth += 1;
|
|
26628
|
+
} else if (here === ")" || here === "]" || here === "}") {
|
|
26629
|
+
depth -= 1;
|
|
26630
|
+
}
|
|
26631
|
+
if (here === "," && depth === 1 || depth === 0) {
|
|
26632
|
+
const named = new RegExp(`^\\s*(${NAME})`, "u").exec(text.slice(start, at));
|
|
26633
|
+
if (named?.[1] !== undefined) {
|
|
26634
|
+
names.push(named[1]);
|
|
26635
|
+
}
|
|
26636
|
+
start = at + 1;
|
|
26637
|
+
if (depth === 0) {
|
|
26638
|
+
break;
|
|
26639
|
+
}
|
|
26640
|
+
}
|
|
26641
|
+
}
|
|
26642
|
+
return names;
|
|
26643
|
+
}
|
|
26644
|
+
var DOTTED = `${NAME}(?:\\.${NAME})*`;
|
|
26645
|
+
function declaredClass(text, name) {
|
|
26646
|
+
const word = `(?<![\\p{L}\\p{N}_.])${escapedForPattern(name)}`;
|
|
26647
|
+
const found = new RegExp(`${word}\\s*:\\s*(${DOTTED})`, "u").exec(text)?.[1] ?? new RegExp(`${word}\\s*:=\\s*(${DOTTED})\\.new\\s*\\(`, "u").exec(text)?.[1] ?? new RegExp(`${word}\\s*:?=[^\\n]*?\\bas\\s+(${DOTTED})\\s*$`, "u").exec(text)?.[1];
|
|
26648
|
+
return found === undefined ? null : found.slice(found.lastIndexOf(".") + 1);
|
|
26649
|
+
}
|
|
26650
|
+
function localAt(lines, index, name) {
|
|
26651
|
+
const word = escapedForPattern(name);
|
|
26652
|
+
const declares = new RegExp(`(?<![\\p{L}\\p{N}_.])(?:var|const)\\s+${word}(?![\\p{L}\\p{N}_])`, "u");
|
|
26653
|
+
const iterates = new RegExp(`^\\s*for\\s+${word}(?![\\p{L}\\p{N}_])`, "u");
|
|
26654
|
+
const local = (text) => ({ type: declaredClass(text, name) });
|
|
26655
|
+
const own = lines[index] ?? "";
|
|
26656
|
+
const headerOnOwnLine = FUNC_LINE.exec(own) ?? LAMBDA_HEADER.exec(own);
|
|
26657
|
+
if (headerOnOwnLine !== null && parametersOf(own, headerOnOwnLine.index).includes(name)) {
|
|
26658
|
+
return local(own);
|
|
26659
|
+
}
|
|
26660
|
+
if (FUNC_LINE.test(own)) {
|
|
26661
|
+
return null;
|
|
26662
|
+
}
|
|
26663
|
+
let depth = indentOf(own);
|
|
26664
|
+
for (let at = index - 1;at >= 0; at -= 1) {
|
|
26665
|
+
const text = lines[at] ?? "";
|
|
26666
|
+
if (text.trim() === "") {
|
|
26667
|
+
continue;
|
|
26668
|
+
}
|
|
26669
|
+
const indent = indentOf(text);
|
|
26670
|
+
if (indent > depth) {
|
|
26671
|
+
continue;
|
|
26672
|
+
}
|
|
26673
|
+
const named = FUNC_LINE.exec(text);
|
|
26674
|
+
if (indent < depth) {
|
|
26675
|
+
depth = indent;
|
|
26676
|
+
if (named !== null) {
|
|
26677
|
+
return parametersOf(text, named.index).includes(name) ? local(text) : null;
|
|
26678
|
+
}
|
|
26679
|
+
const lambda = LAMBDA_HEADER.exec(text);
|
|
26680
|
+
if (lambda !== null && parametersOf(text, lambda.index).includes(name) || iterates.test(text)) {
|
|
26681
|
+
return local(text);
|
|
26682
|
+
}
|
|
26683
|
+
}
|
|
26684
|
+
if (named !== null || depth === 0) {
|
|
26685
|
+
return null;
|
|
26686
|
+
}
|
|
26687
|
+
if (declares.test(text)) {
|
|
26688
|
+
return local(text);
|
|
26689
|
+
}
|
|
26690
|
+
}
|
|
26691
|
+
return null;
|
|
26692
|
+
}
|
|
26693
|
+
function receiverBefore(code, offset) {
|
|
26694
|
+
let at = offset - 1;
|
|
26695
|
+
while (at >= 0 && /\s/.test(code[at] ?? "")) {
|
|
26696
|
+
at -= 1;
|
|
26697
|
+
}
|
|
26698
|
+
if (code[at] !== ".") {
|
|
26699
|
+
return null;
|
|
26700
|
+
}
|
|
26701
|
+
at -= 1;
|
|
26702
|
+
while (at >= 0 && /\s/.test(code[at] ?? "")) {
|
|
26703
|
+
at -= 1;
|
|
26704
|
+
}
|
|
26705
|
+
let start = at + 1;
|
|
26706
|
+
while (start > 0 && isIdentifierPart(code[start - 1])) {
|
|
26707
|
+
start -= 1;
|
|
26708
|
+
}
|
|
26709
|
+
if (start > at) {
|
|
26710
|
+
return null;
|
|
26711
|
+
}
|
|
26712
|
+
let before = start - 1;
|
|
26713
|
+
while (before >= 0 && /\s/.test(code[before] ?? "")) {
|
|
26714
|
+
before -= 1;
|
|
26715
|
+
}
|
|
26716
|
+
return { name: code.slice(start, at + 1), dotted: code[before] === "." };
|
|
26717
|
+
}
|
|
26718
|
+
var MEMBER_KINDS = new Set(["var", "const", "signal", "enum", "enumValue"]);
|
|
26719
|
+
function bodyHolding(body, offset) {
|
|
26720
|
+
const inner = body.inner.find((one) => one.start <= offset && offset <= one.end);
|
|
26721
|
+
return inner === undefined ? body : bodyHolding(inner, offset);
|
|
26722
|
+
}
|
|
26723
|
+
function callSitesOf(source, name, otherClasses = new Set) {
|
|
26617
26724
|
const regions = regionsOf(source);
|
|
26618
26725
|
const code = codeOf(source, regions);
|
|
26726
|
+
const codeLines = code.split(`
|
|
26727
|
+
`);
|
|
26619
26728
|
const lines = new Lines(source);
|
|
26620
|
-
|
|
26729
|
+
const shape = shapeOf(source);
|
|
26730
|
+
const memberClass = (member, at) => {
|
|
26731
|
+
const declared = bodyHolding(shape.body, at).declarations.find((one) => one.name === member && (one.kind === "var" || one.kind === "const"));
|
|
26732
|
+
return declared === undefined ? null : declaredClass(codeLines[lines.positionOf(declared.offset).line] ?? "", member);
|
|
26733
|
+
};
|
|
26734
|
+
return occurrencesOf(source, name, regions).filter((one) => one.kind === "code" && one.declaredAs === null).filter((one) => {
|
|
26735
|
+
const index = lines.positionOf(one.offset).line;
|
|
26736
|
+
if (one.afterDot) {
|
|
26737
|
+
const receiver = receiverBefore(code, one.offset);
|
|
26738
|
+
if (receiver === null || otherClasses.size === 0) {
|
|
26739
|
+
return true;
|
|
26740
|
+
}
|
|
26741
|
+
if (otherClasses.has(receiver.name)) {
|
|
26742
|
+
return false;
|
|
26743
|
+
}
|
|
26744
|
+
if (receiver.dotted) {
|
|
26745
|
+
return true;
|
|
26746
|
+
}
|
|
26747
|
+
const typed = localAt(codeLines, index, receiver.name)?.type ?? memberClass(receiver.name, one.offset);
|
|
26748
|
+
return typed === null || !otherClasses.has(typed);
|
|
26749
|
+
}
|
|
26750
|
+
if (localAt(codeLines, index, name) !== null) {
|
|
26751
|
+
return false;
|
|
26752
|
+
}
|
|
26753
|
+
return !bodyHolding(shape.body, one.offset).declarations.some((declared) => declared.name === name && MEMBER_KINDS.has(declared.kind));
|
|
26754
|
+
}).map((one) => {
|
|
26621
26755
|
const line = lines.positionOf(one.offset).line + 1;
|
|
26622
26756
|
const after = code.slice(one.offset + name.length).trimStart();
|
|
26623
26757
|
return { line, within: enclosingFunction(source, line, code), called: after.startsWith("(") };
|
|
@@ -30874,6 +31008,41 @@ async function launchOutsideTheTree(spec, runtime = process.execPath, keeper = K
|
|
|
30874
31008
|
});
|
|
30875
31009
|
}
|
|
30876
31010
|
|
|
31011
|
+
// src/port-probe.ts
|
|
31012
|
+
import { connect as connect2 } from "node:net";
|
|
31013
|
+
function acceptsConnections(port, withinMs = 2000) {
|
|
31014
|
+
return new Promise((resolve) => {
|
|
31015
|
+
const socket = connect2({ host: "127.0.0.1", port });
|
|
31016
|
+
const settle = (answer) => {
|
|
31017
|
+
socket.destroy();
|
|
31018
|
+
resolve(answer);
|
|
31019
|
+
};
|
|
31020
|
+
socket.setTimeout(withinMs, () => {
|
|
31021
|
+
settle(null);
|
|
31022
|
+
});
|
|
31023
|
+
socket.once("connect", () => {
|
|
31024
|
+
settle(true);
|
|
31025
|
+
});
|
|
31026
|
+
socket.once("error", () => {
|
|
31027
|
+
settle(false);
|
|
31028
|
+
});
|
|
31029
|
+
});
|
|
31030
|
+
}
|
|
31031
|
+
var STARTING_MS = 20000;
|
|
31032
|
+
function notListeningNote(servers, greetedAgoMs) {
|
|
31033
|
+
const down = [
|
|
31034
|
+
...servers.lsp.listening === false ? [{ name: "language server", port: servers.lsp.port }] : [],
|
|
31035
|
+
...servers.dap.listening === false ? [{ name: "debug adapter", port: servers.dap.port }] : []
|
|
31036
|
+
];
|
|
31037
|
+
if (down.length === 0) {
|
|
31038
|
+
return;
|
|
31039
|
+
}
|
|
31040
|
+
const named = down.map((one) => `its ${one.name} on port ${one.port}`).join(" and ");
|
|
31041
|
+
const needs = down.map((one) => one.name === "debug adapter" ? "played runs and the debug tools need the debug adapter" : "diagnostics, symbols and renames need the language server").join(", and ");
|
|
31042
|
+
const starting = greetedAgoMs !== null && greetedAgoMs < STARTING_MS ? ` The editor greeted ${Math.round(greetedAgoMs / 1000)}s ago, so ${down.length === 1 ? "it" : "they"} may still be starting: ask again in a few seconds.` : "";
|
|
31043
|
+
return `The editor names ${named}, and nothing accepts connections there. Godot binds each once, as the editor starts, and reports a failure only in the editor's own log panel; ${needs}. editor_launch restart binds ${down.length === 1 ? "it" : "them"} again.${starting}`;
|
|
31044
|
+
}
|
|
31045
|
+
|
|
30877
31046
|
// src/process-time.ts
|
|
30878
31047
|
import { execFile as execFile3 } from "node:child_process";
|
|
30879
31048
|
import { promisify as promisify3 } from "node:util";
|
|
@@ -33228,6 +33397,11 @@ function placedLambda(row, sourceOf) {
|
|
|
33228
33397
|
const within = source === null ? null : enclosingFunction(source, row.line);
|
|
33229
33398
|
return within === null ? row : { ...row, within };
|
|
33230
33399
|
}
|
|
33400
|
+
function classNamesOf(source) {
|
|
33401
|
+
const shape = shapeOf(source);
|
|
33402
|
+
const inner = (body) => body.inner.flatMap((one) => [...one.name === null ? [] : [one.name], ...inner(one)]);
|
|
33403
|
+
return [...shape.className === null ? [] : [shape.className.name], ...inner(shape.body)];
|
|
33404
|
+
}
|
|
33231
33405
|
var CALL_SITES = 40;
|
|
33232
33406
|
function callersOf(totals, name, scripts) {
|
|
33233
33407
|
const listed = [...scripts];
|
|
@@ -33235,9 +33409,20 @@ function callersOf(totals, name, scripts) {
|
|
|
33235
33409
|
const rows = rowsOf(totals, (script) => sources.get(script) ?? null);
|
|
33236
33410
|
const bare = bareName(name);
|
|
33237
33411
|
const target = rows.filter((row) => name.includes(".") ? row.function === name : bareName(row.function) === bare);
|
|
33412
|
+
const asked = name.includes(".") ? name.slice(0, name.lastIndexOf(".")) : null;
|
|
33413
|
+
const otherClasses = new Set;
|
|
33414
|
+
if (asked !== null) {
|
|
33415
|
+
for (const [, source] of listed) {
|
|
33416
|
+
for (const declared of classNamesOf(source)) {
|
|
33417
|
+
if (declared !== asked) {
|
|
33418
|
+
otherClasses.add(declared);
|
|
33419
|
+
}
|
|
33420
|
+
}
|
|
33421
|
+
}
|
|
33422
|
+
}
|
|
33238
33423
|
const sites = [];
|
|
33239
33424
|
for (const [script, source] of listed) {
|
|
33240
|
-
for (const site of callSitesOf(source, bare)) {
|
|
33425
|
+
for (const site of callSitesOf(source, bare, otherClasses)) {
|
|
33241
33426
|
const within = site.within === null ? undefined : rows.find((row) => row.script === script && bareName(row.function) === site.within);
|
|
33242
33427
|
sites.push({
|
|
33243
33428
|
script,
|
|
@@ -34242,7 +34427,7 @@ var TOOL_SPECS = [
|
|
|
34242
34427
|
callersOf: {
|
|
34243
34428
|
type: "string",
|
|
34244
34429
|
ops: ["profile"],
|
|
34245
|
-
description: "profile: also answer, under callers, where the project's scripts name this function in code: its name, or Class.name for the functions of one class. Each place has its script, line, the named function it is written in (within), whether the function is called there or passed on as a Callable (called), and the profile's calls, self and total milliseconds for the function it is in, those taking the most time first, forty at most. Found by name in the source, since the engine's profiler records nothing about who made a call, so a method of the same name on another class is listed too and a call made through a string is not."
|
|
34430
|
+
description: "profile: also answer, under callers, where the project's scripts name this function in code: its name, or Class.name for the functions of one class. Each place has its script, line, the named function it is written in (within), whether the function is called there or passed on as a Callable (called), and the profile's calls, self and total milliseconds for the function it is in, those taking the most time first, forty at most. Found by name in the source, since the engine's profiler records nothing about who made a call, so a method of the same name on another class is listed too, except, when Class.name was asked, one called through another class of the project by name or through a parameter, local or class variable declared as one, and a call made through a string is not. A parameter, local or class variable of the same name is not the function and is not listed."
|
|
34246
34431
|
},
|
|
34247
34432
|
limit: {
|
|
34248
34433
|
type: "integer",
|
|
@@ -34334,7 +34519,7 @@ var TOOL_SPECS = [
|
|
|
34334
34519
|
},
|
|
34335
34520
|
{
|
|
34336
34521
|
name: "editor_status",
|
|
34337
|
-
description: "Whether the editor addon is connected, which Godot answers, whether the editor is playing something, and whether a game with the runtime addon is reachable: runtimeConnected counts the games of the project this server serves, and runtimes lists every game on the machine with ofThisProject saying which is which, since a game of another project is reached only by naming its projectPath. playingInEditor is null when the editor was not asked or did not answer, which is not the same as a no: with no editor connected there is nobody to ask, and an editor that does not serve the question is given a moment and then left. A game can be playing while this is null, so read it as unanswered rather than as nothing playing, and use processActive and runtimes, which come from elsewhere and do not depend on the editor answering. game.run names the run processActive is about, the one editor_output and editor_run stop answer for: its pid, its project, and adopted true when this server read it back from another server rather than starting it; null when there is
|
|
34522
|
+
description: "Whether the editor addon is connected, which Godot answers, whether the editor is playing something, and whether a game with the runtime addon is reachable: runtimeConnected counts the games of the project this server serves, and runtimes lists every game on the machine with ofThisProject saying which is which, since a game of another project is reached only by naming its projectPath. playingInEditor is null when the editor was not asked or did not answer, which is not the same as a no: with no editor connected there is nobody to ask, and an editor that does not serve the question is given a moment and then left. A game can be playing while this is null, so read it as unanswered rather than as nothing playing, and use processActive and runtimes, which come from elsewhere and do not depend on the editor answering. game.run names the run processActive is about, the one editor_output and editor_run stop answer for: its pid, its project, and adopted true when this server read it back from another server rather than starting it. It stays named after the run ends, beside processActive false, so editor_output can still read what that run printed; null when there is no run at all. editor.lspListening and editor.dapListening say whether anything accepts connections on the ports the editor names, lspPort and dapPort: Godot binds each once, as the editor starts, and reports a failure only in the log panel of the editor itself, so a false comes with serversNote, and editor_launch restart binds it again. editor.scan says whether the editor is scanning or importing and how many milliseconds ago its last scan finished (finishedMsAgo), or is null when it did not answer: the editor rewrites the class cache just after a scan, and editor_run waits that out before starting a game, saying so under waitedForEditorScanMs. Each game under runtimes is pinged with a short wait, so one held at a breakpoint is listed as unreachable rather than making this call wait out the runtime timeout; for the run this server is reporting on, heldAt says whether that is what it is, with the same three answers editor_output gives. A game the editor played announces the editor under editorPid, and so does whatever that game started for itself; a game some other server started announces none, which is how the two are told apart when both are running from one project. Each game also says which runtime addon it loaded, under addonVersion (null when it cannot say); addonIsStale is true when that code is not what this server ships, and staleNote says whether the project needs gdharness upgrade, the game a restart, or this server a reconnect. A second checkout of a project, such as a git worktree, carries addons of its own and is where this shows. Every runtime_* answer from such a game carries addonIsStale and staleNote too. When connected is false, mayYetConnect says whether that is final: the editor dials this server rather than the other way round and backs off between tries, so for the first half-minute after a server starts, false means \"not reached yet\" as often as it means \"no editor\". True is worth waiting out; false is an editor that is not there. False beside restartInterrupted is one that an editor_launch restart on a previous server asked to quit, where the launch half never ran because that server was ended in between, usually by the reconnect the restart was taken for; editor_launch open finishes it. True beside restartUnderway is a restart still in progress, its server alive and within the wait for the old editor to go: the editor is coming, and opening another would put two on one project. listeningSince is when this server took the bridge port, which is what that window is measured from and what a caller wanting its own window should read. An editor this server opened counts for as long as its process lives, however far past that window the import runs, and awaitingLaunchedEditor names its pid so the two reasons for true are told apart. Any editor of this project running and not yet connected, whoever opened it, makes it true as well and is named under editorsNotYetConnected with its pid and start time, so a restart a reconnect interrupted after its launch is not read as nothing being there. settingsDropped appears here once when the editor this server opened saved project.godot on its way in, which is the same reading editor_launch restart makes in its own answer. A game that has announced itself in a protocol this server does not speak is listed under unreadable rather than left out, with the protocol it speaks and whether the server or the addon is the older half. This happens during an upgrade that moves the runtime protocol, which most do not: installing replaces the addon on disk as soon as the pin moves, while a server that is already running stays on the version it started with, so the two speak different protocols until the server is reconnected. Those games are real and running, but no tool here can reach them until the halves match, so runtimes stays empty and runtimeConnected stays false. An upgrade that leaves the protocol alone opens no such window and this list stays empty through it. addonIsStale is decided by the code of the editor addons rather than their version, so an editor started before an upgrade that left that code alone reports the older addonVersion, is not stale, and addonNote says so: it needs no restart. port is the one the editor bridge is on; when that is not the one this server was configured with, portWanted names the configured one and portNote says why it moved and, when it was the server this one replaced, which pid kept it. A server replacing another of its project that holds the configured port waits for it to stand down and takes the port over, which takes a few seconds; bridgeHandover says so while it waits, and it moves only if that server has not let go within the wait. An editor that has connected and not yet greeted, which lasts a frame and longer while it imports, reports nothing about itself: greeting says so, addonIsStale is absent rather than guessed, and a restart asked then waits for the greeting to learn who opened it. startedByAnEditor is an editor another editor started, by Godot's own restart or the project manager, which has none of the arguments a server gave the one before it; startedByAnEditorNote says what that costs. An engine run that loads the editor addon to do one job and quit, such as a gate's godot --headless --import, an export or a script, is not taken as the editor: a process that greets without a server's mark has its command line read first, and while that is read identifying is true and connected false; one started with such an option is turned away, and turnedAway names the last one, its pid and the option. Every other call made while an editor is arriving, before its greeting or while it is looked up, waits up to ten seconds for it to settle rather than acting on connected as it reads in that window.",
|
|
34338
34523
|
parameters: {},
|
|
34339
34524
|
requires: []
|
|
34340
34525
|
},
|
|
@@ -34909,6 +35094,7 @@ var __dirname2 = dirname15(fileURLToPath4(import.meta.url));
|
|
|
34909
35094
|
function editorRestartTimeoutMs() {
|
|
34910
35095
|
return waitFromEnv("GDHARNESS_EDITOR_RESTART_TIMEOUT_MS", 90000);
|
|
34911
35096
|
}
|
|
35097
|
+
var EDITOR_SERVERS_START_MS = 20000;
|
|
34912
35098
|
function waitFromEnv(variable, fallback) {
|
|
34913
35099
|
const override = Number.parseInt(envValue(variable) ?? "", 10);
|
|
34914
35100
|
return Number.isInteger(override) && override > 0 ? override : fallback;
|
|
@@ -34972,7 +35158,7 @@ function longerWaitNote(tool, op, waitedMs) {
|
|
|
34972
35158
|
if (spec !== undefined && argumentsOf(spec, op).includes("timeoutMs")) {
|
|
34973
35159
|
return "timeoutMs waits longer next time.";
|
|
34974
35160
|
}
|
|
34975
|
-
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
|
|
35161
|
+
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: a game drawing frames more slowly than that is waited on in fewer frames at a time, and a game inside one long frame, such as a long _ready, answers no call however few frames are asked for` : "the wait is GDHARNESS_RUNTIME_TIMEOUT_MS, which is set in the environment the server is started with";
|
|
34976
35162
|
return `${tool} ${op} takes no timeoutMs: it was given ${waitedMs}ms, and ${how}.`;
|
|
34977
35163
|
}
|
|
34978
35164
|
function projectSpelling(projectPath, absolutePath) {
|
|
@@ -35692,7 +35878,7 @@ function playedCap(held) {
|
|
|
35692
35878
|
totalCapped: readBoolean(held, "totalCapped") === true
|
|
35693
35879
|
};
|
|
35694
35880
|
}
|
|
35695
|
-
var CALLERS_FOUND_BY_NAME = "Found by name in the project's scripts, since the engine's profiler counts calls and time per function and records nothing about who made them: a method of the same name on another class is listed too. Each place carries the profile's numbers for the function it is written in.";
|
|
35881
|
+
var CALLERS_FOUND_BY_NAME = "Found by name in the project's scripts, since the engine's profiler counts calls and time per function and records nothing about who made them: a method of the same name on another class is listed too, unless one class was asked about and it is called through another class by name or through a variable declared as one. Each place carries the profile's numbers for the function it is written in.";
|
|
35696
35882
|
function projectSourceOf(projectPath) {
|
|
35697
35883
|
const read = new Map;
|
|
35698
35884
|
return (script) => {
|
|
@@ -38231,15 +38417,17 @@ class GodotServer {
|
|
|
38231
38417
|
hold: current === null ? {} : await this.holdOf(current, active)
|
|
38232
38418
|
};
|
|
38233
38419
|
})();
|
|
38234
|
-
const [games, { playing, active, run, hold }, scan, editorStatus] = await Promise.all([
|
|
38420
|
+
const [games, { playing, active, run, hold }, scan, editorStatus, servers] = await Promise.all([
|
|
38235
38421
|
pinged,
|
|
38236
38422
|
asked,
|
|
38237
38423
|
this.editorScanState(),
|
|
38238
|
-
this.getEditorStatusPayload()
|
|
38424
|
+
this.getEditorStatusPayload(),
|
|
38425
|
+
this.editorServersListening()
|
|
38239
38426
|
]);
|
|
38240
38427
|
return this.jsonTextResponse({
|
|
38241
38428
|
editor: {
|
|
38242
38429
|
...editorStatus,
|
|
38430
|
+
...servers,
|
|
38243
38431
|
debugPort: playing?.debugPort ?? this.godotBridge.getStatus().debugPort,
|
|
38244
38432
|
scan
|
|
38245
38433
|
},
|
|
@@ -38265,6 +38453,30 @@ class GodotServer {
|
|
|
38265
38453
|
}
|
|
38266
38454
|
});
|
|
38267
38455
|
}
|
|
38456
|
+
async editorServersListening() {
|
|
38457
|
+
const status = this.godotBridge.getStatus();
|
|
38458
|
+
const { lspPort, dapPort } = status;
|
|
38459
|
+
if (!status.connected || lspPort === undefined || dapPort === undefined) {
|
|
38460
|
+
return {};
|
|
38461
|
+
}
|
|
38462
|
+
const [lsp, dap] = await Promise.all([acceptsConnections(lspPort), acceptsConnections(dapPort)]);
|
|
38463
|
+
const greetedAgoMs = status.connectedAt === undefined ? null : Date.now() - status.connectedAt.getTime();
|
|
38464
|
+
return {
|
|
38465
|
+
...lsp === null ? {} : { lspListening: lsp },
|
|
38466
|
+
...dap === null ? {} : { dapListening: dap },
|
|
38467
|
+
serversNote: notListeningNote({ lsp: { port: lspPort, listening: lsp }, dap: { port: dapPort, listening: dap } }, greetedAgoMs)
|
|
38468
|
+
};
|
|
38469
|
+
}
|
|
38470
|
+
async editorServersOnceStarted() {
|
|
38471
|
+
const deadline = Date.now() + EDITOR_SERVERS_START_MS;
|
|
38472
|
+
for (;; ) {
|
|
38473
|
+
const answer = await this.editorServersListening();
|
|
38474
|
+
if (answer.serversNote === undefined || Date.now() >= deadline) {
|
|
38475
|
+
return answer;
|
|
38476
|
+
}
|
|
38477
|
+
await delay4(500);
|
|
38478
|
+
}
|
|
38479
|
+
}
|
|
38268
38480
|
settingKeysOf(projectPath) {
|
|
38269
38481
|
if (projectPath === undefined) {
|
|
38270
38482
|
return null;
|
|
@@ -38308,6 +38520,7 @@ class GodotServer {
|
|
|
38308
38520
|
"editor_status says whether anything has reached the bridge since"
|
|
38309
38521
|
]);
|
|
38310
38522
|
}
|
|
38523
|
+
const servers = await this.editorServersOnceStarted();
|
|
38311
38524
|
const now = this.godotBridge.getStatus();
|
|
38312
38525
|
const after = this.settingKeysOf(before.projectPath);
|
|
38313
38526
|
return this.jsonTextResponse({
|
|
@@ -38318,6 +38531,7 @@ class GodotServer {
|
|
|
38318
38531
|
addonIsStale: editorIsStale(now.addonVersion, SERVER_VERSION, now.addonDigest, shippedEditorDigest()),
|
|
38319
38532
|
staleNote: addonMismatch(now.addonVersion, SERVER_VERSION, now.addonDigest, shippedEditorDigest(), this.editorCodeOnDisk()),
|
|
38320
38533
|
...this.whatTheEditorDropped(settingsBefore, after),
|
|
38534
|
+
...servers,
|
|
38321
38535
|
tookMs: Date.now() - began
|
|
38322
38536
|
});
|
|
38323
38537
|
}
|
|
@@ -38714,6 +38928,7 @@ class GodotServer {
|
|
|
38714
38928
|
} catch (error) {
|
|
38715
38929
|
return this.createErrorResponse(`The editor is connected but its debug adapter is not: ${errorMessage(error)}`, [
|
|
38716
38930
|
`This asked on port ${this.dap().port}, which is where the connected editor says it serves`,
|
|
38931
|
+
"Godot binds its debug adapter once, as the editor starts, and reports a failure only in the editor's own log panel: editor_launch restart binds it again",
|
|
38717
38932
|
"GDHARNESS_DAP_PORT points this server at another one"
|
|
38718
38933
|
]);
|
|
38719
38934
|
}
|
package/build/keeper.js
CHANGED
|
@@ -551,6 +551,8 @@ var CLASS_NAME_LINE = new RegExp(`^${ANNOTATIONS}class_name\\s+(${NAME})`, "u");
|
|
|
551
551
|
var EXTENDS_LINE = new RegExp(`^${ANNOTATIONS}(?:class_name\\s+${NAME}\\s+)?extends\\s`, "u");
|
|
552
552
|
var FUNC_LINE = new RegExp(`^[ \\t]*${ANNOTATIONS}(?:static\\s+)?func\\s+(${NAME})\\s*\\(`, "u");
|
|
553
553
|
var LAMBDA_HEADER = new RegExp(`(?<![\\p{L}\\p{N}_])func(?:\\s+${NAME})?\\s*\\(`, "u");
|
|
554
|
+
var DOTTED = `${NAME}(?:\\.${NAME})*`;
|
|
555
|
+
var MEMBER_KINDS = new Set(["var", "const", "signal", "enum", "enumValue"]);
|
|
554
556
|
var KEYWORDS = new Set([
|
|
555
557
|
"and",
|
|
556
558
|
"as",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gdharness",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.62",
|
|
4
4
|
"mcpName": "io.github.Aureliolo/gdharness",
|
|
5
5
|
"description": "A harness for driving a Godot 4 project from an agent: editor addons, a runtime bridge into the running game, an MCP server in front of them, and a CLI that installs and diagnoses the Godot side.",
|
|
6
6
|
"type": "module",
|