gdharness 1.2.41 → 1.2.42
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 +84 -26
- package/build/godot/addons/gdharness_editor/animation_reads.gd +7 -4
- package/build/godot/addons/gdharness_editor/file_reads.gd +134 -10
- package/build/godot/addons/gdharness_editor/headless_check.gd +34 -4
- package/build/godot/addons/gdharness_editor/reading.gd +25 -0
- package/build/godot/addons/gdharness_editor/scene_file.gd +209 -15
- package/build/godot/addons/gdharness_editor/serialisation.gd +5 -1
- package/build/godot/addons/gdharness_editor/tools/scene_kin.gd +346 -0
- package/build/godot/addons/gdharness_editor/tools/scene_kin.gd.uid +1 -0
- package/build/godot/addons/gdharness_editor/tools/scene_paths.gd +438 -0
- package/build/godot/addons/gdharness_editor/tools/scene_paths.gd.uid +1 -0
- package/build/godot/addons/gdharness_editor/tools/scene_tools.gd +90 -48
- package/build/godot/addons/gdharness_runtime/reading.gd +25 -0
- package/build/godot/addons/gdharness_runtime/serialisation.gd +5 -1
- package/build/godot/operations/animation_reads.gd +7 -4
- package/build/godot/operations/editor_tools.gd +19 -1
- package/build/godot/operations/file_reads.gd +134 -10
- package/build/godot/operations/reading.gd +25 -0
- package/build/godot/operations/serialisation.gd +5 -1
- package/build/index.js +84 -26
- package/package.json +1 -1
package/build/index.js
CHANGED
|
@@ -25128,6 +25128,34 @@ function scriptsWithoutUid(projectPath) {
|
|
|
25128
25128
|
visit(projectPath, "");
|
|
25129
25129
|
return found.sort();
|
|
25130
25130
|
}
|
|
25131
|
+
function pathsInUidCache(projectPath) {
|
|
25132
|
+
let data;
|
|
25133
|
+
try {
|
|
25134
|
+
data = readFileSync3(join2(projectPath, ".godot", "uid_cache.bin"));
|
|
25135
|
+
} catch {
|
|
25136
|
+
return null;
|
|
25137
|
+
}
|
|
25138
|
+
if (data.length < 4) {
|
|
25139
|
+
return null;
|
|
25140
|
+
}
|
|
25141
|
+
const count = data.readUInt32LE(0);
|
|
25142
|
+
const paths = new Map;
|
|
25143
|
+
let at = 4;
|
|
25144
|
+
for (let entry = 0;entry < count; entry++) {
|
|
25145
|
+
if (at + 12 > data.length) {
|
|
25146
|
+
return null;
|
|
25147
|
+
}
|
|
25148
|
+
const id = data.readBigUInt64LE(at);
|
|
25149
|
+
const length = data.readUInt32LE(at + 8);
|
|
25150
|
+
at += 12;
|
|
25151
|
+
if (at + length > data.length) {
|
|
25152
|
+
return null;
|
|
25153
|
+
}
|
|
25154
|
+
paths.set(id, data.toString("utf8", at, at + length));
|
|
25155
|
+
at += length;
|
|
25156
|
+
}
|
|
25157
|
+
return new Set(paths.values());
|
|
25158
|
+
}
|
|
25131
25159
|
function isBinary(bytes) {
|
|
25132
25160
|
return bytes.subarray(0, 8000).includes(0);
|
|
25133
25161
|
}
|
|
@@ -29305,6 +29333,22 @@ function lateAnswer(id) {
|
|
|
29305
29333
|
}
|
|
29306
29334
|
var UNTAKEN_PER_GAME = 4;
|
|
29307
29335
|
var REDIAL_MS = 50;
|
|
29336
|
+
function notSent(game, command, timeoutMs, refusals, dialling) {
|
|
29337
|
+
if (refusals > 0) {
|
|
29338
|
+
const refused = dialling ? `it refused ${refusals === 1 ? "one connection" : `${refusals} connections`} and had not accepted the next when the wait ended, after ${timeoutMs}ms` : `it refused ${refusals === 1 ? "the connection" : `all ${refusals} connections tried`} within ${timeoutMs}ms`;
|
|
29339
|
+
return {
|
|
29340
|
+
ok: false,
|
|
29341
|
+
reason: "refused",
|
|
29342
|
+
message: `The game (${game}) is running but nothing answered on its port: ${refused}. Nothing listens there while a game is starting or shutting down, and the engine refuses connections while more are waiting for the game than the eight it queues, which several servers calling one game at once, or a long frame or a breakpoint holding it, can make it do. Nothing was sent, so nothing ran.`
|
|
29343
|
+
};
|
|
29344
|
+
}
|
|
29345
|
+
const why = dialling ? "its port had not accepted the connection, which is how Linux and macOS hold one while the game's queue is full, as it is when the game has not taken the connections before it during a long frame or at a breakpoint" : `${UNTAKEN_PER_GAME} connections from this server were already waiting for the game to take them, which it does between frames, so a long frame or a breakpoint holds them`;
|
|
29346
|
+
return {
|
|
29347
|
+
ok: false,
|
|
29348
|
+
reason: "busy",
|
|
29349
|
+
message: `'${command}' was not sent to the game (${game}) within ${timeoutMs}ms: ${why}. Nothing was sent, so nothing ran.`
|
|
29350
|
+
};
|
|
29351
|
+
}
|
|
29308
29352
|
var untaken = new Map;
|
|
29309
29353
|
function placeOf(endpoint) {
|
|
29310
29354
|
return `${dialAddress(endpoint.address)}:${endpoint.port}`;
|
|
@@ -29344,6 +29388,7 @@ function runtimeRequest(endpoint, command, params, timeoutMs, afterTheWait = "dr
|
|
|
29344
29388
|
let buffered = "";
|
|
29345
29389
|
let welcomed = false;
|
|
29346
29390
|
let socket = null;
|
|
29391
|
+
let connected = false;
|
|
29347
29392
|
let refusals = 0;
|
|
29348
29393
|
let redial = null;
|
|
29349
29394
|
let giveBackThePlace = null;
|
|
@@ -29380,16 +29425,8 @@ function runtimeRequest(endpoint, command, params, timeoutMs, afterTheWait = "dr
|
|
|
29380
29425
|
settle(reply);
|
|
29381
29426
|
};
|
|
29382
29427
|
const timer = setTimeout(() => {
|
|
29383
|
-
if (socket === null) {
|
|
29384
|
-
finish(refusals
|
|
29385
|
-
ok: false,
|
|
29386
|
-
reason: "refused",
|
|
29387
|
-
message: `The game (${describe2(endpoint)}) is running but nothing answered on its port: it refused ${refusals === 1 ? "the connection" : `all ${refusals} connections tried`} within ${timeoutMs}ms. Nothing listens there while a game is starting or shutting down, and the engine refuses connections while more are waiting for the game than the eight it queues, which several servers calling one game at once, or a long frame or a breakpoint holding it, can make it do. Nothing was sent, so nothing ran.`
|
|
29388
|
-
} : {
|
|
29389
|
-
ok: false,
|
|
29390
|
-
reason: "busy",
|
|
29391
|
-
message: `'${command}' was not sent to the game (${describe2(endpoint)}) within ${timeoutMs}ms: ${UNTAKEN_PER_GAME} connections from this server were already waiting for the game to take them, which it does between frames, so a long frame or a breakpoint holds them. Nothing was sent, so nothing ran.`
|
|
29392
|
-
});
|
|
29428
|
+
if (socket === null || !connected) {
|
|
29429
|
+
finish(notSent(describe2(endpoint), command, timeoutMs, refusals, socket !== null));
|
|
29393
29430
|
return;
|
|
29394
29431
|
}
|
|
29395
29432
|
const still = welcomed ? `The game (${describe2(endpoint)}) has the request and did not answer '${command}' within ${timeoutMs}ms.` : `The game (${describe2(endpoint)}) did not answer '${command}' within ${timeoutMs}ms and had not taken the request: its frame has not ended since it was sent, which a long frame or a breakpoint does, and the request runs when it does.`;
|
|
@@ -29436,6 +29473,7 @@ function runtimeRequest(endpoint, command, params, timeoutMs, afterTheWait = "dr
|
|
|
29436
29473
|
socket = opened;
|
|
29437
29474
|
opened.setEncoding("utf8");
|
|
29438
29475
|
opened.on("connect", () => {
|
|
29476
|
+
connected = true;
|
|
29439
29477
|
opened.write(`${JSON.stringify({ id, command, params })}
|
|
29440
29478
|
`);
|
|
29441
29479
|
});
|
|
@@ -35498,7 +35536,7 @@ var SCENE_PATH = {
|
|
|
35498
35536
|
type: "string",
|
|
35499
35537
|
description: 'Scene file inside the project, such as "scenes/main.tscn" or "res://scenes/main.tscn".'
|
|
35500
35538
|
};
|
|
35501
|
-
var WITHOUT_AN_EDITOR = "Through the editor when one serves the project; with none, a headless engine runs the editor's own tool and the answer says so under writtenBy, or readBy for a read. That engine runs every script of the file, where the editor runs only tool scripts, so what a script changes as the file loads or as a property is set is put back before anything is saved, and a file where that cannot be done, such as a scene whose script does not compile, is refused and nothing is written. An editor of the project that is running and has not reached this server is waited for rather than written around.";
|
|
35539
|
+
var WITHOUT_AN_EDITOR = "Through the editor when one serves the project; with none, a headless engine runs the editor's own tool and the answer says so under writtenBy, or readBy for a read. That engine runs every script of the file, where the editor runs only tool scripts, so what a script changes as the file loads or as a property is set is put back before anything is saved, and a file where that cannot be done, such as a scene whose script does not compile, is refused and nothing is written. Each file the written one refers to is named by its uid, as the editor names it, except a file the project's uid cache has no uid for yet (one made since the project was last scanned), which is named by path and listed under namedByPath with the call that scans it. An editor of the project that is running and has not reached this server is waited for rather than written around.";
|
|
35502
35540
|
var WHOLE_NUMBERS = 'A whole number of 2^53 or more, such as a 64-bit seed or a resource uid, is written as its digits, {"_type": "int", "value": "9007199254740993"}, the form a read answers with: a JSON number that large can arrive as a neighbour of the one sent, so sent as a number it is refused.';
|
|
35503
35541
|
var SCRIPT_PATH = {
|
|
35504
35542
|
type: "string",
|
|
@@ -35876,7 +35914,7 @@ var TOOL_SPECS = [
|
|
|
35876
35914
|
operations: {
|
|
35877
35915
|
status: {
|
|
35878
35916
|
effect: "read",
|
|
35879
|
-
summary: "which resources are outdated or failed, or one resource with resourcePath, each with its reason. Judged by content with the checks the editor makes before it reimports: a sidecar changed since its import, as setting an option without a reimport leaves it; a sidecar with no uid or written for another file; an output under .godot/imported that is gone (missing_outputs) or changed; a source whose hash differs from the one its import recorded, or with no record. Beyond the editor, a glTF scene whose import does not depend on an image it names, because it was built before that image was imported (imported_without; a scene whose import runs an import script is not judged on images). failed is an import that failed, which the editor never tries again on its own. Without resourcePath, a sidecar whose source is gone is listed as missing_source",
|
|
35917
|
+
summary: "which resources are outdated or failed, or one resource with resourcePath, each with its reason and a status the summary counts: needs_reimport (out of date, or a file the engine imports that has never been imported), failed, up_to_date, missing_source, or not_imported (a file the engine does not import at all, with why). Judged by content with the checks the editor makes before it reimports: a sidecar changed since its import, as setting an option without a reimport leaves it; a sidecar with no uid or written for another file; an output under .godot/imported that is gone (missing_outputs) or changed; a source whose hash differs from the one its import recorded, or with no record. Beyond the editor, a glTF scene whose import does not depend on an image it names, because it was built before that image was imported (imported_without; a scene whose import runs an import script is not judged on images). failed is an import that failed, which the editor never tries again on its own. Without resourcePath, a sidecar whose source is gone is listed as missing_source",
|
|
35880
35918
|
requires: []
|
|
35881
35919
|
},
|
|
35882
35920
|
options: { effect: "read", summary: "the import options of one resource", requires: ["resourcePath"] },
|
|
@@ -35897,7 +35935,7 @@ var TOOL_SPECS = [
|
|
|
35897
35935
|
},
|
|
35898
35936
|
refresh_uids: {
|
|
35899
35937
|
effect: "additive",
|
|
35900
|
-
summary: "import the project so every script and shader has its .uid sidecar, and name under uidsCreated the
|
|
35938
|
+
summary: "import the project so every script and shader has its .uid sidecar and the uid cache holds the uid of every file, scenes made with no editor among them, which a headless write names a reference by (see namedByPath), and name under uidsCreated the sidecars this made, under uidsCached the res:// files whose uid the cache holds now and did not before, and under stillWithoutUid the ones the engine would not import: it writes no scene and no script, so a project whose sidecars are all present is left untouched",
|
|
35901
35939
|
requires: []
|
|
35902
35940
|
},
|
|
35903
35941
|
refresh_classes: {
|
|
@@ -35996,7 +36034,12 @@ var TOOL_SPECS = [
|
|
|
35996
36034
|
rootNodeName: {
|
|
35997
36035
|
type: "string",
|
|
35998
36036
|
ops: ["create"],
|
|
35999
|
-
description: "create: the root node name. Default the class name, as the editor names a new root."
|
|
36037
|
+
description: "create: the root node name. Default the class name, as the editor names a new root, or with inherits the name of that scene's root."
|
|
36038
|
+
},
|
|
36039
|
+
inherits: {
|
|
36040
|
+
type: "string",
|
|
36041
|
+
ops: ["create"],
|
|
36042
|
+
description: `create: a scene for the new one to inherit, such as "parts/crate.tscn", as the editor's New Inherited Scene makes one. Its root is an instance of that scene, so changes there reach it, and scene_node set gives the nodes it inherits values of this scene's own. The root's class is that scene's, so rootNodeType is not given with it; the answer names it under rootNodeType and the scene under inherits.`
|
|
36000
36043
|
},
|
|
36001
36044
|
script: { type: "string", ops: ["create"], description: "create: a script to attach to the root." },
|
|
36002
36045
|
newPath: { type: "string", ops: ["save_as"], description: "save_as: where the copy goes." }
|
|
@@ -36017,11 +36060,14 @@ var TOOL_SPECS = [
|
|
|
36017
36060
|
name: "scene_tree",
|
|
36018
36061
|
title: "Scene tree",
|
|
36019
36062
|
effect: "read",
|
|
36020
|
-
description: "The nodes of a scene file: names, classes and hierarchy, with properties when asked. A node that instances another scene names that file under instanceOf, and the nodes listed under it come from that file rather than this one. Needs no editor: one serving the project answers and says whether it holds unsaved changes to the scene, and with none a headless engine reads the file without making its nodes, so no script of theirs runs (autoloads still start once it has answered, as in every headless call). For the nodes of a game that is running, runtime_inspect tree.",
|
|
36063
|
+
description: "The nodes of a scene file: names, classes and hierarchy, with properties when asked. A node that instances another scene names that file under instanceOf, and the nodes listed under it come from that file rather than this one; its editableChildren says whether this scene, or a scene it inherits, keeps values of its own for them (scene_node set editableChildren). Needs no editor: one serving the project answers and says whether it holds unsaved changes to the scene, and with none a headless engine reads the file without making its nodes, so no script of theirs runs (autoloads still start once it has answered, as in every headless call). For the nodes of a game that is running, runtime_inspect tree.",
|
|
36021
36064
|
parameters: {
|
|
36022
36065
|
projectPath: PROJECT_PATH2,
|
|
36023
36066
|
scenePath: SCENE_PATH,
|
|
36024
|
-
depth: {
|
|
36067
|
+
depth: {
|
|
36068
|
+
type: "number",
|
|
36069
|
+
description: "How many levels to descend. Default: all. A node at the last level that has children of its own lists none and says how many under childrenNotListed."
|
|
36070
|
+
},
|
|
36025
36071
|
includeProperties: { type: "boolean", description: "Include each node's properties. Default false." }
|
|
36026
36072
|
},
|
|
36027
36073
|
requires: ["projectPath", "scenePath"]
|
|
@@ -36029,7 +36075,7 @@ var TOOL_SPECS = [
|
|
|
36029
36075
|
{
|
|
36030
36076
|
name: "scene_node",
|
|
36031
36077
|
title: "Scene nodes",
|
|
36032
|
-
description: `One node in a scene file: add, read, set, duplicate, reparent or delete it, or paint TileMap cells. Any ClassDB node type or script class can be added, so a NavigationRegion2D, an AnimationTree or a Camera3D is an add with that nodeType and its properties, and another scene is instanced by an add with instancePath. A set with properties {"name": "Boss"} renames a node. A property holding a Resource takes the res:// path of one, so a texture, a material or a theme is a set like any other. A write is refused while the editor holds unsaved changes to the scene, and
|
|
36078
|
+
description: `One node in a scene file: add, read, set, duplicate, reparent or delete it, or paint TileMap cells. Any ClassDB node type or script class can be added, so a NavigationRegion2D, an AnimationTree or a Camera3D is an add with that nodeType and its properties, and another scene is instanced by an add with instancePath. A set with properties {"name": "Boss"} renames a node, and answers nodePath as its new path. A rename or a reparent carries the node paths naming the node, as the editor's rename does: NodePath values on nodes (in arrays, dictionaries, built-in resources, %unique paths) and the tracks its players' animations hold, saving a library kept in a .tres of its own. Each rewrite is listed under retargeted ({scenePath, nodePath, property or animation and track, from, to}), and under notRetargeted, with a reason, each left: a track kept in another scene or an imported file, or a value another scene gives that the move left naming nothing. The move is followed into every other .tscn that instances or inherits this scene at any depth: the editable children, values, added nodes and connections named by the old path are rewritten, since Godot finds a moved node's values by unique_id but editable children only by path and drops the values kept below them on the next save, and that scene's node paths and tracks are carried as above. A reparent also drops the node ids saved along each rewritten path, which ran through the old parent; the next save writes them again. The scenes rewritten are named under followed, and under notFollowed any left as they were, with why. A delete rewrites no path and lists under stillNaming each node path and track, here and in the scenes instancing or inheriting this one, that named the deleted node and now names nothing. A property holding a Resource takes the res:// path of one, so a texture, a material or a theme is a set like any other. A write is refused while the editor holds unsaved changes to the scene. A node this scene inherits takes values of this scene's own, and so does a node inside an instance of another scene once that instance's editableChildren is true; neither can be moved, renamed, copied or deleted, since the other scene makes it again on load. ${WITHOUT_AN_EDITOR}`,
|
|
36033
36079
|
parameters: {
|
|
36034
36080
|
projectPath: PROJECT_PATH2,
|
|
36035
36081
|
scenePath: SCENE_PATH,
|
|
@@ -36042,7 +36088,7 @@ var TOOL_SPECS = [
|
|
|
36042
36088
|
nodeType: {
|
|
36043
36089
|
type: "string",
|
|
36044
36090
|
ops: ["add"],
|
|
36045
|
-
description:
|
|
36091
|
+
description: `add: the node class, such as "CharacterBody2D", or a class_name a project script declares, which the node is made as with that script attached. Give this or instancePath. The answer's nodeType is the class of the node made, for an instance the class of its scene's root.`
|
|
36046
36092
|
},
|
|
36047
36093
|
instancePath: {
|
|
36048
36094
|
type: "string",
|
|
@@ -36051,6 +36097,11 @@ var TOOL_SPECS = [
|
|
|
36051
36097
|
},
|
|
36052
36098
|
nodeName: { type: "string", ops: ["add"], description: "add: the new node's name." },
|
|
36053
36099
|
properties: { ...PROPERTIES, ops: ["add", "set"] },
|
|
36100
|
+
editableChildren: {
|
|
36101
|
+
type: "boolean",
|
|
36102
|
+
ops: ["set"],
|
|
36103
|
+
description: "set: on an instance of another scene, whether its children are editable here, as the editor's Editable Children makes them. True lets scene_node set give the nodes inside that instance values of this scene's own, which no other instance shares. An instance nested inside another takes it once the one around it has editable children. False drops every value this scene keeps for them, and every node it added below one of them. The answer names the nodes whose values went under droppedValues (they stay, with the instance's own values) and the added nodes that went under droppedNodes; a node added straight under the instance stays. The answer says editableChildren as the file now holds it."
|
|
36104
|
+
},
|
|
36054
36105
|
newName: { type: "string", ops: ["duplicate"], description: "duplicate: the copy's name." },
|
|
36055
36106
|
newParentPath: {
|
|
36056
36107
|
type: "string",
|
|
@@ -36084,17 +36135,17 @@ var TOOL_SPECS = [
|
|
|
36084
36135
|
},
|
|
36085
36136
|
get: {
|
|
36086
36137
|
effect: "read",
|
|
36087
|
-
summary: "read a node's properties, and a TileMapLayer's or TileMap's cells as set_tilemap_cells takes them, the first 1000 with cellCount for all",
|
|
36138
|
+
summary: "read a node's properties, and a TileMapLayer's or TileMap's cells as set_tilemap_cells takes them, the first 1000 with cellCount for all. An instance of another scene also answers instanceOf and editableChildren, as scene_tree does",
|
|
36088
36139
|
requires: ["nodePath"]
|
|
36089
36140
|
},
|
|
36090
36141
|
set: {
|
|
36091
36142
|
effect: "destructive",
|
|
36092
|
-
summary: "set properties on a node",
|
|
36093
|
-
requires: ["nodePath"
|
|
36143
|
+
summary: "set properties on a node, or make the children of an instance editable; give either or both",
|
|
36144
|
+
requires: ["nodePath"]
|
|
36094
36145
|
},
|
|
36095
36146
|
duplicate: {
|
|
36096
36147
|
effect: "additive",
|
|
36097
|
-
summary: "copy a node and its children",
|
|
36148
|
+
summary: "copy a node and its children, beside it or under parentNodePath, answering the copy's newNodePath, parentNodePath and index",
|
|
36098
36149
|
requires: ["nodePath", "newName"]
|
|
36099
36150
|
},
|
|
36100
36151
|
reparent: {
|
|
@@ -36471,7 +36522,7 @@ var TOOL_SPECS = [
|
|
|
36471
36522
|
},
|
|
36472
36523
|
move: {
|
|
36473
36524
|
effect: "destructive",
|
|
36474
|
-
summary: "move or rename a file or a directory, and every path in the project naming it",
|
|
36525
|
+
summary: "move or rename a file or a directory, and every path in the project naming it. With no editor an import pass then rebuilds the project's uid cache, so a reference by uid leads to the new path, and uidTable says whether it ran",
|
|
36475
36526
|
requires: ["newPath"]
|
|
36476
36527
|
}
|
|
36477
36528
|
}
|
|
@@ -36709,8 +36760,8 @@ var TOOL_SPECS = [
|
|
|
36709
36760
|
name: "editor_status",
|
|
36710
36761
|
title: "Editor status",
|
|
36711
36762
|
effect: "read",
|
|
36712
|
-
description: "Whether the editor addon is connected, which Godot answers, whether the editor is playing something, and which games with the runtime addon are reachable, of this project and of every other on the machine. Read connected with mayYetConnect: the editor dials this server and backs off between tries, so for the first half-minute after a server starts false means not reached yet as often as no editor; mayYetConnect true is worth waiting out, false is final, and editorCannotConnect, restartInterrupted, restartUnderway, closedHere and editorsNotYetConnected say why. An editor that has connected and not yet greeted reports nothing about itself, and every other call made in that window waits up to ten seconds for it to settle. playingInEditor null means unanswered rather than nothing playing: processActive and runtimes do not depend on the editor answering. game.run names the run editor_output and editor_run stop answer for, and stays named after it ends. addonIsStale, on
|
|
36713
|
-
answer: "server names this server: its pid, its version, and lifecycleLog, the file every gdharness server of the user appends a line to when it starts, when its client first names itself, when it replaces another server of its project, and when it ends, with why it ended (the client closing its end as stdin:end, a signal by name, superseded with the successor's pid, an uncaught exception or rejection with its stack, or the exit code) and the calls it was answering then. The parent's command line follows on its own line a few seconds after the start. A server killed without warning writes no ended line, and on Windows every kill is that, so a started line with no ended line after it is a server something killed. " + "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. Its play says which play the editor is on: play.play counts the plays that editor has started since it opened, whoever started them, and play.editorPid is the editor, so two reads with different pairs are two plays even when both say playing. 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. startedAt says when it began, and endedAt when it ended, wherever something saw the end: a run adopted from a note another server wrote can have ended hours before this server started. 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 the gdharness upgrade command, spelled out to run in a shell, 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 editorCannotConnect is a project without the gdharness editor addon installed or enabled, where no editor can ever dial in, however young the server or whichever editor is open; its remedies say how to install or enable it. 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.",
|
|
36763
|
+
description: "Whether the editor addon is connected, which Godot answers, whether the editor is playing something, and which games with the runtime addon are reachable, of this project and of every other on the machine. Read connected with mayYetConnect: the editor dials this server and backs off between tries, so for the first half-minute after a server starts false means not reached yet as often as no editor; mayYetConnect true is worth waiting out, false is final, and editorCannotConnect, restartInterrupted, restartUnderway, closedHere and editorsNotYetConnected say why. An editor that has connected and not yet greeted reports nothing about itself, and every other call made in that window waits up to ten seconds for it to settle. playingInEditor null means unanswered rather than nothing playing: processActive and runtimes do not depend on the editor answering. game.run names the run editor_output and editor_run stop answer for, and stays named after it ends. addonIsStale, on a connected editor and on each running game (absent with neither), says it runs addon code other than what this server ships, and staleNote says which half moves: the project, by a gdharness upgrade it spells out to run in a shell, the editor by a restart, or this server by a reconnect. A false editor.lspListening or editor.dapListening comes with serversNote, and editor_launch restart binds the port again. editor.scan says whether the editor is scanning, which editor_run waits out before starting a game. A game announced in a protocol this server does not speak is listed under unreadable, during an upgrade that moves the protocol, until the halves match.",
|
|
36764
|
+
answer: "projectIs, when present, is the gdharness version of the addons in the project, which differs from this server's (project_info says which of the two to move), and configIs, when present, the version the project's MCP config starts, which differs from this server's, so the next connection runs that one. server names this server: its pid, its version, and lifecycleLog, the file every gdharness server of the user appends a line to when it starts, when its client first names itself, when it replaces another server of its project, and when it ends, with why it ended (the client closing its end as stdin:end, a signal by name, superseded with the successor's pid, an uncaught exception or rejection with its stack, or the exit code) and the calls it was answering then. The parent's command line follows on its own line a few seconds after the start. A server killed without warning writes no ended line, and on Windows every kill is that, so a started line with no ended line after it is a server something killed. " + "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. Its play says which play the editor is on: play.play counts the plays that editor has started since it opened, whoever started them, and play.editorPid is the editor, so two reads with different pairs are two plays even when both say playing. 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. startedAt says when it began, and endedAt when it ended, wherever something saw the end: a run adopted from a note another server wrote can have ended hours before this server started. 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 the gdharness upgrade command, spelled out to run in a shell, 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 editorCannotConnect is a project without the gdharness editor addon installed or enabled, where no editor can ever dial in, however young the server or whichever editor is open; its remedies say how to install or enable it. 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. serverVersion is the gdharness version of this server. bridgeAvailable says the editor bridge is listening; when it failed to start, startupError gives the error, and retryingBridge is true while this server tries again. announcedAt is the file where this server told editors of its project which port to dial. connectedAt is when the editor connected, and lastPongAt when it last answered this server's keep-alive ping. addonDigest is a hash of the editor addon's code, which addonIsStale compares with what this server ships. debugPort is the port of the editor's remote debugger, the one games it plays connect to. openedByAServer says a gdharness server started this editor, and syncsBreakpoints that it keeps its breakpoints when a debug session opens, which Godot otherwise clears. pendingRequests counts the requests sent to the editor and not yet answered, and queuedResources the files with writes waiting behind another write to the same file.",
|
|
36714
36765
|
parameters: {},
|
|
36715
36766
|
requires: []
|
|
36716
36767
|
},
|
|
@@ -38420,6 +38471,7 @@ var OUTPUT_PATH_SOLUTIONS = [
|
|
|
38420
38471
|
var PROJECT_FILE_ARGUMENTS = [
|
|
38421
38472
|
"scenePath",
|
|
38422
38473
|
"instancePath",
|
|
38474
|
+
"inherits",
|
|
38423
38475
|
"scriptPath",
|
|
38424
38476
|
"resourcePath",
|
|
38425
38477
|
"newPath",
|
|
@@ -39373,7 +39425,7 @@ class GodotServer {
|
|
|
39373
39425
|
project_addons_behind_this_server: {
|
|
39374
39426
|
server_is: SERVER_VERSION,
|
|
39375
39427
|
project_is: moved,
|
|
39376
|
-
what_to_do: `The project's gdharness addons are ${moved}, older than this ${SERVER_VERSION} server
|
|
39428
|
+
what_to_do: `The project's gdharness addons are ${moved}, older than this ${SERVER_VERSION} server. ` + "Restarting the editor would load that older addon code again, and reconnecting would start " + "this same server version again, so neither brings the two into step. Tell the " + `user, and offer to run ${gdharnessCommand(SERVER_VERSION, this.ownProject === null ? "upgrade" : `upgrade "${this.ownProject}"`)} ` + "in a shell, which replaces the addons with this server's; ask first, since it changes " + "files in their project. editor_launch restart then loads them."
|
|
39377
39429
|
}
|
|
39378
39430
|
});
|
|
39379
39431
|
}
|
|
@@ -40202,18 +40254,24 @@ class GodotServer {
|
|
|
40202
40254
|
const projectPath = project.value.path;
|
|
40203
40255
|
const scanned = await this.untilTheEditorsScanIsWritten(projectPath);
|
|
40204
40256
|
const before = scriptsWithoutUid(projectPath);
|
|
40257
|
+
const cachedBefore = pathsInUidCache(projectPath);
|
|
40205
40258
|
const imported = await runImport(engine.value, projectPath);
|
|
40206
40259
|
if (!imported.ok) {
|
|
40207
40260
|
return this.answer(withScanWait(imported, scanned));
|
|
40208
40261
|
}
|
|
40209
40262
|
const after = scriptsWithoutUid(projectPath);
|
|
40210
40263
|
const given = before.filter((script) => !after.includes(script));
|
|
40264
|
+
const cachedAfter = pathsInUidCache(projectPath);
|
|
40265
|
+
const cached = cachedAfter === null ? null : [...cachedAfter].filter((path) => cachedBefore?.has(path) !== true).sort();
|
|
40211
40266
|
return this.answer(withScanWait({
|
|
40212
40267
|
ok: true,
|
|
40213
40268
|
messages: imported.messages,
|
|
40214
40269
|
...imported.messagesOmitted === undefined ? {} : { messagesOmitted: imported.messagesOmitted },
|
|
40215
40270
|
payload: {
|
|
40216
40271
|
uidsCreated: given,
|
|
40272
|
+
...cached === null ? {
|
|
40273
|
+
uidCacheNote: "The import pass left no uid cache this server could read, .godot/uid_cache.bin."
|
|
40274
|
+
} : { uidsCached: cached },
|
|
40217
40275
|
stillWithoutUid: after,
|
|
40218
40276
|
note: uidsLeftNote(after.length),
|
|
40219
40277
|
...imported.extensionNote === undefined ? {} : { extensionNote: imported.extensionNote },
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gdharness",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.42",
|
|
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",
|