gdharness 1.3.41 → 1.3.43
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 +131 -47
- package/build/godot/addons/auto_reload/auto_reload.gd +18 -19
- package/build/godot/addons/gdharness_editor/animation_reads.gd +84 -35
- package/build/godot/addons/gdharness_editor/bridge_client.gd +53 -58
- package/build/godot/addons/gdharness_editor/declared_values.gd +68 -32
- package/build/godot/addons/gdharness_editor/file_reads.gd +114 -39
- package/build/godot/addons/gdharness_editor/file_walk.gd +12 -5
- package/build/godot/addons/gdharness_editor/headless_check.gd +94 -41
- package/build/godot/addons/gdharness_editor/plugin.gd +2 -3
- package/build/godot/addons/gdharness_editor/property_paths.gd +22 -12
- package/build/godot/addons/gdharness_editor/property_values.gd +143 -78
- package/build/godot/addons/gdharness_editor/reading.gd +37 -12
- package/build/godot/addons/gdharness_editor/same_values.gd +4 -4
- package/build/godot/addons/gdharness_editor/scene_file.gd +136 -79
- package/build/godot/addons/gdharness_editor/scene_uids.gd +12 -6
- package/build/godot/addons/gdharness_editor/serialisation.gd +75 -51
- package/build/godot/addons/gdharness_editor/setting_reads.gd +7 -6
- package/build/godot/addons/gdharness_editor/tool_executor.gd +3 -6
- package/build/godot/addons/gdharness_editor/tools/animation_states.gd +41 -28
- package/build/godot/addons/gdharness_editor/tools/animation_tools.gd +84 -50
- package/build/godot/addons/gdharness_editor/tools/animation_tracks.gd +70 -40
- package/build/godot/addons/gdharness_editor/tools/class_tools.gd +34 -30
- package/build/godot/addons/gdharness_editor/tools/filesystem_tools.gd +24 -22
- package/build/godot/addons/gdharness_editor/tools/navigation_bake.gd +49 -29
- package/build/godot/addons/gdharness_editor/tools/play_tools.gd +26 -21
- package/build/godot/addons/gdharness_editor/tools/profile_tools.gd +40 -40
- package/build/godot/addons/gdharness_editor/tools/resource_tools.gd +181 -107
- package/build/godot/addons/gdharness_editor/tools/scene_groups.gd +16 -9
- package/build/godot/addons/gdharness_editor/tools/scene_kin.gd +75 -43
- package/build/godot/addons/gdharness_editor/tools/scene_paths.gd +35 -20
- package/build/godot/addons/gdharness_editor/tools/scene_placing.gd +28 -13
- package/build/godot/addons/gdharness_editor/tools/scene_sharing.gd +66 -0
- package/build/godot/addons/gdharness_editor/tools/scene_sharing.gd.uid +1 -0
- package/build/godot/addons/gdharness_editor/tools/scene_tools.gd +171 -62
- package/build/godot/addons/gdharness_editor/tools/script_paths.gd +20 -8
- package/build/godot/addons/gdharness_editor/tools/settings_tools.gd +11 -8
- package/build/godot/addons/gdharness_editor/tools/signal_tools.gd +47 -34
- package/build/godot/addons/gdharness_editor/tools/tileset_tools.gd +77 -50
- package/build/godot/addons/gdharness_editor/value_shapes.gd +5 -6
- package/build/godot/addons/gdharness_runtime/call_errors.gd +1 -2
- package/build/godot/addons/gdharness_runtime/declared_values.gd +68 -32
- package/build/godot/addons/gdharness_runtime/error_report.gd +1 -2
- package/build/godot/addons/gdharness_runtime/reading.gd +37 -12
- package/build/godot/addons/gdharness_runtime/runtime_accessibility.gd +84 -63
- package/build/godot/addons/gdharness_runtime/runtime_autoload.gd +153 -148
- package/build/godot/addons/gdharness_runtime/runtime_capture.gd +64 -42
- package/build/godot/addons/gdharness_runtime/runtime_changes.gd +184 -122
- package/build/godot/addons/gdharness_runtime/runtime_frames.gd +2 -2
- package/build/godot/addons/gdharness_runtime/runtime_hover.gd +24 -20
- package/build/godot/addons/gdharness_runtime/runtime_input.gd +134 -92
- package/build/godot/addons/gdharness_runtime/runtime_items.gd +29 -13
- package/build/godot/addons/gdharness_runtime/runtime_menus.gd +0 -1
- package/build/godot/addons/gdharness_runtime/runtime_mouse.gd +38 -20
- package/build/godot/addons/gdharness_runtime/runtime_pad.gd +17 -13
- package/build/godot/addons/gdharness_runtime/runtime_paths.gd +84 -46
- package/build/godot/addons/gdharness_runtime/runtime_queries.gd +117 -79
- package/build/godot/addons/gdharness_runtime/runtime_says.gd +14 -17
- package/build/godot/addons/gdharness_runtime/runtime_screen.gd +23 -16
- package/build/godot/addons/gdharness_runtime/runtime_targets.gd +117 -66
- package/build/godot/addons/gdharness_runtime/runtime_touch.gd +211 -0
- package/build/godot/addons/gdharness_runtime/runtime_touch.gd.uid +1 -0
- package/build/godot/addons/gdharness_runtime/runtime_typing.gd +44 -27
- package/build/godot/addons/gdharness_runtime/runtime_values.gd +108 -73
- package/build/godot/addons/gdharness_runtime/runtime_waits.gd +111 -84
- package/build/godot/addons/gdharness_runtime/runtime_words.gd +10 -7
- package/build/godot/addons/gdharness_runtime/serialisation.gd +75 -51
- package/build/godot/addons/gdharness_runtime/value_shapes.gd +5 -6
- package/build/godot/addons/gdharness_tests/bench.gd +4 -5
- package/build/godot/addons/gdharness_tests/event.gd +10 -5
- package/build/godot/addons/gdharness_tests/share.gd +38 -18
- package/build/godot/addons/gdharness_tests/suites.gd +3 -3
- package/build/godot/addons/gdharness_tests/wait.gd +3 -2
- package/build/godot/loader/loader.gd +9 -9
- package/build/godot/operations/animation_reads.gd +84 -35
- package/build/godot/operations/audio_buses.gd +48 -19
- package/build/godot/operations/class_cache.gd +16 -11
- package/build/godot/operations/classdb_queries.gd +89 -44
- package/build/godot/operations/declared_values.gd +68 -32
- package/build/godot/operations/dependencies.gd +139 -93
- package/build/godot/operations/editor_tools.gd +9 -10
- package/build/godot/operations/export_presets.gd +100 -54
- package/build/godot/operations/file_reads.gd +114 -39
- package/build/godot/operations/file_walk.gd +12 -5
- package/build/godot/operations/gdscript_analysis.gd +105 -66
- package/build/godot/operations/gdscript_authoring.gd +138 -39
- package/build/godot/operations/godot_operations.gd +17 -11
- package/build/godot/operations/import_pipeline.gd +110 -59
- package/build/godot/operations/input_actions.gd +63 -39
- package/build/godot/operations/patterns.gd +3 -1
- package/build/godot/operations/plugins.gd +36 -16
- package/build/godot/operations/project_config.gd +144 -27
- package/build/godot/operations/project_diagnostics.gd +24 -11
- package/build/godot/operations/project_file.gd +16 -16
- package/build/godot/operations/project_validation.gd +71 -44
- package/build/godot/operations/property_paths.gd +22 -12
- package/build/godot/operations/property_values.gd +143 -78
- package/build/godot/operations/reading.gd +37 -12
- package/build/godot/operations/rename_checks.gd +2 -2
- package/build/godot/operations/resource_files.gd +11 -9
- package/build/godot/operations/same_values.gd +4 -4
- package/build/godot/operations/scene_connections.gd +27 -13
- package/build/godot/operations/script_check.gd +52 -49
- package/build/godot/operations/serialisation.gd +75 -51
- package/build/godot/operations/setting_reads.gd +7 -6
- package/build/godot/operations/value_shapes.gd +5 -6
- package/build/godot/tests/listed.gd +321 -268
- package/package.json +1 -1
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
@tool
|
|
2
2
|
extends RefCounted
|
|
3
|
-
|
|
4
3
|
## Whether a value read back is the value that was given, as the tools that write properties judge
|
|
5
4
|
## what a write kept and what it changed besides.
|
|
6
5
|
|
|
7
6
|
const GdhRead = preload("reading.gd")
|
|
8
7
|
const GdhSerialisation = preload("serialisation.gd")
|
|
9
|
-
|
|
10
8
|
# The types made of floats, which a read back compares within float precision: a Vector2 property
|
|
11
9
|
# stores 32-bit components, so 0.1 comes back as 0.100000001.
|
|
12
10
|
const FLOAT_SHAPES: Array[int] = [
|
|
@@ -23,7 +21,6 @@ const FLOAT_SHAPES: Array[int] = [
|
|
|
23
21
|
TYPE_AABB,
|
|
24
22
|
TYPE_PROJECTION,
|
|
25
23
|
]
|
|
26
|
-
|
|
27
24
|
const PACKED: Array[int] = [
|
|
28
25
|
TYPE_PACKED_BYTE_ARRAY,
|
|
29
26
|
TYPE_PACKED_INT32_ARRAY,
|
|
@@ -63,7 +60,10 @@ static func same(held: Variant, given: Variant) -> bool:
|
|
|
63
60
|
return held_type == given_type and held == given
|
|
64
61
|
var held_number: float = GdhRead.as_float(held)
|
|
65
62
|
var given_number: float = GdhRead.as_float(given)
|
|
66
|
-
return (
|
|
63
|
+
return (
|
|
64
|
+
(is_nan(held_number) and is_nan(given_number))
|
|
65
|
+
or is_equal_approx(held_number, given_number)
|
|
66
|
+
)
|
|
67
67
|
if GdhSerialisation.TEXTS.has(held_type) and GdhSerialisation.TEXTS.has(given_type):
|
|
68
68
|
return str(held) == str(given)
|
|
69
69
|
if _listed(held_type) and _listed(given_type):
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
@tool
|
|
2
2
|
extends RefCounted
|
|
3
|
-
|
|
4
3
|
## One scene file opened for a tool, read past the resource cache and written back only once what
|
|
5
4
|
## would be saved has been checked.
|
|
6
5
|
##
|
|
@@ -26,7 +25,6 @@ extends RefCounted
|
|
|
26
25
|
## anything is saved, and a write is refused when that does not hold. See [GdhHeadlessCheck].
|
|
27
26
|
|
|
28
27
|
const GdhHeadlessCheck = preload("headless_check.gd")
|
|
29
|
-
|
|
30
28
|
## How many times what scripts changed is put back before a write is refused for it.
|
|
31
29
|
const PUTTING_BACK_PASSES: int = 4
|
|
32
30
|
const GdhRead = preload("reading.gd")
|
|
@@ -45,6 +43,17 @@ var root: Node = null
|
|
|
45
43
|
var path: String = ""
|
|
46
44
|
## What the last write saved, for its answer to read what the file now stores.
|
|
47
45
|
var written_pack: PackedScene = null
|
|
46
|
+
## The properties a headless write put back on each node the tool changed, by instance id, as a
|
|
47
|
+
## set: what the node's script, which is not a tool, changed as the tool wrote, and the file was
|
|
48
|
+
## not given.
|
|
49
|
+
var restored_by_write: Dictionary = {}
|
|
50
|
+
## What the scene loads with where it stores what the tool gave, by node path and then property:
|
|
51
|
+
## the node's script changed the value as the copy the write checks loaded.
|
|
52
|
+
var loads_as: Dictionary = {}
|
|
53
|
+
## The node paths in [member loads_as] whose script is not a tool, so an editor does not run it.
|
|
54
|
+
var loads_untooled: Dictionary = {}
|
|
55
|
+
## The pack a write's check reads, for [method stores] to answer from.
|
|
56
|
+
var _checked_pack: PackedScene = null
|
|
48
57
|
## The path of each node a scene this one inherits gives, with the file that makes it.
|
|
49
58
|
var _base_paths: Dictionary = {}
|
|
50
59
|
var _state: SceneState = null
|
|
@@ -76,19 +85,19 @@ var _engine_kept: Dictionary = {}
|
|
|
76
85
|
func open(scene_path: String, for_writing: bool) -> Dictionary:
|
|
77
86
|
path = scene_path
|
|
78
87
|
if not FileAccess.file_exists(path):
|
|
79
|
-
return {"ok": false, "error": "Scene not found: " + path}
|
|
88
|
+
return { "ok": false, "error": "Scene not found: " + path }
|
|
80
89
|
if for_writing:
|
|
81
90
|
var held_back: String = unsaved_in_editor(path)
|
|
82
91
|
if not held_back.is_empty():
|
|
83
|
-
return {"ok": false, "error": held_back}
|
|
92
|
+
return { "ok": false, "error": held_back }
|
|
84
93
|
var packed: PackedScene = (
|
|
85
94
|
ResourceLoader.load(path, "PackedScene", ResourceLoader.CACHE_MODE_IGNORE) as PackedScene
|
|
86
95
|
)
|
|
87
96
|
if packed == null:
|
|
88
|
-
return {"ok": false, "error": "Failed to load %s as a scene" % path}
|
|
97
|
+
return { "ok": false, "error": "Failed to load %s as a scene" % path }
|
|
89
98
|
root = packed.instantiate(PackedScene.GEN_EDIT_STATE_MAIN)
|
|
90
99
|
if root == null:
|
|
91
|
-
return {"ok": false, "error": "Failed to instantiate " + path}
|
|
100
|
+
return { "ok": false, "error": "Failed to instantiate " + path }
|
|
92
101
|
_state = packed.get_state()
|
|
93
102
|
_base_paths = makers(_state)
|
|
94
103
|
# On a read as well, which would otherwise answer what the scripts set as what the file holds.
|
|
@@ -133,15 +142,16 @@ func asked(node: Node, names: Array) -> void:
|
|
|
133
142
|
GdhHeadlessCheck.spare(_reached, GdhHeadlessCheck.reached(node, property))
|
|
134
143
|
|
|
135
144
|
|
|
136
|
-
## Takes [param resource], built into the scene, as one the tool changed, which a write saves as it
|
|
145
|
+
## Takes [param resource], built into the scene, as one the tool changed, which a write saves as it
|
|
146
|
+
## is.
|
|
137
147
|
func reached_into(resource: Resource) -> void:
|
|
138
148
|
var id: String = resource.get_scene_unique_id()
|
|
139
149
|
if not id.is_empty():
|
|
140
150
|
_reached[id] = true
|
|
141
151
|
|
|
142
152
|
|
|
143
|
-
## Takes each copy in [param copies], keyed by the node it was made from, as holding what that
|
|
144
|
-
## file entry holds.
|
|
153
|
+
## Takes each copy in [param copies], keyed by the node it was made from, as holding what that
|
|
154
|
+
## node's file entry holds.
|
|
145
155
|
func copied(copies: Dictionary) -> void:
|
|
146
156
|
for source: Variant in copies:
|
|
147
157
|
var original: Node = source
|
|
@@ -155,8 +165,9 @@ static func unsaved_in_editor(scene_path: String) -> String:
|
|
|
155
165
|
return ""
|
|
156
166
|
return (
|
|
157
167
|
(
|
|
158
|
-
"%s has unsaved changes in the editor, and writing the file would discard them when
|
|
159
|
-
+ "editor reloads it. Save it first (scene_create op=save) or close it without
|
|
168
|
+
"%s has unsaved changes in the editor, and writing the file would discard them when "
|
|
169
|
+
+ "the editor reloads it. Save it first (scene_create op=save) or close it without "
|
|
170
|
+
+ "saving."
|
|
160
171
|
)
|
|
161
172
|
% scene_path
|
|
162
173
|
)
|
|
@@ -165,7 +176,7 @@ static func unsaved_in_editor(scene_path: String) -> String:
|
|
|
165
176
|
## Frees [member root] and answers [param error] as a refusal.
|
|
166
177
|
func refuse(error: String) -> Dictionary:
|
|
167
178
|
close()
|
|
168
|
-
return {"ok": false, "error": error}
|
|
179
|
+
return { "ok": false, "error": error }
|
|
169
180
|
|
|
170
181
|
|
|
171
182
|
## [param said], the refusal for [param node_path] found nowhere in this scene, with how a path is
|
|
@@ -202,10 +213,10 @@ func close() -> void:
|
|
|
202
213
|
|
|
203
214
|
|
|
204
215
|
## What the file holds under the instance at [param at] that goes once its children are not
|
|
205
|
-
## editable, as {values, nodes}: the paths of the instance's nodes it sets values on, which stay
|
|
206
|
-
## the instance's own values, and of the nodes the scene added below one of them, which are
|
|
207
|
-
## since the packer skips everything below a node of an instance that is not editable. A node
|
|
208
|
-
## straight under [param at] is the scene's own and stays.
|
|
216
|
+
## editable, as {values, nodes}: the paths of the instance's nodes it sets values on, which stay
|
|
217
|
+
## with the instance's own values, and of the nodes the scene added below one of them, which are
|
|
218
|
+
## gone, since the packer skips everything below a node of an instance that is not editable. A node
|
|
219
|
+
## added straight under [param at] is the scene's own and stays.
|
|
209
220
|
func held_under(at: String) -> Dictionary:
|
|
210
221
|
var values: Array[String] = []
|
|
211
222
|
var nodes: Array[String] = []
|
|
@@ -219,7 +230,7 @@ func held_under(at: String) -> Dictionary:
|
|
|
219
230
|
values.append(entry)
|
|
220
231
|
elif not values_only and parent.begins_with(at + "/"):
|
|
221
232
|
nodes.append(entry)
|
|
222
|
-
return {"values": values, "nodes": nodes}
|
|
233
|
+
return { "values": values, "nodes": nodes }
|
|
223
234
|
|
|
224
235
|
|
|
225
236
|
## The path of [param node] as a tool names it: "." for the root.
|
|
@@ -230,7 +241,7 @@ func path_of(node: Node) -> String:
|
|
|
230
241
|
## The class of [param node] as a read answers it: nodeType the engine class, and scriptClass the
|
|
231
242
|
## class its script declares, when it declares one.
|
|
232
243
|
static func class_of(node: Node) -> Dictionary:
|
|
233
|
-
var named: Dictionary = {"nodeType": node.get_class()}
|
|
244
|
+
var named: Dictionary = { "nodeType": node.get_class() }
|
|
234
245
|
var script: Script = node.get_script()
|
|
235
246
|
if script != null and not str(script.get_global_name()).is_empty():
|
|
236
247
|
named["scriptClass"] = str(script.get_global_name())
|
|
@@ -260,8 +271,8 @@ static func node_at(top: Node, at: String) -> Node:
|
|
|
260
271
|
## Why the property or signal [param missing] could not be found on [param node], adding the one
|
|
261
272
|
## cause the name alone does not show: a script that does not compile leaves the editor holding a
|
|
262
273
|
## placeholder with no properties or signals at all, and the refusal named the property instead.
|
|
263
|
-
## Such a script has no engine class beneath it. Compiling a copy of the source to find out failed
|
|
264
|
-
## every script with a class_name, since the copy hides the global class the original declares.
|
|
274
|
+
## Such a script has no engine class beneath it. Compiling a copy of the source to find out failed
|
|
275
|
+
## on every script with a class_name, since the copy hides the global class the original declares.
|
|
265
276
|
static func missing_on(node: Node, missing: String) -> String:
|
|
266
277
|
var script: Script = node.get_script()
|
|
267
278
|
if script == null or not str(script.get_instance_base_type()).is_empty():
|
|
@@ -279,15 +290,18 @@ func foreign(node: Node) -> String:
|
|
|
279
290
|
return ""
|
|
280
291
|
if node.owner == null:
|
|
281
292
|
return (
|
|
282
|
-
|
|
293
|
+
(
|
|
294
|
+
"%s is made by a script as the scene is instanced, and no scene file saves "
|
|
295
|
+
+ "anything about it."
|
|
296
|
+
)
|
|
283
297
|
% path_of(node)
|
|
284
298
|
)
|
|
285
299
|
if root.is_editable_instance(node.owner):
|
|
286
300
|
return ""
|
|
287
301
|
var said: String = (
|
|
288
302
|
(
|
|
289
|
-
"%s belongs to the scene instanced at %s (%s), and this scene saves nothing about it.
|
|
290
|
-
+ "that scene instead, which changes every instance of it"
|
|
303
|
+
"%s belongs to the scene instanced at %s (%s), and this scene saves nothing about it. "
|
|
304
|
+
+ "Edit that scene instead, which changes every instance of it"
|
|
291
305
|
)
|
|
292
306
|
% [path_of(node), path_of(node.owner), node.owner.scene_file_path]
|
|
293
307
|
)
|
|
@@ -338,19 +352,22 @@ func fixed(node: Node, deleting: bool = false) -> String:
|
|
|
338
352
|
"%s is inside the scene instanced at %s (%s), which creates it again on load."
|
|
339
353
|
% [path_of(node), path_of(node.owner), instanced]
|
|
340
354
|
)
|
|
341
|
-
return
|
|
355
|
+
return (
|
|
356
|
+
inside + (_deleted_elsewhere(node, maker) if deleting else " Edit %s instead." % maker)
|
|
357
|
+
)
|
|
342
358
|
var at: String = path_of(node)
|
|
343
359
|
if _base_paths.has(at):
|
|
344
360
|
var maker: String = str(_base_paths[at])
|
|
345
361
|
var made: String = (
|
|
346
|
-
"%s is made by %s, a scene %s inherits, which creates it again on load."
|
|
362
|
+
"%s is made by %s, a scene %s inherits, which creates it again on load."
|
|
363
|
+
% [at, maker, path]
|
|
347
364
|
)
|
|
348
365
|
return made + (_deleted_elsewhere(node, maker) if deleting else " Edit that scene instead.")
|
|
349
366
|
return ""
|
|
350
367
|
|
|
351
368
|
|
|
352
|
-
## Each node the scenes [param state] inherits give it, by path, with the deepest file making it
|
|
353
|
-
## than values; a node [param state] makes itself is not in it.
|
|
369
|
+
## Each node the scenes [param state] inherits give it, by path, with the deepest file making it
|
|
370
|
+
## more than values; a node [param state] makes itself is not in it.
|
|
354
371
|
static func makers(state: SceneState) -> Dictionary:
|
|
355
372
|
var made_by: Dictionary = {}
|
|
356
373
|
var at: SceneState = state.get_base_scene_state()
|
|
@@ -369,20 +386,22 @@ static func makers(state: SceneState) -> Dictionary:
|
|
|
369
386
|
## What a caller can do with [param node], made by [param maker], instead of deleting it here.
|
|
370
387
|
static func _deleted_elsewhere(node: Node, maker: String) -> String:
|
|
371
388
|
var hidden: String = (
|
|
372
|
-
"set its visible to false here to hide it in this scene alone, or "
|
|
389
|
+
"set its visible to false here to hide it in this scene alone, or "
|
|
390
|
+
if "visible" in node
|
|
391
|
+
else ""
|
|
373
392
|
)
|
|
374
393
|
return (
|
|
375
394
|
(
|
|
376
|
-
" No scene can delete a node another scene makes: %sdelete it in %s, which removes it
|
|
377
|
-
+ "every scene using that one."
|
|
395
|
+
" No scene can delete a node another scene makes: %sdelete it in %s, which removes it "
|
|
396
|
+
+ "from every scene using that one."
|
|
378
397
|
)
|
|
379
398
|
% [hidden, maker]
|
|
380
399
|
)
|
|
381
400
|
|
|
382
401
|
|
|
383
|
-
## Why the folder a new file at [param file_path] goes in cannot be made, or "" once it is there.
|
|
384
|
-
## engine makes no folder when it saves, so a create into one not yet made failed with no more
|
|
385
|
-
## the engine's error, while script_edit create made its folder and went ahead.
|
|
402
|
+
## Why the folder a new file at [param file_path] goes in cannot be made, or "" once it is there.
|
|
403
|
+
## The engine makes no folder when it saves, so a create into one not yet made failed with no more
|
|
404
|
+
## than the engine's error, while script_edit create made its folder and went ahead.
|
|
386
405
|
static func make_folder_for(file_path: String) -> String:
|
|
387
406
|
var folder: String = file_path.get_base_dir()
|
|
388
407
|
if DirAccess.dir_exists_absolute(folder):
|
|
@@ -391,10 +410,10 @@ static func make_folder_for(file_path: String) -> String:
|
|
|
391
410
|
return "" if made == OK else "Could not make the folder %s: %s" % [folder, error_string(made)]
|
|
392
411
|
|
|
393
412
|
|
|
394
|
-
## Why [param script], loaded from [param script_path], cannot be attached because the editor could
|
|
395
|
-
## compile it, or "". A script that did not compile has no base type, and the refusal built from
|
|
396
|
-
## said "start.gd extends , so it cannot be on a Control root" about a script that was fine,
|
|
397
|
-
## failure was a class it called being behind in the editor. A headless engine compiles every
|
|
413
|
+
## Why [param script], loaded from [param script_path], cannot be attached because the editor could
|
|
414
|
+
## not compile it, or "". A script that did not compile has no base type, and the refusal built from
|
|
415
|
+
## that said "start.gd extends , so it cannot be on a Control root" about a script that was fine,
|
|
416
|
+
## whose failure was a class it called being behind in the editor. A headless engine compiles every
|
|
398
417
|
## script from its file, so only an editor can hold a copy that is behind.
|
|
399
418
|
static func uncompiled(script: Script, script_path: String) -> String:
|
|
400
419
|
if not str(script.get_instance_base_type()).is_empty():
|
|
@@ -410,9 +429,9 @@ static func uncompiled(script: Script, script_path: String) -> String:
|
|
|
410
429
|
return (
|
|
411
430
|
(
|
|
412
431
|
"%s does not compile in the editor, so it cannot be attached. script_diagnostics on it "
|
|
413
|
-
+ "gives the parser's errors. If they deny a member that another class's file
|
|
414
|
-
+ "the editor's copy of that class is behind: editor_rescan with
|
|
415
|
-
+ "class, then ask again."
|
|
432
|
+
+ "gives the parser's errors. If they deny a member that another class's file "
|
|
433
|
+
+ "declares, the editor's copy of that class is behind: editor_rescan with "
|
|
434
|
+
+ "reloadScript on that class, then ask again."
|
|
416
435
|
)
|
|
417
436
|
% script_path
|
|
418
437
|
)
|
|
@@ -430,21 +449,29 @@ static func script_class(type_name: String, kind: String) -> Dictionary:
|
|
|
430
449
|
if entry.get("is_abstract", false) == true:
|
|
431
450
|
return {
|
|
432
451
|
"error":
|
|
433
|
-
|
|
452
|
+
(
|
|
453
|
+
"%s, declared in %s, is abstract, so nothing can be made of it."
|
|
454
|
+
% [type_name, declaring]
|
|
455
|
+
),
|
|
434
456
|
}
|
|
435
457
|
var script: Script = load(declaring) as Script
|
|
436
458
|
if script == null:
|
|
437
|
-
return {
|
|
459
|
+
return {
|
|
460
|
+
"error": "%s is declared in %s, which does not load." % [type_name, declaring],
|
|
461
|
+
}
|
|
438
462
|
var failed: String = uncompiled(script, declaring)
|
|
439
463
|
if not failed.is_empty():
|
|
440
|
-
return {"error": failed}
|
|
464
|
+
return { "error": failed }
|
|
441
465
|
var base: String = str(script.get_instance_base_type())
|
|
442
466
|
if not ClassDB.is_parent_class(base, kind):
|
|
443
467
|
return {
|
|
444
468
|
"error":
|
|
445
|
-
|
|
469
|
+
(
|
|
470
|
+
"%s, declared in %s, extends %s, which is not a %s."
|
|
471
|
+
% [type_name, declaring, base, kind]
|
|
472
|
+
),
|
|
446
473
|
}
|
|
447
|
-
return {"script": script, "path": declaring, "base": base}
|
|
474
|
+
return { "script": script, "path": declaring, "base": base }
|
|
448
475
|
return {}
|
|
449
476
|
|
|
450
477
|
|
|
@@ -454,12 +481,12 @@ static func script_class(type_name: String, kind: String) -> Dictionary:
|
|
|
454
481
|
## argument the type came in, for the refusal when the call also named another script.
|
|
455
482
|
static func made_as(type_name: String, given: String, kind: String, argument: String) -> Dictionary:
|
|
456
483
|
if ClassDB.class_exists(type_name):
|
|
457
|
-
return {"type": type_name, "script": given}
|
|
484
|
+
return { "type": type_name, "script": given }
|
|
458
485
|
var declared: Dictionary = script_class(type_name, kind)
|
|
459
486
|
if declared.has("error"):
|
|
460
487
|
return declared
|
|
461
488
|
if declared.is_empty():
|
|
462
|
-
return {"type": type_name, "script": given}
|
|
489
|
+
return { "type": type_name, "script": given }
|
|
463
490
|
var declaring: String = str(declared["path"])
|
|
464
491
|
if not given.is_empty() and given != declaring:
|
|
465
492
|
return {
|
|
@@ -467,9 +494,9 @@ static func made_as(type_name: String, given: String, kind: String, argument: St
|
|
|
467
494
|
(
|
|
468
495
|
"%s %s is the class %s declares, and script names %s: pass one of them."
|
|
469
496
|
% [argument, type_name, declaring, given]
|
|
470
|
-
)
|
|
497
|
+
),
|
|
471
498
|
}
|
|
472
|
-
return {"type": str(declared["base"]), "script": declaring}
|
|
499
|
+
return { "type": str(declared["base"]), "script": declaring }
|
|
473
500
|
|
|
474
501
|
|
|
475
502
|
## Why no node can be called [param node_name], wherever it sits, or "".
|
|
@@ -539,7 +566,9 @@ func set_at(at: String) -> Dictionary:
|
|
|
539
566
|
if _relative(str(_state.get_node_path(index))) != at:
|
|
540
567
|
continue
|
|
541
568
|
for p: int in _state.get_node_property_count(index):
|
|
542
|
-
held[str(_state.get_node_property_name(index, p))] = _state.get_node_property_value(
|
|
569
|
+
held[str(_state.get_node_property_name(index, p))] = _state.get_node_property_value(
|
|
570
|
+
index, p
|
|
571
|
+
)
|
|
543
572
|
return held
|
|
544
573
|
|
|
545
574
|
|
|
@@ -564,6 +593,19 @@ func kept(saved: Node, loaded: Variant, given: Variant) -> bool:
|
|
|
564
593
|
return GdhSame.same(loaded, given)
|
|
565
594
|
|
|
566
595
|
|
|
596
|
+
## Whether the pack a write is checking stores [param given] for [param property] on the node at
|
|
597
|
+
## [param at], a path from the root.
|
|
598
|
+
func stores(at: String, property: String, given: Variant) -> bool:
|
|
599
|
+
if _checked_pack == null:
|
|
600
|
+
return false
|
|
601
|
+
var state: SceneState = _checked_pack.get_state()
|
|
602
|
+
for index: int in state.get_node_count():
|
|
603
|
+
if _relative(str(state.get_node_path(index))) == at:
|
|
604
|
+
var stored: Dictionary = _stored_at(state, index)
|
|
605
|
+
return stored.has(property) and GdhSame.same(stored[property], given)
|
|
606
|
+
return false
|
|
607
|
+
|
|
608
|
+
|
|
567
609
|
## Packs [member root], has [param check] read the result as the file will hold it, and writes the
|
|
568
610
|
## file only if the check answers "". Frees [member root] either way.
|
|
569
611
|
##
|
|
@@ -583,15 +625,18 @@ func write(check: Callable = Callable()) -> Dictionary:
|
|
|
583
625
|
packed = settled[0]
|
|
584
626
|
if not path.ends_with(".tscn") and _holds_scripted_embedded():
|
|
585
627
|
var binary: String = (
|
|
586
|
-
"Nothing was written: %s is a binary scene holding an embedded resource with a
|
|
587
|
-
+ "that is not a tool, and what that script changed when a headless engine
|
|
588
|
-
+ "cannot be told apart from the file here. "
|
|
628
|
+
"Nothing was written: %s is a binary scene holding an embedded resource with a "
|
|
629
|
+
+ "script that is not a tool, and what that script changed when a headless engine "
|
|
630
|
+
+ "loaded it cannot be told apart from the file here. "
|
|
589
631
|
)
|
|
590
632
|
return refuse(binary % path + GdhHeadlessCheck.REMEDY)
|
|
591
633
|
if check.is_valid():
|
|
592
634
|
var saved: Node = packed.instantiate(PackedScene.GEN_EDIT_STATE_MAIN)
|
|
593
635
|
if saved == null:
|
|
594
|
-
return refuse(
|
|
636
|
+
return refuse(
|
|
637
|
+
"The packed copy of %s did not instantiate, so nothing was written." % path
|
|
638
|
+
)
|
|
639
|
+
_checked_pack = packed
|
|
595
640
|
var missing: String = str(check.call(saved))
|
|
596
641
|
saved.free()
|
|
597
642
|
if not missing.is_empty():
|
|
@@ -612,30 +657,33 @@ func write(check: Callable = Callable()) -> Dictionary:
|
|
|
612
657
|
if written.is_empty():
|
|
613
658
|
also_saved.append_array(also)
|
|
614
659
|
return written
|
|
660
|
+
var just_saved: GdhHeadlessCheck.GdhSaved = GdhHeadlessCheck.GdhSaved.new(
|
|
661
|
+
path, _text, func() -> Error: return GdhSceneUids.save_keeping_uid(packed, path)
|
|
662
|
+
)
|
|
615
663
|
var unkept: String = GdhHeadlessCheck.settle_saved(
|
|
616
|
-
|
|
617
|
-
_text,
|
|
664
|
+
just_saved,
|
|
618
665
|
values,
|
|
619
666
|
null,
|
|
620
667
|
{},
|
|
621
668
|
_reached,
|
|
622
669
|
carried,
|
|
623
|
-
func() -> Error: return GdhSceneUids.save_keeping_uid(packed, path)
|
|
624
670
|
)
|
|
625
671
|
if unkept.is_empty():
|
|
626
672
|
also_saved.append_array(also)
|
|
627
673
|
return {}
|
|
628
674
|
return {
|
|
629
|
-
"ok": false,
|
|
675
|
+
"ok": false,
|
|
676
|
+
"error": "Nothing was written: " + unkept + GdhHeadlessCheck.put_back(path, previous),
|
|
630
677
|
}
|
|
631
678
|
|
|
632
679
|
|
|
633
680
|
## Each property [param state] stores differently from the file, on a node the file held, that the
|
|
634
|
-
## call neither asked for nor changed, as {node, property, was, saved}: was left out where the file
|
|
635
|
-
## not set it, saved where the write does not. The engine writes a node as it holds it once the
|
|
636
|
-
## has loaded, and one property can set others as it loads, as anchors_preset gives a centred
|
|
637
|
-
## both grow directions, so a file not written by the editor gains lines on any node, the
|
|
638
|
-
## names among them. What the call's own change moved on that node is its answer's
|
|
681
|
+
## call neither asked for nor changed, as {node, property, was, saved}: was left out where the file
|
|
682
|
+
## did not set it, saved where the write does not. The engine writes a node as it holds it once the
|
|
683
|
+
## file has loaded, and one property can set others as it loads, as anchors_preset gives a centred
|
|
684
|
+
## Control both grow directions, so a file not written by the editor gains lines on any node, the
|
|
685
|
+
## one a set names among them. What the call's own change moved on that node is its answer's
|
|
686
|
+
## alsoChanged.
|
|
639
687
|
func _also_saved(state: SceneState) -> Array[Dictionary]:
|
|
640
688
|
var noted: Array[Dictionary] = []
|
|
641
689
|
var values: GdhSerialisation = GdhSerialisation.new()
|
|
@@ -660,7 +708,7 @@ func _also_saved(state: SceneState) -> Array[Dictionary]:
|
|
|
660
708
|
var held: bool = filed.has(name)
|
|
661
709
|
if held == stored.has(name) and (not held or GdhSame.same(stored[name], filed[name])):
|
|
662
710
|
continue
|
|
663
|
-
var entry: Dictionary = {"node": path_of(node), "property": name}
|
|
711
|
+
var entry: Dictionary = { "node": path_of(node), "property": name }
|
|
664
712
|
if held:
|
|
665
713
|
entry["was"] = values.serialize_value(filed[name])
|
|
666
714
|
if stored.has(name):
|
|
@@ -687,10 +735,15 @@ static func save_packed(packed: PackedScene, scene_path: String) -> Dictionary:
|
|
|
687
735
|
if not DirAccess.dir_exists_absolute(directory):
|
|
688
736
|
var made: Error = DirAccess.make_dir_recursive_absolute(directory)
|
|
689
737
|
if made != OK:
|
|
690
|
-
return {
|
|
738
|
+
return {
|
|
739
|
+
"ok": false, "error": "Could not create %s: %s" % [directory, error_string(made)],
|
|
740
|
+
}
|
|
691
741
|
var saving: Error = GdhSceneUids.save_keeping_uid(packed, scene_path)
|
|
692
742
|
if saving != OK:
|
|
693
|
-
return {
|
|
743
|
+
return {
|
|
744
|
+
"ok": false,
|
|
745
|
+
"error": "Failed to save %s: %s" % [scene_path, error_string(saving)],
|
|
746
|
+
}
|
|
694
747
|
refresh(scene_path)
|
|
695
748
|
return {}
|
|
696
749
|
|
|
@@ -699,7 +752,9 @@ static func save_packed(packed: PackedScene, scene_path: String) -> Dictionary:
|
|
|
699
752
|
## the editor's file list, and the tab showing it, whether current or not.
|
|
700
753
|
static func refresh(scene_path: String) -> void:
|
|
701
754
|
if ResourceLoader.has_cached(scene_path):
|
|
702
|
-
var _replaced: Resource = ResourceLoader.load(
|
|
755
|
+
var _replaced: Resource = ResourceLoader.load(
|
|
756
|
+
scene_path, "", ResourceLoader.CACHE_MODE_REPLACE
|
|
757
|
+
)
|
|
703
758
|
if not Engine.is_editor_hint():
|
|
704
759
|
return
|
|
705
760
|
scan_later()
|
|
@@ -748,11 +803,11 @@ static func unsaved(scene_path: String) -> bool:
|
|
|
748
803
|
|
|
749
804
|
## [param packed] with what the scene's scripts changed put back, as [pack, ""], or [null, why not].
|
|
750
805
|
##
|
|
751
|
-
## On a node the tool did not ask to change, a difference is put back to what the file holds, or,
|
|
752
|
-
## a property the file does not set, to what the node would load with without it. On a node it
|
|
753
|
-
## ask to change, through a script that is not a tool, every property it did not name is put
|
|
754
|
-
## what it held before. Packed again afterwards and put back again until nothing differs,
|
|
755
|
-
## putting one property back can run a setter that changes another already put back (a script
|
|
806
|
+
## On a node the tool did not ask to change, a difference is put back to what the file holds, or,
|
|
807
|
+
## for a property the file does not set, to what the node would load with without it. On a node it
|
|
808
|
+
## did ask to change, through a script that is not a tool, every property it did not name is put
|
|
809
|
+
## back to what it held before. Packed again afterwards and put back again until nothing differs,
|
|
810
|
+
## since putting one property back can run a setter that changes another already put back (a script
|
|
756
811
|
## setting speed in _init, whose setter tints modulate), and refused if something still does.
|
|
757
812
|
func _settled(packed: PackedScene) -> Array:
|
|
758
813
|
var current: PackedScene = packed
|
|
@@ -777,8 +832,10 @@ func _settled(packed: PackedScene) -> Array:
|
|
|
777
832
|
for found: Array in moved:
|
|
778
833
|
var node: Node = found[0]
|
|
779
834
|
var before: Dictionary = _before[node.get_instance_id()]
|
|
835
|
+
var restored: Dictionary = restored_by_write.get_or_add(node.get_instance_id(), {})
|
|
780
836
|
for name: String in found[1]:
|
|
781
837
|
node.set(name, before.get(name, GdhHeadlessCheck.default_of(node, name)))
|
|
838
|
+
restored[name] = true
|
|
782
839
|
current = PackedScene.new()
|
|
783
840
|
var packing: Error = current.pack(root)
|
|
784
841
|
if packing != OK:
|
|
@@ -792,8 +849,8 @@ func _settled(packed: PackedScene) -> Array:
|
|
|
792
849
|
return [null, _stray_text(stray_node, named)]
|
|
793
850
|
|
|
794
851
|
|
|
795
|
-
## Each node the tool asked to change through a script that is not a tool, with the properties it
|
|
796
|
-
## not name that no longer hold what they held before, as [node, names].
|
|
852
|
+
## Each node the tool asked to change through a script that is not a tool, with the properties it
|
|
853
|
+
## did not name that no longer hold what they held before, as [node, names].
|
|
797
854
|
func _moved_by_scripts() -> Array[Array]:
|
|
798
855
|
var moved: Array[Array] = []
|
|
799
856
|
for key: Variant in _asked:
|
|
@@ -855,8 +912,8 @@ func _stray_text(node: Node, name: String) -> String:
|
|
|
855
912
|
return (
|
|
856
913
|
(
|
|
857
914
|
(
|
|
858
|
-
"the script of %s did not start when a headless engine instanced %s, so the
|
|
859
|
-
+ "saved without it; the engine's messages give its errors. "
|
|
915
|
+
"the script of %s did not start when a headless engine instanced %s, so the "
|
|
916
|
+
+ "node would be saved without it; the engine's messages give its errors. "
|
|
860
917
|
)
|
|
861
918
|
% [at, path]
|
|
862
919
|
)
|
|
@@ -865,9 +922,9 @@ func _stray_text(node: Node, name: String) -> String:
|
|
|
865
922
|
return (
|
|
866
923
|
(
|
|
867
924
|
(
|
|
868
|
-
"%s.%s would be saved differently from what %s holds, and the call did not ask for
|
|
869
|
-
+ "scene's scripts ran when a headless engine instanced it, as they do in
|
|
870
|
-
+ "an editor. "
|
|
925
|
+
"%s.%s would be saved differently from what %s holds, and the call did not ask for "
|
|
926
|
+
+ "it: the scene's scripts ran when a headless engine instanced it, as they do in "
|
|
927
|
+
+ "the game and not in an editor. "
|
|
871
928
|
)
|
|
872
929
|
% [at, name, path]
|
|
873
930
|
)
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
@tool
|
|
2
2
|
extends RefCounted
|
|
3
|
-
|
|
4
3
|
## How a scene or resource a tool saves keeps naming files by uid, as the editor's saves do.
|
|
5
4
|
|
|
6
5
|
## The files a headless save named by path alone because the uid cache holds no uid for them,
|
|
@@ -84,12 +83,17 @@ static func note_by_path(named: String) -> void:
|
|
|
84
83
|
|
|
85
84
|
## The uid attribute a reference to [param named] is written with, as ' uid="uid://..."', or "".
|
|
86
85
|
##
|
|
87
|
-
## Only a uid the uid cache gives that path, which is what the loader resolves it by. A uid the
|
|
88
|
-
## does not know (a file made with no editor since the last scan) is answered with a warning
|
|
89
|
-
## load, and one it gives another file (a scene copied whole keeps its original's) loads
|
|
86
|
+
## Only a uid the uid cache gives that path, which is what the loader resolves it by. A uid the
|
|
87
|
+
## cache does not know (a file made with no editor since the last scan) is answered with a warning
|
|
88
|
+
## on every load, and one it gives another file (a scene copied whole keeps its original's) loads
|
|
89
|
+
## that file.
|
|
90
90
|
static func uid_attribute(named: String) -> String:
|
|
91
91
|
var id: int = ResourceLoader.get_resource_uid(named)
|
|
92
|
-
if
|
|
92
|
+
if (
|
|
93
|
+
id == ResourceUID.INVALID_ID
|
|
94
|
+
or not ResourceUID.has_id(id)
|
|
95
|
+
or ResourceUID.get_id_path(id) != named
|
|
96
|
+
):
|
|
93
97
|
return ""
|
|
94
98
|
return ' uid="%s"' % ResourceUID.id_to_text(id)
|
|
95
99
|
|
|
@@ -100,7 +104,9 @@ static func uid_in(file_path: String) -> int:
|
|
|
100
104
|
return ResourceUID.INVALID_ID
|
|
101
105
|
var file: FileAccess = FileAccess.open(file_path, FileAccess.READ)
|
|
102
106
|
if file != null:
|
|
103
|
-
var header: RegExMatch = RegEx.create_from_string('uid="(uid://[a-z0-9]+)"').search(
|
|
107
|
+
var header: RegExMatch = RegEx.create_from_string('uid="(uid://[a-z0-9]+)"').search(
|
|
108
|
+
file.get_line()
|
|
109
|
+
)
|
|
104
110
|
file.close()
|
|
105
111
|
if header != null:
|
|
106
112
|
return ResourceUID.text_to_id(header.get_string(1))
|