gdharness 1.3.0 → 1.3.1
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 +14 -13
- package/build/godot/addons/gdharness_editor/animation_reads.gd +7 -19
- package/build/godot/addons/gdharness_editor/bridge_client.gd +8 -8
- package/build/godot/addons/gdharness_editor/file_reads.gd +60 -25
- package/build/godot/addons/gdharness_editor/headless_check.gd +7 -7
- package/build/godot/addons/gdharness_editor/plugin.gd +6 -6
- package/build/godot/addons/gdharness_editor/property_values.gd +120 -53
- package/build/godot/addons/gdharness_editor/reading.gd +28 -0
- package/build/godot/addons/gdharness_editor/scene_file.gd +28 -23
- package/build/godot/addons/gdharness_editor/serialisation.gd +45 -94
- package/build/godot/addons/gdharness_editor/tool_executor.gd +24 -24
- package/build/godot/addons/gdharness_editor/tools/animation_tools.gd +52 -41
- package/build/godot/addons/gdharness_editor/tools/filesystem_tools.gd +4 -4
- package/build/godot/addons/gdharness_editor/tools/play_tools.gd +2 -2
- package/build/godot/addons/gdharness_editor/tools/profile_tools.gd +14 -14
- package/build/godot/addons/gdharness_editor/tools/resource_tools.gd +48 -41
- package/build/godot/addons/gdharness_editor/tools/scene_kin.gd +17 -17
- package/build/godot/addons/gdharness_editor/tools/scene_paths.gd +14 -12
- package/build/godot/addons/gdharness_editor/tools/scene_placing.gd +7 -7
- package/build/godot/addons/gdharness_editor/tools/scene_tools.gd +81 -77
- package/build/godot/addons/gdharness_editor/tools/settings_tools.gd +4 -4
- package/build/godot/addons/gdharness_editor/value_shapes.gd +111 -0
- package/build/godot/addons/gdharness_editor/value_shapes.gd.uid +1 -0
- package/build/godot/addons/gdharness_runtime/reading.gd +28 -0
- package/build/godot/addons/gdharness_runtime/runtime_accessibility.gd +18 -18
- package/build/godot/addons/gdharness_runtime/runtime_autoload.gd +29 -29
- package/build/godot/addons/gdharness_runtime/runtime_capture.gd +21 -18
- package/build/godot/addons/gdharness_runtime/runtime_changes.gd +86 -80
- package/build/godot/addons/gdharness_runtime/runtime_input.gd +65 -64
- package/build/godot/addons/gdharness_runtime/runtime_items.gd +6 -6
- package/build/godot/addons/gdharness_runtime/runtime_menus.gd +8 -8
- package/build/godot/addons/gdharness_runtime/runtime_mouse.gd +6 -6
- package/build/godot/addons/gdharness_runtime/runtime_pad.gd +10 -10
- package/build/godot/addons/gdharness_runtime/runtime_paths.gd +7 -7
- package/build/godot/addons/gdharness_runtime/runtime_queries.gd +41 -41
- package/build/godot/addons/gdharness_runtime/runtime_says.gd +3 -3
- package/build/godot/addons/gdharness_runtime/runtime_targets.gd +33 -33
- package/build/godot/addons/gdharness_runtime/runtime_typing.gd +20 -20
- package/build/godot/addons/gdharness_runtime/runtime_values.gd +25 -24
- package/build/godot/addons/gdharness_runtime/runtime_waits.gd +56 -50
- package/build/godot/addons/gdharness_runtime/serialisation.gd +45 -94
- package/build/godot/addons/gdharness_runtime/value_shapes.gd +111 -0
- package/build/godot/addons/gdharness_runtime/value_shapes.gd.uid +1 -0
- package/build/godot/operations/animation_reads.gd +7 -19
- package/build/godot/operations/audio_buses.gd +10 -10
- package/build/godot/operations/class_cache.gd +16 -16
- package/build/godot/operations/classdb_queries.gd +12 -12
- package/build/godot/operations/dependencies.gd +33 -29
- package/build/godot/operations/editor_tools.gd +12 -12
- package/build/godot/operations/export_presets.gd +10 -8
- package/build/godot/operations/file_reads.gd +60 -25
- package/build/godot/operations/gdscript_analysis.gd +20 -20
- package/build/godot/operations/gdscript_authoring.gd +17 -15
- package/build/godot/operations/godot_operations.gd +64 -63
- package/build/godot/operations/import_pipeline.gd +20 -20
- package/build/godot/operations/input_actions.gd +17 -17
- package/build/godot/operations/logger.gd +1 -1
- package/build/godot/operations/plugins.gd +8 -8
- package/build/godot/operations/project_config.gd +24 -17
- package/build/godot/operations/project_diagnostics.gd +9 -9
- package/build/godot/operations/property_values.gd +120 -53
- package/build/godot/operations/reading.gd +28 -0
- package/build/godot/operations/rename_checks.gd +3 -3
- package/build/godot/operations/resource_files.gd +5 -5
- package/build/godot/operations/script_check.gd +7 -7
- package/build/godot/operations/serialisation.gd +45 -94
- package/build/godot/operations/value_shapes.gd +111 -0
- package/package.json +1 -1
package/build/cli.js
CHANGED
|
@@ -11912,7 +11912,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
11912
11912
|
};
|
|
11913
11913
|
PROPERTIES = {
|
|
11914
11914
|
type: "object",
|
|
11915
|
-
description: `Properties to set, keyed by Godot property name. Vectors, colours and the like may be written as {"x": 1, "y": 2} or tagged {"_type": "Vector2", "x": 1, "y": 2}, which is the form a read answers with, so a value read off one node can be written straight to another. A key may also be a colon path into one of the components of a vector, a colour, a rectangle or a transform, as GDScript names them, so one axis is written alone: "position:x", "modulate:a", "region_rect:size:y"; a component the value does not have is refused, naming the ones it has. A property holding a node takes the path to it from the node being set, such as "../Player". Each value is read back once set, and one the engine changed or refused is refused with what it holds. ${WHOLE_NUMBERS}`,
|
|
11915
|
+
description: `Properties to set, keyed by Godot property name. Vectors, colours and the like may be written as {"x": 1, "y": 2} or tagged {"_type": "Vector2", "x": 1, "y": 2}, which is the form a read answers with, so a value read off one node can be written straight to another. One carrying a field its type has not, such as a z for a Vector2, is refused naming it. A colour may also be given as its html code, "#ff8000" or "#ff800080". A key may also be a colon path into one of the components of a vector, a colour, a rectangle or a transform, as GDScript names them, so one axis is written alone: "position:x", "modulate:a", "region_rect:size:y"; a component the value does not have is refused, naming the ones it has. A property holding a node takes the path to it from the node being set, such as "../Player". Each value is read back once set, and one the engine changed or refused is refused with what it holds. ${WHOLE_NUMBERS}`,
|
|
11916
11916
|
additionalProperties: true
|
|
11917
11917
|
};
|
|
11918
11918
|
XY = {
|
|
@@ -12087,7 +12087,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
12087
12087
|
value: {
|
|
12088
12088
|
blank: true,
|
|
12089
12089
|
ops: ["set"],
|
|
12090
|
-
description: 'The value to write. Engine types may be tagged, {"_type": "Vector2", "x": 1, "y": 2}, and infinity as {"_type": "float", "value": "inf"}, the form a read answers with. "" writes an empty string.'
|
|
12090
|
+
description: 'The value to write, fitted to the type the setting is registered as, the way scene_node set fits a property. Engine types may be written as their fields, {"x": 1, "y": 2}, or tagged, {"_type": "Vector2", "x": 1, "y": 2}, and infinity as {"_type": "float", "value": "inf"}, the form a read answers with; one carrying a field its type has not is refused naming it, and a colour may also be its html code, "#ff8000". "" writes an empty string.'
|
|
12091
12091
|
},
|
|
12092
12092
|
name: { type: "string", ops: ["add_autoload", "remove_autoload"], description: "Autoload name." },
|
|
12093
12093
|
path: {
|
|
@@ -12346,7 +12346,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
12346
12346
|
name: "project_test",
|
|
12347
12347
|
title: "Run tests",
|
|
12348
12348
|
effect: "additive",
|
|
12349
|
-
description: "Runs the project's gdUnit4 tests headless and answers with every case that did not pass: where it is, and what the assertion said. Each entry under failed carries its first report as message and detail, and, when it has more than one, its reports under reports in the order the case met them: gdUnit4 goes on past a failed assertion, and a case can fail several and then stop on a runtime error, which makes its status error. Identical reports fold into one with times, at most twenty different ones are kept (reportsLeftOut counts the rest), and past about a megabyte the largest cases keep only their first message (failedWithoutDetail). failures counts the cases that failed an assertion and errors the cases that hit an error, so such a case counts in both. Suites where everything passed are counted, not listed, and an engine message keeps the frames above gdUnit4, not the twenty inside it; identical engine messages come back once under engineEntries, with times saying how often they were printed, and engineErrors counts every one. The class list is rebuilt first, so a suite written a moment ago is found, and the rebuild's answer is under classes, with the classes each addon declares counted under addedInAddons and changedInAddons rather than named: a class it puts back that the cache had after the last rebuild is named under classes.lostSinceLastRebuild, which is the editor writing its shorter list over the file on every save until restarted. On Windows and Linux the run gets a user:// of its own, away from a player's saves; on macOS the engine reads user:// off HOME, so it writes where the player keeps theirs, and savesNote says so. A run that found nothing to run is never called a pass
|
|
12349
|
+
description: "Runs the project's gdUnit4 tests headless and answers with every case that did not pass: where it is, and what the assertion said. Each entry under failed carries its first report as message and detail, and, when it has more than one, its reports under reports in the order the case met them: gdUnit4 goes on past a failed assertion, and a case can fail several and then stop on a runtime error, which makes its status error. Identical reports fold into one with times, at most twenty different ones are kept (reportsLeftOut counts the rest), and past about a megabyte the largest cases keep only their first message (failedWithoutDetail). failures counts the cases that failed an assertion and errors the cases that hit an error, so such a case counts in both. Suites where everything passed are counted, not listed, and an engine message keeps the frames above gdUnit4, not the twenty inside it; identical engine messages come back once under engineEntries, with times saying how often they were printed, and engineErrors counts every one. The class list is rebuilt first, so a suite written a moment ago is found, and the rebuild's answer is under classes, with the classes each addon declares counted under addedInAddons and changedInAddons rather than named: a class it puts back that the cache had after the last rebuild is named under classes.lostSinceLastRebuild, which is the editor writing its shorter list over the file on every save until restarted. What changed on disk since it was imported is then imported before any suite runs, and named under importedFirst. On Windows and Linux the run gets a user:// of its own, away from a player's saves; on macOS the engine reads user:// off HOME, so it writes where the player keeps theirs, and savesNote says so. A run that found nothing to run is never called a pass, and names the path it looked in. A script that does not load stops gdUnit4 before any suite runs: its errors come back in full under scriptErrors, and the scripts that failed only because one they depend on did are listed by path under failedThroughDependency. When they failed on a class whose own script has a parser error, which gdUnit4 never names, that script is checked by the engine and its error comes first under scriptErrors, with the class it declares under declares, and the errors a script has only because that class did not resolve, which the engine reads as Variant, are kept apart under followFromUnresolved. An autoload that does not compile stops the run at once, its errors listed the same way. A client sending a progress token is told as each suite finishes, and at least every twenty seconds. A run that ends without a report, at its timeout or otherwise, names the suites it finished under suitesFinished, the failing ones with their failed cases under suitesFailing, and the one it was in under suiteRunning. When a client cuts a run off, that is kept in the project and the next project_test there carries it under previousRunCut. A run where nothing failed but nodes were left in the tree comes back under warnings, with the count per suite: gdUnit4 decides its verdict on those and keeps them out of its report, so they are read off what it printed. A run whose every case passed is still not called passed when it printed a script error that no case failed on: gdUnit4 fails a case on the script errors its logger sees, and it cannot see a stack overflow, so the cases pass on whatever the faulted calls returned. Those errors come back under uncaughtScriptErrors, and under engineEntries by index with detailUnder rather than with their backtrace a second time; a backtrace that repeats, as a recursion's does, has each repeated frame or cycle of frames written once with the frames it covers and how many times. One raised inside assert_error(...).is_runtime_error is the test's own and does not count, and none count when the project turns off gdUnit4's report/godot/script_error setting. Needs gdUnit4 under addons/gdUnit4.",
|
|
12350
12350
|
answer: "time is gdUnit4's own sum of the seconds its cases took, from its report. tookMs is the whole call in milliseconds, from the class rebuild to the engine's exit, which is what a caller waits for. " + IMPORTED_FIRST,
|
|
12351
12351
|
parameters: {
|
|
12352
12352
|
projectPath: PROJECT_PATH,
|
|
@@ -12437,7 +12437,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
12437
12437
|
{
|
|
12438
12438
|
name: "scene_node",
|
|
12439
12439
|
title: "Scene nodes",
|
|
12440
|
-
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}`,
|
|
12440
|
+
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. Other properties the engine changed in step, as a Control's offsets with its size, come back under alsoChanged as {was, now}.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}`,
|
|
12441
12441
|
parameters: {
|
|
12442
12442
|
projectPath: PROJECT_PATH,
|
|
12443
12443
|
scenePath: SCENE_PATH,
|
|
@@ -12790,7 +12790,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
12790
12790
|
{
|
|
12791
12791
|
name: "resource_edit",
|
|
12792
12792
|
title: "Edit resources",
|
|
12793
|
-
description: 'Resource files: read one, create any ClassDB resource as .tres, change one, write a shader, build a TileSet, or set a Theme colour or font size. A material is a create with resourceType StandardMaterial3D, ShaderMaterial or CanvasItemMaterial. read answers with the properties the file sets, in the form modify takes, and a Theme lists its items there as "Button/colors/font_color". move takes any file of the project, a script or an image as much as a resource, or a directory, and works from the files, so it needs no editor; one serving the project is brought up to it as after a move in its own FileSystem dock, its open scenes and cached resources named for the new paths and the project settings the move rewrote taken from the file. read needs no editor either: with none serving the project, a headless engine reads the file. The ops that write a resource go through the editor when one serves the project; with none, a headless engine runs the tool the editor would and the answer says so under writtenBy. That engine runs the script of the resource, 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 the save, and a resource where that cannot be done is refused and the file left as it was.',
|
|
12793
|
+
description: 'Resource files: read one, create any ClassDB resource as .tres, change one, write a shader, build a TileSet, or set a Theme colour or font size. A material is a create with resourceType StandardMaterial3D, ShaderMaterial or CanvasItemMaterial. read answers with the properties the file sets, in the form modify takes, and a Theme lists its items there as "Button/colors/font_color". A create or modify whose properties made the engine change others, as a Gradient given more colours than offsets gives each new point an offset of 0, names each of those under alsoChanged with what it held before and holds now. move takes any file of the project, a script or an image as much as a resource, or a directory, and works from the files, so it needs no editor; one serving the project is brought up to it as after a move in its own FileSystem dock, its open scenes and cached resources named for the new paths and the project settings the move rewrote taken from the file. read needs no editor either: with none serving the project, a headless engine reads the file. The ops that write a resource go through the editor when one serves the project; with none, a headless engine runs the tool the editor would and the answer says so under writtenBy. That engine runs the script of the resource, 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 the save, and a resource where that cannot be done is refused and the file left as it was.',
|
|
12794
12794
|
parameters: {
|
|
12795
12795
|
projectPath: PROJECT_PATH,
|
|
12796
12796
|
resourcePath: {
|
|
@@ -12925,7 +12925,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
12925
12925
|
{
|
|
12926
12926
|
name: "editor_run",
|
|
12927
12927
|
title: "Run the game",
|
|
12928
|
-
description: "The run: starting the project, stopping it, or booting it once to see whether it comes up clean. start keeps it running and collecting output until it quits or is stopped, windowed where there is a display and headless where there is not, unless headless says otherwise; only runtime_capture needs the window. A run that quits on its own is kept, so a scene that prints an answer and quits is start, then editor_output until running is false. What runs is a scene: a SceneTree script is not an entry point here, so put the script on the root of a scene of its own and name that in scene. args hands the game its own flags, the ones it reads with OS.get_cmdline_user_args(), and a run carrying any is started by this server rather than by the editor. check boots it headless for a few frames, waits for it to quit, and answers with the verdict: whether it came up, and every error and warning it printed on the way. start and check both wait first for a scan the open editor is running and for the class cache it writes just after, since a game booted in between resolves no global class, as does every other engine this server starts on the project; waitedForEditorScanMs says how long, and scanNote says when a scan ran past thirty seconds and the game started during it. A start waits for the game to become something the runtime_* tools can talk to and says which it is under runtime: listening with the port it took, or why not, so the first call after a start does not have to be made twice. When it is not listening, mayYetAnnounce says whether that is final: false is a runtime that is not coming, true is a game still on its way up, which editor_status will see and runtimeWaitMs waits longer for, and the note then says what a runtime_* call naming no projectPath meets until it announces. A start this server made also answers with transcript, the file both the run's streams are written to, so a watch on it can be armed off the start rather than off a second call.",
|
|
12928
|
+
description: "The run: starting the project, stopping it, or booting it once to see whether it comes up clean. start keeps it running and collecting output until it quits or is stopped, windowed where there is a display and headless where there is not, unless headless says otherwise; only runtime_capture needs the window. A run that quits on its own is kept, so a scene that prints an answer and quits is start, then editor_output until running is false. What runs is a scene: a SceneTree script is not an entry point here, so put the script on the root of a scene of its own and name that in scene. args hands the game its own flags, the ones it reads with OS.get_cmdline_user_args(), and a run carrying any is started by this server rather than by the editor. check boots it headless for a few frames, waits for it to quit, and answers with the verdict: whether it came up, and every error and warning it printed on the way. start and check both wait first for a scan the open editor is running and for the class cache it writes just after, since a game booted in between resolves no global class, as does every other engine this server starts on the project; waitedForEditorScanMs says how long, and scanNote says when a scan ran past thirty seconds and the game started during it. Then whatever changed on disk since it was imported is imported, and named under importedFirst. A start waits for the game to become something the runtime_* tools can talk to and says which it is under runtime: listening with the port it took, or why not, so the first call after a start does not have to be made twice. When it is not listening, mayYetAnnounce says whether that is final: false is a runtime that is not coming, true is a game still on its way up, which editor_status will see and runtimeWaitMs waits longer for, and the note then says what a runtime_* call naming no projectPath meets until it announces. A start this server made also answers with transcript, the file both the run's streams are written to, so a watch on it can be armed off the start rather than off a second call.",
|
|
12929
12929
|
parameters: {
|
|
12930
12930
|
projectPath: {
|
|
12931
12931
|
...PROJECT_PATH,
|
|
@@ -13065,13 +13065,14 @@ var init_tool_definitions = __esm(() => {
|
|
|
13065
13065
|
requires: []
|
|
13066
13066
|
}
|
|
13067
13067
|
},
|
|
13068
|
-
answer: "start: through is gdharness for a game this server started itself, and editor for one the editor plays. scene is the scene the game started in, the project's main scene when none was given. refreshedClasses names the class_name declarations on disk that the engine's class cache was missing, which were added to it before the game started so the game could resolve them; it is empty when the cache was current. stop: endedPid is, for a run the editor plays, the number its game announced under, read at the stop rather than remembered, and the operating system hands a freed number to the next process, so it can equal the game before it. stopped is false, with the number under notSignalled, when that number no longer answers as the process the run was started as and nothing was signalled, the note naming which property it failed on, and false with the number under stillGoing when what was told to end was still running ten seconds later. endedThrough says how the stop ended a run that was going: close (the game was asked to close and exited, so exitCode is its own), keeper (the process that started the game ended it through the handle it holds), signal (its pid was signalled once shown to still be the run), or editor (the editor playing it was asked). A start that ends the run before it says how under endedPreviousRunThrough, with the same values, and one that refused to signal that run names the property it failed on under previousRunLeftBecause. check: hung is a run killed at timeoutMs, and errors and warnings count what it printed, which comes back under entries with repeats folded together. entries holds the errors and warnings and not the ordinary lines the run printed, which printedLinesNotShown counts; a check given --verbose holds those too, and editor_run start then editor_output reads everything. An engine crash is an error whatever it prints after: the crash handler's report is the entry, and the engine version and the native and GDScript backtraces are its detail, so a crashed run is never booted or clean. arguments is the engine command line the check ran, as a start's is. start and check: " + IMPORTED_FIRST + " An exported build carries its own imports and is not asked. profile: per function its script, the function (Class.name when the script declares a class_name), the line its body's first statement is on, for a lambda the named function it is written in (within), calls, and self and total milliseconds, with functionsCounted, scriptMs (the self time of them all) and coveredMs. The runtime addon's own functions, gdharness answering its tools inside the game, are not among them: their self time is runtimeAddonMs, over runtimeAddonFunctions functions. complete is true when the engine's own totals arrived, which it sends only when the profiler is switched off: the runtime addon does that as a game quits, so a scene that does its work in _ready and quits is covered whole; otherwise the answer is the frames summed so far and the note says what that misses. frames counts the frames of the profile that arrived, and the engine's totals also hold the work after the last of them, so once complete a function run every frame can show a call more than frames. Self time counts a call into the engine's own methods as the caller's, and a call through super too, while the overridden function's row counts it again: such a row carries selfIncludesSuper, so self times along an override chain overlap. The profiler's own cost of timing a call lands in the self time of the function called; with the runtime addon it is measured each run on an empty function, as perCallOverheadUs, and a row with at least half its selfMs in it carries overheadMs, since a small function called often costs that much less without the profiler. A function the profiler started inside is left out, since its time would be counted from the engine's start. The milliseconds are time passed while the function ran, not processor time given to it, so they grow when other processes compete for the processor: compare two profiles taken under the same load. They are also summed over every thread: a function run by WorkerThreadPool or a Thread on several threads at once counts each thread's time, the engine does not say which thread a call ran on, and a function waiting for them has the wait in its own self time, so selfMs is not time taken from frames, and scriptMs can exceed coveredMs, which the note then says. A property's get and set blocks are named Class.@name_getter and Class.@name_setter, as the engine names them, and their rows carry property, the property's name. A game the editor plays that was not started with profile: true says, in the answer, that it was profiled from the moment it was asked. A profile with mark answers marked: true; one with since answers since: \"mark\" and markSetMsAgo, and lessThanAtMark counts the functions left out for reading less than at the mark, which the engine's own totals, capped, can do when they replace the frames' sum.",
|
|
13068
|
+
answer: "start: through is gdharness for a game this server started itself, and editor for one the editor plays. scene is the scene the game started in, the project's main scene when none was given. runtime carries addonIsStale and staleNote when the game announced on a runtime addon other than this server's, which no runtime_* call is then sent to, and staleNote says what brings it into step. refreshedClasses names the class_name declarations on disk that the engine's class cache was missing, which were added to it before the game started so the game could resolve them; it is empty when the cache was current. stop: endedPid is, for a run the editor plays, the number its game announced under, read at the stop rather than remembered, and the operating system hands a freed number to the next process, so it can equal the game before it. stopped is false, with the number under notSignalled, when that number no longer answers as the process the run was started as and nothing was signalled, the note naming which property it failed on, and false with the number under stillGoing when what was told to end was still running ten seconds later. endedThrough says how the stop ended a run that was going: close (the game was asked to close and exited, so exitCode is its own), keeper (the process that started the game ended it through the handle it holds), signal (its pid was signalled once shown to still be the run), or editor (the editor playing it was asked). exitedBeforeStop is true when the run was already over when the stop came, so nothing was ended and exitCode, when anything collected it, is the one it ended with. A start that ends the run before it says how under endedPreviousRunThrough, with the same values, and one that refused to signal that run names the property it failed on under previousRunLeftBecause. check: hung is a run killed at timeoutMs, and errors and warnings count what it printed, which comes back under entries with repeats folded together. entries holds the errors and warnings and not the ordinary lines the run printed, which printedLinesNotShown counts; a check given --verbose holds those too, and editor_run start then editor_output reads everything. An engine crash is an error whatever it prints after: the crash handler's report is the entry, and the engine version and the native and GDScript backtraces are its detail, so a crashed run is never booted or clean. arguments is the engine command line the check ran, as a start's is, and durationMs runs from that engine's start to the check's answer, after its exit, so it holds no import that came first. start and check: " + IMPORTED_FIRST + " An exported build carries its own imports and is not asked. waitedForEditorScanMs and scanNote are left out when no editor scan was waited for. A silent windowed run has --audio-driver Dummy in its arguments; a headless run is silent without it, and one the editor plays with sound has no arguments of its own. profile: per function its script, the function (Class.name when the script declares a class_name), the line its body's first statement is on, for a lambda the named function it is written in (within), calls, and self and total milliseconds, with functionsCounted, scriptMs (the self time of them all) and coveredMs. The runtime addon's own functions, gdharness answering its tools inside the game, are not among them: their self time is runtimeAddonMs, over runtimeAddonFunctions functions. complete is true when the engine's own totals arrived, which it sends only when the profiler is switched off: the runtime addon does that as a game quits, so a scene that does its work in _ready and quits is covered whole; otherwise the answer is the frames summed so far and the note says what that misses. frames counts the frames of the profile that arrived, and the engine's totals also hold the work after the last of them, so once complete a function run every frame can show a call more than frames. Self time counts a call into the engine's own methods as the caller's, and a call through super too, while the overridden function's row counts it again: such a row carries selfIncludesSuper, so self times along an override chain overlap. The profiler's own cost of timing a call lands in the self time of the function called; with the runtime addon it is measured each run on an empty function, as perCallOverheadUs, and a row with at least half its selfMs in it carries overheadMs, since a small function called often costs that much less without the profiler. A function the profiler started inside is left out, since its time would be counted from the engine's start. The milliseconds are time passed while the function ran, not processor time given to it, so they grow when other processes compete for the processor: compare two profiles taken under the same load. They are also summed over every thread: a function run by WorkerThreadPool or a Thread on several threads at once counts each thread's time, the engine does not say which thread a call ran on, and a function waiting for them has the wait in its own self time, so selfMs is not time taken from frames, and scriptMs can exceed coveredMs, which the note then says. A property's get and set blocks are named Class.@name_getter and Class.@name_setter, as the engine names them, and their rows carry property, the property's name. A game the editor plays that was not started with profile: true says, in the answer, that it was profiled from the moment it was asked. A profile with mark answers marked: true; one with since answers since: \"mark\" and markSetMsAgo, and lessThanAtMark counts the functions left out for reading less than at the mark, which the engine's own totals, capped, can do when they replace the frames' sum.",
|
|
13069
13069
|
defaultOperation: "start"
|
|
13070
13070
|
},
|
|
13071
13071
|
{
|
|
13072
13072
|
name: "editor_output",
|
|
13073
13073
|
title: "Game and editor output",
|
|
13074
13074
|
description: "What the project started by editor_run has printed, as entries with a severity: the errors and warnings the engine reported, each with where it happened, and everything else as info. For a run the editor plays the reports come from the game itself, through the runtime addon, since the editor's debugger relays what a game prints and not what it reports; a played game without the runtime addon answers here with its prints alone, with errorsUnread true and no clean, since what it raised reaches nothing here. Answers with the counts and the verdict as well as the entries: clean is true when no error was reported, and warnings, counted beside it, do not make it false. heldAt is three answers: null for a run this server knows is not held, running or over, an object for one it knows is held at a breakpoint, whether the adapter said so or the runtime did by accepting a connection and never answering, and absent with heldUnknown beside it for a server that connected to the debugger after the stop and has no runtime to ask, since the adapter reports a stop only to the sessions connected when it happened. A run that has quit still answers here, with running false and its exit code, until the next one starts; a run ended by a signal has no exit code, and exitSignal names the signal instead. A run this server started also answers with transcript, the file both its streams are written to: uncapped, written while the run is going, and the thing to read or tail for a long run rather than the engine's own log, which every engine start rotates away. Read the file for output and this answer for state: endedBy says whether gdharness ended the run, and a transcript that has stopped growing is a run between prints rather than a run that is over. running is asked of the operating system whenever there is a process to ask about, which is every run this server started and every editor-played run whose game announced its runtime. For an editor-played run that never announced, there is no process id here and running is the editor's answer, which can lag: one measured elsewhere reported a game as playing for fifteen seconds after its process had been ended from outside. editor_status names that game under runtimes, which does not depend on the editor answering. op: \"editor\" answers about the editor itself instead, which is a different log and the only way anything here can read what an editor printed: its console reaches no plugin, so the addon cannot scrape it, and an editor gdharness opened is asked for a log file as it starts. That covers the whole session from the editor's first line, which is where the output worth reading usually is, since the class cache, the plugins and the language server all say what they have to say before any harness has connected. The answer carries the entries in the order they were printed and, beside them under repeated, each message shape several lines share: how many, what varied in each slot of it and how many different values stood there, the first occurrence, and the ordinary lines above the group, found by walking back past the group rather than by counting lines. An editor somebody opened by hand has no console here and is refused saying so rather than answered empty, because an editor that printed nothing and an editor nobody captured are not the same thing. startedAt is when the run began and elapsedMs how long it has been going; for a run that is over, endedAt is when it ended and elapsedMs how long it ran, and both are left out when nobody saw the end, as for a game closed in the editor or a run picked up from a note written before ends were recorded.",
|
|
13075
|
+
answer: "Each entry's source says where it was read: transcript for a line of the run's transcript file, which holds both its streams and is how every run with one is read; stdout or stderr for a line of that stream of a run with no transcript, such as an editor-played run picked up after a reconnect whose first server left no note; debugger for what the editor's debug adapter reported and the game never printed, such as the script error a game stopped on.",
|
|
13075
13076
|
parameters: {
|
|
13076
13077
|
projectPath: {
|
|
13077
13078
|
type: "string",
|
|
@@ -13349,7 +13350,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
13349
13350
|
value: {
|
|
13350
13351
|
ops: ["set"],
|
|
13351
13352
|
blank: true,
|
|
13352
|
-
description: `set: the value, fitted to the property's type. ${WHOLE_NUMBERS} A number the property would have to cut is refused rather than truncated: a fraction into an int, a number other than 0 or 1 into a bool. A vector, colour or the like may be written as its fields, {"x": 140, "y": 300}, or tagged, {"_type": "Vector2", "x": 140, "y": 300}, which is the form a read answers with. "" writes an empty string. A property typed as or holding an object takes a path naming one the game holds, the way call's args do, and holds that instance, or null, which the text null also means, to empty it. One declared as Variant or untyped takes anything, as the engine does: a value of the kind it holds is fitted to it, an int staying an int, and any other is stored as sent, a fraction into one holding an int included. A typed list or map, such as Array[int] or Dictionary[String, int], takes a JSON list or object with each element fitted to its element type, and an element that cannot become one is refused by its index or key, as is an object of another script than the one the list holds, whether or not that script has a class_name.`
|
|
13353
|
+
description: `set: the value, fitted to the property's type. ${WHOLE_NUMBERS} A number the property would have to cut is refused rather than truncated: a fraction into an int, a number other than 0 or 1 into a bool. A vector, colour or the like may be written as its fields, {"x": 140, "y": 300}, or tagged, {"_type": "Vector2", "x": 140, "y": 300}, which is the form a read answers with, and one carrying a field its type has not, such as a z for a Vector2, is refused naming it. A colour may also be given as its html code, "#ff8000" or "#ff800080". "" writes an empty string. A property typed as or holding an object takes a path naming one the game holds, the way call's args do, and holds that instance, or null, which the text null also means, to empty it. One declared as Variant or untyped takes anything, as the engine does: a value of the kind it holds is fitted to it, an int staying an int, and any other is stored as sent, a fraction into one holding an int included. A typed list or map, such as Array[int] or Dictionary[String, int], takes a JSON list or object with each element fitted to its element type, and an element that cannot become one is refused by its index or key, as is an object of another script than the one the list holds, whether or not that script has a class_name.`
|
|
13353
13354
|
},
|
|
13354
13355
|
method: {
|
|
13355
13356
|
type: "string",
|
|
@@ -13402,7 +13403,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
13402
13403
|
{
|
|
13403
13404
|
name: "runtime_capture",
|
|
13404
13405
|
title: "Capture the screen",
|
|
13405
|
-
description: "A picture of the running game: the whole screen, one region or node of it at full resolution, or one viewport, as an image. The whole screen
|
|
13406
|
+
description: "A picture of the running game: the whole screen, one region or node of it at full resolution, or one viewport, as an image. The whole screen is the game's window, with the game's own dialogs and popups laid over it where they sit, also when a project opens them as windows of their own (subwindows not embedded); one of its windows lying outside the game's window, or minimised, is not in it, and viewport captures a shown one by its path. Needs the game running with a window. A capture asked for before the game's first frame is drawn waits for that frame, since the game announces itself before it and the viewport holds nothing it drew until then.",
|
|
13406
13407
|
parameters: {
|
|
13407
13408
|
projectPath: RUNNING_PROJECT_PATH,
|
|
13408
13409
|
pid: RUNNING_PID,
|
|
@@ -49814,7 +49815,7 @@ class GodotServer {
|
|
|
49814
49815
|
if (going !== null && !running) {
|
|
49815
49816
|
this.drainTranscript(going);
|
|
49816
49817
|
}
|
|
49817
|
-
|
|
49818
|
+
const verdict = runtimeVerdict(endpoint, {
|
|
49818
49819
|
addon: true,
|
|
49819
49820
|
budgetMs,
|
|
49820
49821
|
...budget.sizedToMs === undefined ? {} : { sizedToMs: budget.sizedToMs },
|
|
@@ -49825,6 +49826,8 @@ class GodotServer {
|
|
|
49825
49826
|
...going?.throughEditor === true ? { playingSeen: going.seenPlaying === true } : {},
|
|
49826
49827
|
runtimeCameUp: going !== null && !running && going.log.all.some((entry) => entry.text.startsWith(RUNTIME_LISTENING_LINE))
|
|
49827
49828
|
});
|
|
49829
|
+
const staleNote = endpoint === null ? undefined : this.runtimeStaleNote(endpoint);
|
|
49830
|
+
return staleNote === undefined ? verdict : { ...verdict, addonIsStale: true, staleNote };
|
|
49828
49831
|
}
|
|
49829
49832
|
announceBudget(args, projectPath, scene) {
|
|
49830
49833
|
const key = scene ?? MAIN_SCENE;
|
|
@@ -52378,9 +52381,7 @@ class GodotServer {
|
|
|
52378
52381
|
}
|
|
52379
52382
|
const staleNote = this.runtimeStaleNote(choice.endpoint);
|
|
52380
52383
|
if (staleNote !== undefined) {
|
|
52381
|
-
return this.createErrorResponse(staleNote
|
|
52382
|
-
"editor_status lists the game and the runtime addon it loaded"
|
|
52383
|
-
]);
|
|
52384
|
+
return this.createErrorResponse(staleNote);
|
|
52384
52385
|
}
|
|
52385
52386
|
const screenshotDir = expectsScreenshot ? scratchDirectory("runtime-screenshot") : null;
|
|
52386
52387
|
const screenshotPath = screenshotDir ? join33(screenshotDir, "capture.png") : null;
|
|
@@ -4,9 +4,8 @@ extends RefCounted
|
|
|
4
4
|
## and by a headless engine with none connected from a bare node of the class given what the file
|
|
5
5
|
## stores, which runs no script of the scene's, so a read answers the same either way.
|
|
6
6
|
|
|
7
|
-
const
|
|
8
|
-
const
|
|
9
|
-
const Serialisation = preload("serialisation.gd")
|
|
7
|
+
const GdhFileReads = preload("file_reads.gd")
|
|
8
|
+
const GdhSerialisation = preload("serialisation.gd")
|
|
10
9
|
|
|
11
10
|
const LOOP_MODE_NAMES: Dictionary[int, String] = {
|
|
12
11
|
Animation.LOOP_NONE: "none",
|
|
@@ -56,21 +55,10 @@ static func scene_states(scene_path: String, tree_path: String, state_machine_pa
|
|
|
56
55
|
## A node of the class the file gives the node at [param node_path], holding every property the
|
|
57
56
|
## files set but its script, as [node, null] or [null, refusal]. The caller frees it.
|
|
58
57
|
static func _bare(scene_path: String, node_path: String, kind: String) -> Array:
|
|
59
|
-
var read: Dictionary =
|
|
60
|
-
if not read.
|
|
58
|
+
var read: Dictionary = GdhFileReads.scene_node_of_kind(scene_path, node_path, kind)
|
|
59
|
+
if not read.get("ok", false):
|
|
61
60
|
return [null, read]
|
|
62
|
-
var
|
|
63
|
-
if not ClassDB.class_exists(type) or not ClassDB.is_parent_class(type, kind):
|
|
64
|
-
var said: String = "%s not found at: %s" % [kind, node_path]
|
|
65
|
-
if read.is_empty():
|
|
66
|
-
var hinted: String = said
|
|
67
|
-
if FileReads.scene_node(scene_path, node_path.get_slice("/", 0)).is_empty():
|
|
68
|
-
hinted = Read.from_the_root(
|
|
69
|
-
said, node_path, str(FileReads.scene_node(scene_path, ".").get("name", ""))
|
|
70
|
-
)
|
|
71
|
-
said = hinted if hinted != said else FileReads.missing_from(said, scene_path, node_path)
|
|
72
|
-
return [null, {"ok": false, "error": said}]
|
|
73
|
-
var bare: Node = ClassDB.instantiate(type)
|
|
61
|
+
var bare: Node = ClassDB.instantiate(str(read["type"]))
|
|
74
62
|
bare.name = str(read["name"])
|
|
75
63
|
var given: Dictionary = read["properties"]
|
|
76
64
|
for property: Variant in given:
|
|
@@ -107,7 +95,7 @@ static func animations(player: AnimationPlayer, animation_name: String) -> Dicti
|
|
|
107
95
|
answer["ok"] = true
|
|
108
96
|
answer["animationName"] = animation_name
|
|
109
97
|
var tracks: Array[Dictionary] = []
|
|
110
|
-
var values:
|
|
98
|
+
var values: GdhSerialisation = GdhSerialisation.new()
|
|
111
99
|
for track: int in anim.get_track_count():
|
|
112
100
|
tracks.append(_track(anim, track, values))
|
|
113
101
|
answer["tracks"] = tracks
|
|
@@ -122,7 +110,7 @@ static func _summary(anim: Animation) -> Dictionary:
|
|
|
122
110
|
}
|
|
123
111
|
|
|
124
112
|
|
|
125
|
-
static func _track(anim: Animation, track: int, values:
|
|
113
|
+
static func _track(anim: Animation, track: int, values: GdhSerialisation) -> Dictionary:
|
|
126
114
|
var kind: int = anim.track_get_type(track)
|
|
127
115
|
var path: NodePath = anim.track_get_path(track)
|
|
128
116
|
var shaped: Dictionary = {
|
|
@@ -8,7 +8,7 @@ signal connected
|
|
|
8
8
|
signal disconnected
|
|
9
9
|
signal tool_requested(request_id: String, tool_name: String, args: Dictionary)
|
|
10
10
|
|
|
11
|
-
const
|
|
11
|
+
const GdhRead = preload("reading.gd")
|
|
12
12
|
|
|
13
13
|
const DEFAULT_URL: String = "ws://127.0.0.1:6505/godot"
|
|
14
14
|
## Written beside the addon by the install, so it names the version this copy came from.
|
|
@@ -256,15 +256,15 @@ func announced_url() -> String:
|
|
|
256
256
|
var file: FileAccess = FileAccess.open(ANNOUNCEMENT, FileAccess.READ)
|
|
257
257
|
if file == null:
|
|
258
258
|
return ""
|
|
259
|
-
var said: Variant =
|
|
259
|
+
var said: Variant = GdhRead.json_or_null(file.get_as_text())
|
|
260
260
|
file.close()
|
|
261
261
|
if not said is Dictionary:
|
|
262
262
|
return ""
|
|
263
263
|
|
|
264
264
|
var announcement: Dictionary = said
|
|
265
|
-
if
|
|
265
|
+
if GdhRead.as_int(announcement.get("protocol", 0)) != ANNOUNCE_PROTOCOL:
|
|
266
266
|
return ""
|
|
267
|
-
var port: int =
|
|
267
|
+
var port: int = GdhRead.as_int(announcement.get("port", 0))
|
|
268
268
|
if port < 1 or port > 65535:
|
|
269
269
|
return ""
|
|
270
270
|
var host: String = str(announcement.get("host", "127.0.0.1"))
|
|
@@ -382,7 +382,7 @@ func _keep_breakpoints_through_sessions() -> void:
|
|
|
382
382
|
var settings: EditorSettings = EditorInterface.get_editor_settings()
|
|
383
383
|
if settings == null or not settings.has_setting(SYNC_BREAKPOINTS_SETTING):
|
|
384
384
|
return
|
|
385
|
-
if
|
|
385
|
+
if GdhRead.as_bool(settings.get_setting(SYNC_BREAKPOINTS_SETTING)):
|
|
386
386
|
return
|
|
387
387
|
settings.set_setting(SYNC_BREAKPOINTS_SETTING, true)
|
|
388
388
|
# Stored is not the same as noticed. The adapter reads this setting again on the editor-wide
|
|
@@ -406,7 +406,7 @@ func _syncs_breakpoints() -> bool:
|
|
|
406
406
|
var settings: EditorSettings = EditorInterface.get_editor_settings()
|
|
407
407
|
if settings == null or not settings.has_setting(SYNC_BREAKPOINTS_SETTING):
|
|
408
408
|
return false
|
|
409
|
-
return
|
|
409
|
+
return GdhRead.as_bool(settings.get_setting(SYNC_BREAKPOINTS_SETTING))
|
|
410
410
|
|
|
411
411
|
|
|
412
412
|
## What the settings say this editor serves on, or 0 when there is nothing to ask.
|
|
@@ -416,7 +416,7 @@ func _serving(setting: String) -> int:
|
|
|
416
416
|
var settings: EditorSettings = EditorInterface.get_editor_settings()
|
|
417
417
|
if settings == null or not settings.has_setting(setting):
|
|
418
418
|
return 0
|
|
419
|
-
return
|
|
419
|
+
return GdhRead.as_int(settings.get_setting(setting))
|
|
420
420
|
|
|
421
421
|
|
|
422
422
|
## The version marker beside this addon, or "" when the copy was not installed by gdharness.
|
|
@@ -497,7 +497,7 @@ func _on_reconnect_timer() -> void:
|
|
|
497
497
|
|
|
498
498
|
|
|
499
499
|
func _handle_message(json_string: String) -> void:
|
|
500
|
-
var parsed: Variant =
|
|
500
|
+
var parsed: Variant = GdhRead.json_or_null(json_string)
|
|
501
501
|
if not parsed is Dictionary:
|
|
502
502
|
push_error("[gdharness] The server sent something that is not a message: %s" % json_string)
|
|
503
503
|
return
|
|
@@ -10,9 +10,9 @@ extends RefCounted
|
|
|
10
10
|
## without making a node. Loading the file still does what loading does in the game: static
|
|
11
11
|
## initialisers run, and a resource with a script is made with it.
|
|
12
12
|
|
|
13
|
-
const
|
|
14
|
-
const
|
|
15
|
-
const
|
|
13
|
+
const GdhPropertyValues = preload("property_values.gd")
|
|
14
|
+
const GdhRead = preload("reading.gd")
|
|
15
|
+
const GdhSerialisation = preload("serialisation.gd")
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
## The nodes of [param scene_path] as {name, type, path, children}, with instanceOf and
|
|
@@ -42,13 +42,43 @@ static func scene_node(scene_path: String, node_path: String) -> Dictionary:
|
|
|
42
42
|
return {"ok": true, "name": found["name"], "type": found["type"], "properties": found["properties"]}
|
|
43
43
|
|
|
44
44
|
|
|
45
|
-
##
|
|
46
|
-
##
|
|
47
|
-
|
|
45
|
+
## The node at [param node_path] in [param scene_path] as [method scene_node] answers it, when it is
|
|
46
|
+
## a [param kind]; otherwise a refusal saying what the scene has there and where its nodes of that
|
|
47
|
+
## kind are, so the caller has the path to pass. The file is read once, because every load prints
|
|
48
|
+
## the engine's warnings about it again.
|
|
49
|
+
static func scene_node_of_kind(scene_path: String, node_path: String, kind: String) -> Dictionary:
|
|
48
50
|
var laid: Dictionary = _laid(scene_path)
|
|
49
51
|
if not laid.get("ok", false):
|
|
50
|
-
return
|
|
52
|
+
return laid
|
|
51
53
|
var nodes: Dictionary = laid["nodes"]
|
|
54
|
+
var path: String = _joined(".", node_path)
|
|
55
|
+
var said: String = "%s not found at: %s" % [kind, node_path]
|
|
56
|
+
var found: String = ""
|
|
57
|
+
if nodes.has(path):
|
|
58
|
+
found = str(nodes[path]["type"])
|
|
59
|
+
if ClassDB.class_exists(found) and ClassDB.is_parent_class(found, kind):
|
|
60
|
+
return {
|
|
61
|
+
"ok": true,
|
|
62
|
+
"name": nodes[path]["name"],
|
|
63
|
+
"type": found,
|
|
64
|
+
"properties": nodes[path]["properties"]
|
|
65
|
+
}
|
|
66
|
+
else:
|
|
67
|
+
var hinted: String = said
|
|
68
|
+
if not nodes.has(_joined(".", node_path.get_slice("/", 0))):
|
|
69
|
+
hinted = GdhRead.from_the_root(said, node_path, str(nodes["."]["name"]))
|
|
70
|
+
said = hinted if hinted != said else _missing_in(nodes, said, node_path)
|
|
71
|
+
var held: PackedStringArray = []
|
|
72
|
+
for at: Variant in nodes:
|
|
73
|
+
var type: String = str(nodes[at]["type"])
|
|
74
|
+
if ClassDB.class_exists(type) and ClassDB.is_parent_class(type, kind):
|
|
75
|
+
var _held: bool = held.append(str(at))
|
|
76
|
+
return {"ok": false, "error": GdhRead.not_of_kind(said, node_path, found, kind, held)}
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
## [param said], the refusal for [param node_path], which the scene laid as [param nodes] does not
|
|
80
|
+
## have, with where the path leaves the scene as [method GdhRead.missing_below] says it.
|
|
81
|
+
static func _missing_in(nodes: Dictionary, said: String, node_path: String) -> String:
|
|
52
82
|
var parts: PackedStringArray = node_path.trim_prefix("./").split("/", false)
|
|
53
83
|
var at: String = "."
|
|
54
84
|
var reached: PackedStringArray = []
|
|
@@ -64,7 +94,7 @@ static func missing_from(said: String, scene_path: String, node_path: String) ->
|
|
|
64
94
|
var children: Array[String] = nodes[at]["children"]
|
|
65
95
|
for child: String in children:
|
|
66
96
|
var _named: bool = names.append(str(nodes[child]["name"]))
|
|
67
|
-
return
|
|
97
|
+
return GdhRead.missing_below(said, str(nodes["."]["name"]), reached, parts[reached.size()], names)
|
|
68
98
|
|
|
69
99
|
|
|
70
100
|
## Every node of [param scene_path] as {ok, nodes}, keyed by path, or a refusal.
|
|
@@ -77,15 +107,14 @@ static func _laid(scene_path: String) -> Dictionary:
|
|
|
77
107
|
if loaded == null:
|
|
78
108
|
return {"ok": false, "error": "Failed to load %s as a scene" % scene_path}
|
|
79
109
|
if not loaded is PackedScene:
|
|
80
|
-
var
|
|
81
|
-
var sort: String = ("an " if kind.left(1).to_upper() in ["A", "E", "I", "O", "U"] else "a ") + kind
|
|
110
|
+
var sort: String = GdhRead.with_article(loaded.get_class())
|
|
82
111
|
return {
|
|
83
112
|
"ok": false,
|
|
84
113
|
"error": "%s is %s, not a scene: resource_edit read reads its properties." % [scene_path, sort]
|
|
85
114
|
}
|
|
86
115
|
var packed: PackedScene = loaded
|
|
87
116
|
var nodes: Dictionary = {}
|
|
88
|
-
var refused: String = _lay(packed.get_state(), ".", nodes, [scene_path])
|
|
117
|
+
var refused: String = _lay(packed.get_state(), ".", nodes, [scene_path], packed)
|
|
89
118
|
if not refused.is_empty():
|
|
90
119
|
return {"ok": false, "error": refused}
|
|
91
120
|
if not nodes.has("."):
|
|
@@ -99,8 +128,11 @@ static func _laid(scene_path: String) -> Dictionary:
|
|
|
99
128
|
##
|
|
100
129
|
## [param within] is the files being laid, outermost first, so a scene that instances one of them
|
|
101
130
|
## is refused rather than laid forever; the engine refuses to load such a scene, so this is only
|
|
102
|
-
## reached through a state built some other way.
|
|
103
|
-
|
|
131
|
+
## reached through a state built some other way. [param loaded] is the scene [param state] is read
|
|
132
|
+
## from, when the caller has it loaded already.
|
|
133
|
+
static func _lay(
|
|
134
|
+
state: SceneState, prefix: String, nodes: Dictionary, within: Array, loaded: PackedScene = null
|
|
135
|
+
) -> String:
|
|
104
136
|
var base: SceneState = state.get_base_scene_state()
|
|
105
137
|
if base != null and not nodes.has(prefix):
|
|
106
138
|
var refused: String = _lay(base, prefix, nodes, within)
|
|
@@ -164,7 +196,7 @@ static func _lay(state: SceneState, prefix: String, nodes: Dictionary, within: A
|
|
|
164
196
|
# Only the scene being read and the scenes it inherits share its root, the owner whose editable
|
|
165
197
|
# instances a write here sets; a file instanced below makes its own owner's editable.
|
|
166
198
|
if prefix == ".":
|
|
167
|
-
for editable: String in _editable_in(state.get_path()):
|
|
199
|
+
for editable: String in _editable_in(state.get_path(), loaded):
|
|
168
200
|
var at: String = _joined(prefix, editable)
|
|
169
201
|
var marked: Dictionary = nodes.get(at, {})
|
|
170
202
|
if marked.has("editableChildren"):
|
|
@@ -173,10 +205,13 @@ static func _lay(state: SceneState, prefix: String, nodes: Dictionary, within: A
|
|
|
173
205
|
|
|
174
206
|
|
|
175
207
|
## The paths of the instances [param scene_path] gives editable children, from its root. SceneState
|
|
176
|
-
## does not give them to a script, so they are read off the scene's bundle
|
|
177
|
-
|
|
208
|
+
## does not give them to a script, so they are read off the scene's bundle: [param given_scene]'s,
|
|
209
|
+
## when the caller has the file loaded, since loading it again prints its warnings again.
|
|
210
|
+
static func _editable_in(scene_path: String, given_scene: PackedScene = null) -> PackedStringArray:
|
|
178
211
|
var found: PackedStringArray = []
|
|
179
|
-
var loaded: Resource =
|
|
212
|
+
var loaded: Resource = given_scene
|
|
213
|
+
if loaded == null:
|
|
214
|
+
loaded = ResourceLoader.load(scene_path, "", ResourceLoader.CACHE_MODE_IGNORE)
|
|
180
215
|
if not loaded is PackedScene:
|
|
181
216
|
return found
|
|
182
217
|
var given: Variant = loaded.get("_bundled")
|
|
@@ -236,7 +271,7 @@ static func _by_id(nodes: Dictionary, owner: String, id: int) -> String:
|
|
|
236
271
|
if path == owner or not (owner == "." or path.begins_with(owner + "/")):
|
|
237
272
|
continue
|
|
238
273
|
var entry: Dictionary = nodes[path]
|
|
239
|
-
if
|
|
274
|
+
if GdhRead.as_int(entry.get("uniqueId", -1), -1) != id:
|
|
240
275
|
continue
|
|
241
276
|
var above: String = "." if not path.contains("/") else path.get_base_dir()
|
|
242
277
|
if _owner_of(nodes, above, owner) == owner:
|
|
@@ -311,7 +346,7 @@ static func _stored(type: String, given: Dictionary) -> Dictionary:
|
|
|
311
346
|
if script != null:
|
|
312
347
|
for declared: Dictionary in script.get_script_property_list():
|
|
313
348
|
var property: String = str(declared.get("name", ""))
|
|
314
|
-
if not property.is_empty() and
|
|
349
|
+
if not property.is_empty() and GdhRead.as_int(declared.get("usage", 0)) & PROPERTY_USAGE_STORAGE:
|
|
315
350
|
scripted[property] = script.get_property_default_value(property)
|
|
316
351
|
var values: Dictionary = {}
|
|
317
352
|
if ClassDB.class_exists(type) and ClassDB.can_instantiate(type):
|
|
@@ -321,7 +356,7 @@ static func _stored(type: String, given: Dictionary) -> Dictionary:
|
|
|
321
356
|
bare.set(str(property), given[property])
|
|
322
357
|
for declared: Dictionary in bare.get_property_list():
|
|
323
358
|
var property: String = str(declared.get("name", ""))
|
|
324
|
-
if not property.is_empty() and
|
|
359
|
+
if not property.is_empty() and GdhRead.as_int(declared.get("usage", 0)) & PROPERTY_USAGE_STORAGE:
|
|
325
360
|
values[property] = bare.get(property)
|
|
326
361
|
if bare is Node:
|
|
327
362
|
bare.free()
|
|
@@ -330,7 +365,7 @@ static func _stored(type: String, given: Dictionary) -> Dictionary:
|
|
|
330
365
|
# Given last, which keeps what a bare node cannot hold: the script itself, and a path to a node
|
|
331
366
|
# for a property that holds the node once it is in a tree.
|
|
332
367
|
values.merge(given, true)
|
|
333
|
-
var serialiser:
|
|
368
|
+
var serialiser: GdhSerialisation = GdhSerialisation.new()
|
|
334
369
|
var answered: Dictionary = {}
|
|
335
370
|
for property: Variant in values:
|
|
336
371
|
answered[property] = serialiser.serialize_value(values[property])
|
|
@@ -369,13 +404,13 @@ static func resource(res_path: String, include_defaults: bool) -> Dictionary:
|
|
|
369
404
|
if script != null:
|
|
370
405
|
for declared: Dictionary in script.get_script_property_list():
|
|
371
406
|
scripted[str(declared.get("name", ""))] = true
|
|
372
|
-
var serialiser:
|
|
407
|
+
var serialiser: GdhSerialisation = GdhSerialisation.new()
|
|
373
408
|
var properties: Dictionary = {}
|
|
374
409
|
for p: Dictionary in loaded.get_property_list():
|
|
375
410
|
var property: String = str(p.get("name", ""))
|
|
376
411
|
if property.is_empty() or property == "script":
|
|
377
412
|
continue
|
|
378
|
-
if not (
|
|
413
|
+
if not (GdhRead.as_int(p.get("usage", 0)) & PROPERTY_USAGE_STORAGE):
|
|
379
414
|
continue
|
|
380
415
|
var value: Variant = loaded.get(property)
|
|
381
416
|
if not include_defaults:
|
|
@@ -384,7 +419,7 @@ static func resource(res_path: String, include_defaults: bool) -> Dictionary:
|
|
|
384
419
|
if scripted.has(property)
|
|
385
420
|
else ClassDB.class_get_property_default_value(loaded.get_class(), property)
|
|
386
421
|
)
|
|
387
|
-
if
|
|
422
|
+
if GdhPropertyValues.same(value, default):
|
|
388
423
|
continue
|
|
389
424
|
properties[property] = serialiser.serialize_value(value)
|
|
390
425
|
var answer: Dictionary = {
|
|
@@ -412,7 +447,7 @@ static func _kept_in(value: Variant, file: String, id: String, seen: Dictionary)
|
|
|
412
447
|
if held.resource_scene_unique_id == id:
|
|
413
448
|
return held
|
|
414
449
|
for p: Dictionary in held.get_property_list():
|
|
415
|
-
if
|
|
450
|
+
if GdhRead.as_int(p.get("usage", 0)) & PROPERTY_USAGE_STORAGE:
|
|
416
451
|
var found: Resource = _kept_in(held.get(str(p.get("name", ""))), file, id, seen)
|
|
417
452
|
if found != null:
|
|
418
453
|
return found
|
|
@@ -11,8 +11,8 @@ extends RefCounted
|
|
|
11
11
|
## both, so a property a built-in setter derives (a Control's offsets from its size) is left alone
|
|
12
12
|
## unless a script that is not a tool is involved.
|
|
13
13
|
|
|
14
|
-
const
|
|
15
|
-
const
|
|
14
|
+
const GdhPropertyValues = preload("property_values.gd")
|
|
15
|
+
const GdhRead = preload("reading.gd")
|
|
16
16
|
|
|
17
17
|
const REMEDY: String = (
|
|
18
18
|
"Open the project's editor (editor_launch with hidden: true) and ask again: the write then goes "
|
|
@@ -25,7 +25,7 @@ const REMEDY: String = (
|
|
|
25
25
|
static func stored(object: Object) -> Dictionary:
|
|
26
26
|
var values: Dictionary = {}
|
|
27
27
|
for declared: Dictionary in object.get_property_list():
|
|
28
|
-
if not (
|
|
28
|
+
if not (GdhRead.as_int(declared.get("usage", 0)) & PROPERTY_USAGE_STORAGE):
|
|
29
29
|
continue
|
|
30
30
|
var name: String = str(declared.get("name", ""))
|
|
31
31
|
if name.is_empty():
|
|
@@ -78,7 +78,7 @@ static func differing(object: Object, before: Dictionary, names: Dictionary) ->
|
|
|
78
78
|
if names.has(name):
|
|
79
79
|
continue
|
|
80
80
|
var was: Variant = before[name] if before.has(name) else default_of(object, name)
|
|
81
|
-
if not
|
|
81
|
+
if not GdhPropertyValues.same(now[name], was):
|
|
82
82
|
changed.append(name)
|
|
83
83
|
return changed
|
|
84
84
|
|
|
@@ -93,7 +93,7 @@ static func default_of(object: Object, name: String) -> Variant:
|
|
|
93
93
|
var given: Variant = script.get_property_default_value(name)
|
|
94
94
|
if given != null:
|
|
95
95
|
return given
|
|
96
|
-
return _initialised(script, name,
|
|
96
|
+
return _initialised(script, name, GdhRead.as_int(declared.get("type", TYPE_NIL)))
|
|
97
97
|
return ClassDB.class_get_property_default_value(object.get_class(), name)
|
|
98
98
|
|
|
99
99
|
|
|
@@ -122,7 +122,7 @@ static func _initialised(script: Script, name: String, type: int) -> Variant:
|
|
|
122
122
|
## nothing a script did: the saver writes a property whose default the engine cannot report whatever
|
|
123
123
|
## it holds, the editor's save as well.
|
|
124
124
|
static func at_default(object: Object, name: String) -> bool:
|
|
125
|
-
return
|
|
125
|
+
return GdhPropertyValues.same(object.get(name), default_of(object, name))
|
|
126
126
|
|
|
127
127
|
|
|
128
128
|
## The ids, in the file, of the embedded resources [param property] reaches into on [param object],
|
|
@@ -276,7 +276,7 @@ static func reads_the_same(was: Variant, now: Variant, default: Variant) -> bool
|
|
|
276
276
|
return false
|
|
277
277
|
else:
|
|
278
278
|
values.append(str_to_var(str(text)))
|
|
279
|
-
return
|
|
279
|
+
return GdhPropertyValues.same(values[0], values[1])
|
|
280
280
|
|
|
281
281
|
|
|
282
282
|
static func _main_entries(text: String) -> Dictionary:
|
|
@@ -5,8 +5,8 @@ extends EditorPlugin
|
|
|
5
5
|
## that does what the server asks, with a label in the toolbar saying whether the two are
|
|
6
6
|
## connected.
|
|
7
7
|
|
|
8
|
-
const
|
|
9
|
-
const
|
|
8
|
+
const GdhBridgeClient = preload("bridge_client.gd")
|
|
9
|
+
const GdhToolExecutor = preload("tool_executor.gd")
|
|
10
10
|
|
|
11
11
|
## The tools answered while the editor scans or imports, because none of them saves a file: the
|
|
12
12
|
## ones a caller waits on a scan with, and the ones that only read or stop a played game.
|
|
@@ -19,17 +19,17 @@ const ANSWERED_WHILE_SCANNING: Array[String] = [
|
|
|
19
19
|
"profile_read",
|
|
20
20
|
]
|
|
21
21
|
|
|
22
|
-
var _client:
|
|
23
|
-
var _tool_executor:
|
|
22
|
+
var _client: GdhBridgeClient
|
|
23
|
+
var _tool_executor: GdhToolExecutor
|
|
24
24
|
var _status_label: Label
|
|
25
25
|
|
|
26
26
|
|
|
27
27
|
func _enter_tree() -> void:
|
|
28
|
-
_client =
|
|
28
|
+
_client = GdhBridgeClient.new()
|
|
29
29
|
_client.name = "GdharnessBridgeClient"
|
|
30
30
|
add_child(_client)
|
|
31
31
|
|
|
32
|
-
_tool_executor =
|
|
32
|
+
_tool_executor = GdhToolExecutor.new()
|
|
33
33
|
_tool_executor.name = "GdharnessToolExecutor"
|
|
34
34
|
add_child(_tool_executor)
|
|
35
35
|
_tool_executor.set_editor_plugin(self)
|