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
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
extends RefCounted
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
const
|
|
5
|
-
const
|
|
6
|
-
const
|
|
3
|
+
const GdhRead = preload("reading.gd")
|
|
4
|
+
const GdhLog = preload("logger.gd")
|
|
5
|
+
const GdhProjectFile = preload("project_file.gd")
|
|
6
|
+
const GdhSerialisation = preload("serialisation.gd")
|
|
7
|
+
const GdhShapes = preload("value_shapes.gd")
|
|
7
8
|
|
|
8
|
-
var _log:
|
|
9
|
-
var _values:
|
|
9
|
+
var _log: GdhLog
|
|
10
|
+
var _values: GdhSerialisation = GdhSerialisation.new()
|
|
10
11
|
|
|
11
12
|
|
|
12
|
-
func _init(p_log:
|
|
13
|
+
func _init(p_log: GdhLog) -> void:
|
|
13
14
|
_log = p_log
|
|
14
15
|
|
|
15
16
|
|
|
@@ -76,7 +77,7 @@ func _settings_under(prefix: String) -> Dictionary:
|
|
|
76
77
|
var name: String = str(property.get("name", ""))
|
|
77
78
|
if not name.begins_with(prefix):
|
|
78
79
|
continue
|
|
79
|
-
var kind: int =
|
|
80
|
+
var kind: int = GdhRead.as_int(property.get("type", TYPE_NIL), TYPE_NIL)
|
|
80
81
|
var value: Variant = ProjectSettings.get_setting(name)
|
|
81
82
|
var described: Dictionary = {
|
|
82
83
|
"setting": name,
|
|
@@ -113,8 +114,13 @@ func set_project_setting(params: Dictionary) -> Dictionary:
|
|
|
113
114
|
var default_value: Variant = ProjectSettings.property_get_revert(setting_path)
|
|
114
115
|
var was_default: bool = had_value and default_value != null and old_value == default_value
|
|
115
116
|
|
|
116
|
-
var final_value: Variant = _values.deserialize_value(value)
|
|
117
117
|
var wanted: int = _wanted_type(setting_path, old_value if had_value else null)
|
|
118
|
+
# A value of fields or a colour's html code is fitted as a property write fits it, so a value one
|
|
119
|
+
# of them takes the other takes too. A number is left to the check below, which refuses one the
|
|
120
|
+
# setting would cut where fitting it would cut it.
|
|
121
|
+
var final_value: Variant = _values.deserialize_value(value)
|
|
122
|
+
if final_value is Dictionary or (wanted == TYPE_COLOR and final_value is String):
|
|
123
|
+
final_value = _values.fitted(value, wanted)
|
|
118
124
|
if wanted > TYPE_NIL and final_value != null and typeof(final_value) != wanted:
|
|
119
125
|
if not _converts_faithfully(final_value, wanted):
|
|
120
126
|
return _log.failure(_cannot_hold(setting_path, wanted, final_value))
|
|
@@ -136,7 +142,7 @@ func set_project_setting(params: Dictionary) -> Dictionary:
|
|
|
136
142
|
return answer
|
|
137
143
|
|
|
138
144
|
ProjectSettings.set_setting(setting_path, final_value)
|
|
139
|
-
var err: Error =
|
|
145
|
+
var err: Error = GdhProjectFile.save()
|
|
140
146
|
if err != OK:
|
|
141
147
|
return _log.failure("Failed to save project.godot: " + error_string(err))
|
|
142
148
|
return answer
|
|
@@ -157,7 +163,7 @@ func set_project_setting(params: Dictionary) -> Dictionary:
|
|
|
157
163
|
func _wanted_type(setting_path: String, current: Variant) -> int:
|
|
158
164
|
for info: Dictionary in ProjectSettings.get_property_list():
|
|
159
165
|
if str(info.get("name", "")) == setting_path:
|
|
160
|
-
return
|
|
166
|
+
return GdhRead.as_int(info.get("type", TYPE_NIL), TYPE_NIL)
|
|
161
167
|
return typeof(current)
|
|
162
168
|
|
|
163
169
|
|
|
@@ -181,19 +187,20 @@ static func _cannot_hold(setting_path: String, wanted: int, value: Variant) -> S
|
|
|
181
187
|
if wanted in numbers and typeof(value) in numbers:
|
|
182
188
|
return "%s, and %s is not the same number as one.%s" % [declared, str(value), cost]
|
|
183
189
|
var given: String = type_string(typeof(value))
|
|
184
|
-
|
|
190
|
+
var remedy: String = GdhShapes.remedy(value, wanted)
|
|
191
|
+
return "%s, and %s (%s) cannot become one.%s" % [declared, JSON.stringify(value), given, remedy]
|
|
185
192
|
|
|
186
193
|
|
|
187
194
|
# Add an autoload singleton
|
|
188
195
|
func add_autoload(params: Dictionary) -> Dictionary:
|
|
189
196
|
var name: String = str(params.get("name", ""))
|
|
190
197
|
var path: String = str(params.get("path", ""))
|
|
191
|
-
var global: bool =
|
|
198
|
+
var global: bool = GdhRead.as_bool(params.get("global", true), true)
|
|
192
199
|
|
|
193
200
|
# There is no disabled autoload in Godot 4: every entry in the list is loaded under the root at
|
|
194
201
|
# every start. `enabled: false` used to write the entry without its asterisk and answer that it
|
|
195
202
|
# was disabled, and the autoload ran all the same.
|
|
196
|
-
if not
|
|
203
|
+
if not GdhRead.as_bool(params.get("enabled", true), true):
|
|
197
204
|
return _log.failure(
|
|
198
205
|
(
|
|
199
206
|
"An autoload cannot be added disabled: Godot loads every autoload in the list at every"
|
|
@@ -219,7 +226,7 @@ func add_autoload(params: Dictionary) -> Dictionary:
|
|
|
219
226
|
# "Global Variable" column. Without it the node still comes up under the root; only the name
|
|
220
227
|
# stops resolving.
|
|
221
228
|
ProjectSettings.set_setting(setting, ("*" if global else "") + path)
|
|
222
|
-
var err: Error =
|
|
229
|
+
var err: Error = GdhProjectFile.save()
|
|
223
230
|
if err != OK:
|
|
224
231
|
return _log.failure("Failed to save project.godot: " + error_string(err))
|
|
225
232
|
|
|
@@ -244,7 +251,7 @@ func remove_autoload(params: Dictionary) -> Dictionary:
|
|
|
244
251
|
|
|
245
252
|
var old_value: String = str(ProjectSettings.get_setting(setting))
|
|
246
253
|
ProjectSettings.set_setting(setting, null)
|
|
247
|
-
var err: Error =
|
|
254
|
+
var err: Error = GdhProjectFile.save()
|
|
248
255
|
if err != OK:
|
|
249
256
|
return _log.failure("Failed to save project.godot: " + error_string(err))
|
|
250
257
|
|
|
@@ -293,7 +300,7 @@ func set_main_scene(params: Dictionary) -> Dictionary:
|
|
|
293
300
|
var old_main_scene: String = str(ProjectSettings.get_setting("application/run/main_scene", ""))
|
|
294
301
|
|
|
295
302
|
ProjectSettings.set_setting("application/run/main_scene", scene_path)
|
|
296
|
-
var err: Error =
|
|
303
|
+
var err: Error = GdhProjectFile.save()
|
|
297
304
|
|
|
298
305
|
if err != OK:
|
|
299
306
|
return _log.failure("Failed to save project settings: " + str(err))
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
extends RefCounted
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
const
|
|
5
|
-
const
|
|
3
|
+
const GdhFileWalk = preload("file_walk.gd")
|
|
4
|
+
const GdhImportPipeline = preload("import_pipeline.gd")
|
|
5
|
+
const GdhLog = preload("logger.gd")
|
|
6
6
|
|
|
7
|
-
var _log:
|
|
8
|
-
var _files:
|
|
7
|
+
var _log: GdhLog
|
|
8
|
+
var _files: GdhFileWalk = GdhFileWalk.new()
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
func _init(p_log:
|
|
11
|
+
func _init(p_log: GdhLog) -> void:
|
|
12
12
|
_log = p_log
|
|
13
13
|
|
|
14
14
|
|
|
@@ -44,7 +44,7 @@ func get_project_health(params: Dictionary) -> Dictionary:
|
|
|
44
44
|
# Every format the engine imports, the list the import status reads: five of them were checked
|
|
45
45
|
# here, so a project of glTF models, fonts and WebP textures had nothing to reimport.
|
|
46
46
|
var resources: Array[String] = _files.find_files_with_extensions(
|
|
47
|
-
"res://",
|
|
47
|
+
"res://", GdhImportPipeline.IMPORTABLE_EXTENSIONS
|
|
48
48
|
)
|
|
49
49
|
|
|
50
50
|
var missing_imports: int = 0
|
|
@@ -119,8 +119,8 @@ func get_project_health(params: Dictionary) -> Dictionary:
|
|
|
119
119
|
if main_scene.is_empty():
|
|
120
120
|
details.append("No main scene configured")
|
|
121
121
|
deductions += 10
|
|
122
|
-
elif not
|
|
123
|
-
details.append("Main scene " +
|
|
122
|
+
elif not GdhFileWalk.missing_reason(main_scene).is_empty():
|
|
123
|
+
details.append("Main scene " + GdhFileWalk.missing_reason(main_scene))
|
|
124
124
|
deductions += 15
|
|
125
125
|
|
|
126
126
|
var project_name: String = str(ProjectSettings.get_setting("application/config/name", ""))
|
|
@@ -14,8 +14,9 @@ extends RefCounted
|
|
|
14
14
|
## write accepts. What is here on top of it is the part only a property knows: the type wanted, so a
|
|
15
15
|
## dictionary shaped like a vector or a bare pair of numbers can be read as one.
|
|
16
16
|
|
|
17
|
-
const
|
|
18
|
-
const
|
|
17
|
+
const GdhRead = preload("reading.gd")
|
|
18
|
+
const GdhSerialisation = preload("serialisation.gd")
|
|
19
|
+
const GdhShapes = preload("value_shapes.gd")
|
|
19
20
|
|
|
20
21
|
# The data types a Theme names its items by, as in "Button/colors/font_color", and what each holds.
|
|
21
22
|
const THEME_ITEMS: Dictionary[String, Array] = {
|
|
@@ -65,12 +66,18 @@ const FLOAT_SHAPES: Array[int] = [
|
|
|
65
66
|
# names, so a write to "Visibility" or "Transform" changed nothing and was answered as made.
|
|
66
67
|
const HEADINGS: int = PROPERTY_USAGE_CATEGORY | PROPERTY_USAGE_GROUP | PROPERTY_USAGE_SUBGROUP
|
|
67
68
|
|
|
68
|
-
var _values:
|
|
69
|
+
var _values: GdhSerialisation = GdhSerialisation.new()
|
|
69
70
|
|
|
70
71
|
# Why the value being parsed cannot be written, when the parse is what found out. A resource is
|
|
71
72
|
# built inside the parse and there is nowhere in a parsed value to put the reason it is wrong.
|
|
72
73
|
var _refused: String = ""
|
|
73
74
|
|
|
75
|
+
# The stored properties the last write_all changed without being given them, by name, as [what it
|
|
76
|
+
# held before, what it holds now]. A setter keeps other properties in step with the one it is given:
|
|
77
|
+
# a Gradient given a third colour gives that point an offset of 0, a Control given a size moves its
|
|
78
|
+
# offsets. An answer naming only what was given leaves the caller to guess the rest.
|
|
79
|
+
var _also_changed: Dictionary = {}
|
|
80
|
+
|
|
74
81
|
|
|
75
82
|
## Turns what arrived over the wire into the Godot value a property wants.
|
|
76
83
|
##
|
|
@@ -85,7 +92,7 @@ func parse(value: Variant, expected_type: int = TYPE_NIL, elements: Array = [])
|
|
|
85
92
|
var fields: Dictionary = value
|
|
86
93
|
# A dictionary with keys that are not text arrives as its entries, and is read against the
|
|
87
94
|
# declared element types like any other.
|
|
88
|
-
if str(fields.get("_type", "")) == "Dictionary" and
|
|
95
|
+
if str(fields.get("_type", "")) == "Dictionary" and GdhSerialisation.pairs(fields.get("entries")):
|
|
89
96
|
var built: Dictionary = _values.deserialize_value(fields)
|
|
90
97
|
return _parse_entries(built, elements)
|
|
91
98
|
var tagged: Array = _parse_tagged_dictionary(fields)
|
|
@@ -113,38 +120,39 @@ func cannot_hold(
|
|
|
113
120
|
) -> String:
|
|
114
121
|
var held: String = type_string(expected_type)
|
|
115
122
|
var cost: String = " Setting it would save a different value than the one asked for."
|
|
116
|
-
if not
|
|
123
|
+
if not GdhSerialisation.acceptable(value, expected_type):
|
|
117
124
|
var shape: String = "%s.%s is %s and the value given is %s, which cannot become one."
|
|
118
|
-
|
|
125
|
+
var remedy: String = GdhShapes.remedy(value, expected_type)
|
|
126
|
+
return shape % [holder, property, held, type_string(typeof(value))] + remedy + cost
|
|
119
127
|
|
|
120
128
|
# And the same question of each element, because a list is accepted for a packed or typed array
|
|
121
129
|
# whatever is in it: a polygon written as [{"x": 1}] passes the check above and lands as one zero
|
|
122
130
|
# vector, and [1.5, "x"] for a PackedInt32Array as [1, 0].
|
|
123
|
-
var element: int =
|
|
131
|
+
var element: int = GdhRead.as_int(PACKED_ELEMENTS.get(expected_type, TYPE_NIL), TYPE_NIL)
|
|
124
132
|
if expected_type == TYPE_ARRAY and not elements.is_empty():
|
|
125
|
-
element =
|
|
133
|
+
element = GdhRead.as_int(elements[0], TYPE_NIL)
|
|
126
134
|
if element != TYPE_NIL and value is Array:
|
|
127
135
|
var items: Array = value
|
|
128
136
|
for index: int in items.size():
|
|
129
|
-
if not
|
|
137
|
+
if not GdhSerialisation.acceptable(items[index], element):
|
|
130
138
|
var shape: String = "%s.%s is %s and item %d of the list given is %s, not %s."
|
|
131
139
|
var named: Array = [
|
|
132
140
|
holder, property, held, index, type_string(typeof(items[index])), type_string(element)
|
|
133
141
|
]
|
|
134
|
-
return shape % named + cost
|
|
142
|
+
return shape % named + GdhShapes.remedy(items[index], element) + cost
|
|
135
143
|
if expected_type == TYPE_DICTIONARY and elements.size() == 2 and value is Dictionary:
|
|
136
144
|
var entries: Dictionary = value
|
|
137
|
-
var key_type: int =
|
|
138
|
-
var value_type: int =
|
|
145
|
+
var key_type: int = GdhRead.as_int(elements[0], TYPE_NIL)
|
|
146
|
+
var value_type: int = GdhRead.as_int(elements[1], TYPE_NIL)
|
|
139
147
|
for key: Variant in entries:
|
|
140
|
-
if not
|
|
148
|
+
if not GdhSerialisation.acceptable(key, key_type):
|
|
141
149
|
var shape: String = "%s.%s has %s keys, and %s is not one."
|
|
142
150
|
return shape % [holder, property, type_string(key_type), shown(key)] + cost
|
|
143
151
|
var entry: Variant = entries[key]
|
|
144
|
-
if not
|
|
152
|
+
if not GdhSerialisation.acceptable(entry, value_type):
|
|
145
153
|
var shape: String = "%s.%s holds %s values, and the one given for %s is %s."
|
|
146
154
|
var named: Array = [holder, property, type_string(value_type), shown(key), shown(entry)]
|
|
147
|
-
return shape % named + cost
|
|
155
|
+
return shape % named + GdhShapes.remedy(entry, value_type) + cost
|
|
148
156
|
return ""
|
|
149
157
|
|
|
150
158
|
|
|
@@ -168,6 +176,11 @@ func cannot_hold(
|
|
|
168
176
|
## Nothing here saves, so a refusal partway leaves the change in memory and the file as it was. The
|
|
169
177
|
## caller returns the refusal instead of saving.
|
|
170
178
|
func write_all(target: Object, properties: Dictionary) -> String:
|
|
179
|
+
_also_changed = {}
|
|
180
|
+
var named: Dictionary = {}
|
|
181
|
+
for key: Variant in properties:
|
|
182
|
+
named[str(key).get_slice(":", 0)] = true
|
|
183
|
+
var before: Dictionary = _stored(target, named)
|
|
171
184
|
var given: Dictionary = {}
|
|
172
185
|
for key: Variant in properties:
|
|
173
186
|
var property: String = str(key)
|
|
@@ -197,9 +210,62 @@ func write_all(target: Object, properties: Dictionary) -> String:
|
|
|
197
210
|
)
|
|
198
211
|
% [target.get_class(), property, shown(given[property]), shown(held)]
|
|
199
212
|
)
|
|
213
|
+
for property: String in before:
|
|
214
|
+
var now: Variant = target.get(property)
|
|
215
|
+
if not same(now, before[property]):
|
|
216
|
+
_also_changed[property] = [before[property], now]
|
|
200
217
|
return ""
|
|
201
218
|
|
|
202
219
|
|
|
220
|
+
## Puts under alsoChanged in [param answer] each property the last [method write_all] changed
|
|
221
|
+
## without being given it, as {was, now}, when there are any. A node is named by its path from
|
|
222
|
+
## [param relative_to], as a scene answer names one. Called before the scene is written, since the
|
|
223
|
+
## write frees the nodes these values can hold.
|
|
224
|
+
func note_also_changed(answer: Dictionary, relative_to: Node = null) -> void:
|
|
225
|
+
if _also_changed.is_empty():
|
|
226
|
+
return
|
|
227
|
+
var noted: Dictionary = {}
|
|
228
|
+
for property: Variant in _also_changed:
|
|
229
|
+
var pair: Array = _also_changed[property]
|
|
230
|
+
noted[property] = {"was": _readable(pair[0], relative_to), "now": _readable(pair[1], relative_to)}
|
|
231
|
+
answer["alsoChanged"] = noted
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
func _readable(value: Variant, relative_to: Node) -> Variant:
|
|
235
|
+
if value is Node and relative_to != null:
|
|
236
|
+
var target: Node = value
|
|
237
|
+
return _values.serialize_value(relative_to.get_path_to(target))
|
|
238
|
+
if value is Array:
|
|
239
|
+
var items: Array = value
|
|
240
|
+
return items.map(func(item: Variant) -> Variant: return _readable(item, relative_to))
|
|
241
|
+
return _values.serialize_value(value)
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
## What [param target] stores, by name, but its script and the properties in [param leave]. A list
|
|
245
|
+
## or a map is copied, since a setter can change the one it holds in place.
|
|
246
|
+
func _stored(target: Object, leave: Dictionary) -> Dictionary:
|
|
247
|
+
var held: Dictionary = {}
|
|
248
|
+
for entry: Dictionary in target.get_property_list():
|
|
249
|
+
var name: String = str(entry["name"])
|
|
250
|
+
var usage: int = GdhRead.as_int(entry["usage"])
|
|
251
|
+
if (
|
|
252
|
+
usage & PROPERTY_USAGE_STORAGE == 0
|
|
253
|
+
or usage & HEADINGS != 0
|
|
254
|
+
or name == "script"
|
|
255
|
+
or leave.has(name)
|
|
256
|
+
):
|
|
257
|
+
continue
|
|
258
|
+
var value: Variant = target.get(name)
|
|
259
|
+
if value is Array:
|
|
260
|
+
var listed: Array = value
|
|
261
|
+
value = listed.duplicate(true)
|
|
262
|
+
elif value is Dictionary:
|
|
263
|
+
var mapped: Dictionary = value
|
|
264
|
+
value = mapped.duplicate(true)
|
|
265
|
+
held[name] = value
|
|
266
|
+
return held
|
|
267
|
+
|
|
268
|
+
|
|
203
269
|
## What [param target] holds under [param key], a property or a colon path into one's components.
|
|
204
270
|
func read_at(target: Object, key: String) -> Variant:
|
|
205
271
|
if ":" in key and declaration(target, key).is_empty():
|
|
@@ -228,9 +294,8 @@ func _component_slot(target: Object, path: String) -> Dictionary:
|
|
|
228
294
|
var walked: String = steps[0]
|
|
229
295
|
for index: int in range(1, steps.size()):
|
|
230
296
|
var step: String = steps[index]
|
|
231
|
-
var has: PackedStringArray =
|
|
232
|
-
var
|
|
233
|
-
var sort: String = ("an " if kind.left(1).to_upper() in ["A", "E", "I", "O", "U"] else "a ") + kind
|
|
297
|
+
var has: PackedStringArray = GdhSerialisation.components_of(value)
|
|
298
|
+
var sort: String = GdhRead.with_article(type_string(typeof(value)))
|
|
234
299
|
if has.is_empty():
|
|
235
300
|
return {
|
|
236
301
|
"refused":
|
|
@@ -255,8 +320,8 @@ func _component_slot(target: Object, path: String) -> Dictionary:
|
|
|
255
320
|
## [param raw] as the value [param declared] takes, as [ok, value] or [false, why not].
|
|
256
321
|
func value_for(target: Object, declared: Dictionary, raw: Variant) -> Array:
|
|
257
322
|
var property: String = str(declared.get("name", ""))
|
|
258
|
-
var expected_type: int =
|
|
259
|
-
var hint: int =
|
|
323
|
+
var expected_type: int = GdhRead.as_int(declared.get("type", TYPE_NIL), TYPE_NIL)
|
|
324
|
+
var hint: int = GdhRead.as_int(declared.get("hint", PROPERTY_HINT_NONE))
|
|
260
325
|
if expected_type == TYPE_OBJECT and hint == PROPERTY_HINT_NODE_TYPE:
|
|
261
326
|
return _node_reference(target, property, raw)
|
|
262
327
|
|
|
@@ -272,7 +337,7 @@ func value_for(target: Object, declared: Dictionary, raw: Variant) -> Array:
|
|
|
272
337
|
var elements: Array = elements_of(declared)
|
|
273
338
|
if _fraction(raw, expected_type, elements):
|
|
274
339
|
return [false, _fraction_refusal(target, property, raw)]
|
|
275
|
-
var rounded: String =
|
|
340
|
+
var rounded: String = GdhSerialisation.inexact(raw, expected_type)
|
|
276
341
|
if not rounded.is_empty():
|
|
277
342
|
return [false, "%s.%s holds int: %s." % [target.get_class(), property, rounded]]
|
|
278
343
|
_refused = ""
|
|
@@ -328,7 +393,7 @@ func _node_reference(target: Object, property: String, raw: Variant) -> Array:
|
|
|
328
393
|
## an object of the wrong class: measured on 4.7.2, a Sprite2D given a Material for its texture
|
|
329
394
|
## held nothing afterwards, and the scene was saved without the texture it had.
|
|
330
395
|
func _wrong_class(value: Object, declared: Dictionary) -> String:
|
|
331
|
-
var hint: int =
|
|
396
|
+
var hint: int = GdhRead.as_int(declared.get("hint", PROPERTY_HINT_NONE))
|
|
332
397
|
var wanted: String = str(declared.get("hint_string", ""))
|
|
333
398
|
if hint != PROPERTY_HINT_RESOURCE_TYPE or wanted.is_empty():
|
|
334
399
|
return ""
|
|
@@ -354,8 +419,8 @@ func _wrong_class(value: Object, declared: Dictionary) -> String:
|
|
|
354
419
|
## behind a placeholder in the editor, and measured on 4.7.2 that placeholder stores [1.5, 2.0]
|
|
355
420
|
## as given, which the game then fails to load into the typed array.
|
|
356
421
|
static func elements_of(declared: Dictionary) -> Array:
|
|
357
|
-
var type: int =
|
|
358
|
-
var hint: int =
|
|
422
|
+
var type: int = GdhRead.as_int(declared.get("type", TYPE_NIL), TYPE_NIL)
|
|
423
|
+
var hint: int = GdhRead.as_int(declared.get("hint", PROPERTY_HINT_NONE))
|
|
359
424
|
var wanted: String = str(declared.get("hint_string", ""))
|
|
360
425
|
if wanted.is_empty():
|
|
361
426
|
return []
|
|
@@ -397,21 +462,21 @@ func _fraction(value: Variant, type: int, elements: Array = []) -> bool:
|
|
|
397
462
|
if type == TYPE_INT:
|
|
398
463
|
var number: Variant = value
|
|
399
464
|
if typeof(value) == TYPE_STRING:
|
|
400
|
-
number =
|
|
465
|
+
number = GdhRead.json_or_null(str(value))
|
|
401
466
|
if typeof(number) != TYPE_FLOAT:
|
|
402
467
|
return false
|
|
403
468
|
var whole: float = number
|
|
404
469
|
return is_finite(whole) and whole != floorf(whole)
|
|
405
|
-
var element: int =
|
|
470
|
+
var element: int = GdhRead.as_int(PACKED_ELEMENTS.get(type, TYPE_NIL), TYPE_NIL)
|
|
406
471
|
if type == TYPE_ARRAY and not elements.is_empty():
|
|
407
|
-
element =
|
|
472
|
+
element = GdhRead.as_int(elements[0], TYPE_NIL)
|
|
408
473
|
if value is Array and element != TYPE_NIL:
|
|
409
474
|
var items: Array = value
|
|
410
475
|
return items.any(func(item: Variant) -> bool: return _fraction(item, element))
|
|
411
476
|
if type == TYPE_DICTIONARY and elements.size() == 2 and value is Dictionary:
|
|
412
477
|
var entries: Dictionary = value
|
|
413
|
-
var key_type: int =
|
|
414
|
-
var value_type: int =
|
|
478
|
+
var key_type: int = GdhRead.as_int(elements[0], TYPE_NIL)
|
|
479
|
+
var value_type: int = GdhRead.as_int(elements[1], TYPE_NIL)
|
|
415
480
|
for key: Variant in entries:
|
|
416
481
|
if _fraction(key, key_type) or _fraction(entries[key], value_type):
|
|
417
482
|
return true
|
|
@@ -463,7 +528,7 @@ func declaration(target: Object, property: String) -> Dictionary:
|
|
|
463
528
|
for declared: Dictionary in target.get_property_list():
|
|
464
529
|
if str(declared.get("name", "")) != property:
|
|
465
530
|
continue
|
|
466
|
-
if
|
|
531
|
+
if GdhRead.as_int(declared.get("usage", 0)) & HEADINGS:
|
|
467
532
|
continue
|
|
468
533
|
return declared
|
|
469
534
|
if target is Theme:
|
|
@@ -503,13 +568,13 @@ static func same(held: Variant, given: Variant) -> bool:
|
|
|
503
568
|
return held_none == given_none
|
|
504
569
|
if held_type == TYPE_OBJECT or given_type == TYPE_OBJECT:
|
|
505
570
|
return _same_object(held, given)
|
|
506
|
-
if
|
|
571
|
+
if GdhSerialisation.NUMBERS.has(held_type) and GdhSerialisation.NUMBERS.has(given_type):
|
|
507
572
|
if held_type == TYPE_BOOL or given_type == TYPE_BOOL:
|
|
508
573
|
return held_type == given_type and held == given
|
|
509
|
-
var held_number: float =
|
|
510
|
-
var given_number: float =
|
|
574
|
+
var held_number: float = GdhRead.as_float(held)
|
|
575
|
+
var given_number: float = GdhRead.as_float(given)
|
|
511
576
|
return (is_nan(held_number) and is_nan(given_number)) or is_equal_approx(held_number, given_number)
|
|
512
|
-
if
|
|
577
|
+
if GdhSerialisation.TEXTS.has(held_type) and GdhSerialisation.TEXTS.has(given_type):
|
|
513
578
|
return str(held) == str(given)
|
|
514
579
|
if _listed(held_type) and _listed(given_type):
|
|
515
580
|
var held_items: Array = type_convert(held, TYPE_ARRAY)
|
|
@@ -675,7 +740,7 @@ func _parse_shaped_dictionary(value: Dictionary, expected_type: int, elements: A
|
|
|
675
740
|
if expected_type == TYPE_NIL or expected_type == TYPE_DICTIONARY:
|
|
676
741
|
return _parse_entries(value, elements)
|
|
677
742
|
var tag: String = type_string(expected_type)
|
|
678
|
-
if
|
|
743
|
+
if GdhShapes.REQUIRED.has(tag):
|
|
679
744
|
var tagged: Dictionary = value.duplicate()
|
|
680
745
|
tagged["_type"] = tag
|
|
681
746
|
var built: Variant = _values.deserialize_value(tagged)
|
|
@@ -690,8 +755,8 @@ func _parse_entries(value: Dictionary, elements: Array) -> Dictionary:
|
|
|
690
755
|
var key_type: int = TYPE_NIL
|
|
691
756
|
var value_type: int = TYPE_NIL
|
|
692
757
|
if elements.size() == 2:
|
|
693
|
-
key_type =
|
|
694
|
-
value_type =
|
|
758
|
+
key_type = GdhRead.as_int(elements[0], TYPE_NIL)
|
|
759
|
+
value_type = GdhRead.as_int(elements[1], TYPE_NIL)
|
|
695
760
|
var entries: Dictionary = {}
|
|
696
761
|
for key: Variant in value:
|
|
697
762
|
entries[_values.fitted(key, key_type)] = parse(value[key], value_type)
|
|
@@ -709,7 +774,7 @@ func _typed_dictionary(entries: Dictionary, key_type: int, value_type: int) -> D
|
|
|
709
774
|
for key: Variant in entries:
|
|
710
775
|
if key_type != TYPE_NIL and typeof(key) != key_type:
|
|
711
776
|
return entries
|
|
712
|
-
if not
|
|
777
|
+
if not GdhSerialisation.acceptable(entries[key], value_type):
|
|
713
778
|
return entries
|
|
714
779
|
var fitted: Dictionary = {}
|
|
715
780
|
for key: Variant in entries:
|
|
@@ -720,46 +785,48 @@ func _typed_dictionary(entries: Dictionary, key_type: int, value_type: int) -> D
|
|
|
720
785
|
## An array, either positional for a vector the property declares, or a list to parse per item.
|
|
721
786
|
func _parse_array(value: Array, expected_type: int, elements: Array) -> Variant:
|
|
722
787
|
if PACKED_ELEMENTS.has(expected_type):
|
|
723
|
-
var element: int =
|
|
788
|
+
var element: int = GdhRead.as_int(PACKED_ELEMENTS[expected_type], TYPE_NIL)
|
|
724
789
|
return value.map(func(item: Variant) -> Variant: return parse(item, element))
|
|
725
790
|
|
|
726
791
|
match expected_type:
|
|
727
792
|
TYPE_VECTOR2:
|
|
728
793
|
if value.size() == 2:
|
|
729
|
-
return Vector2(
|
|
794
|
+
return Vector2(GdhRead.as_float(value[0]), GdhRead.as_float(value[1]))
|
|
730
795
|
TYPE_VECTOR2I:
|
|
731
796
|
if value.size() == 2:
|
|
732
|
-
return Vector2i(
|
|
797
|
+
return Vector2i(GdhRead.as_int(value[0]), GdhRead.as_int(value[1]))
|
|
733
798
|
TYPE_VECTOR3:
|
|
734
799
|
if value.size() == 3:
|
|
735
|
-
return Vector3(
|
|
800
|
+
return Vector3(
|
|
801
|
+
GdhRead.as_float(value[0]), GdhRead.as_float(value[1]), GdhRead.as_float(value[2])
|
|
802
|
+
)
|
|
736
803
|
TYPE_VECTOR3I:
|
|
737
804
|
if value.size() == 3:
|
|
738
|
-
return Vector3i(
|
|
805
|
+
return Vector3i(GdhRead.as_int(value[0]), GdhRead.as_int(value[1]), GdhRead.as_int(value[2]))
|
|
739
806
|
TYPE_VECTOR4:
|
|
740
807
|
if value.size() == 4:
|
|
741
808
|
return Vector4(
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
809
|
+
GdhRead.as_float(value[0]),
|
|
810
|
+
GdhRead.as_float(value[1]),
|
|
811
|
+
GdhRead.as_float(value[2]),
|
|
812
|
+
GdhRead.as_float(value[3])
|
|
746
813
|
)
|
|
747
814
|
TYPE_COLOR:
|
|
748
815
|
if value.size() == 3 or value.size() == 4:
|
|
749
816
|
return Color(
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
817
|
+
GdhRead.as_float(value[0]),
|
|
818
|
+
GdhRead.as_float(value[1]),
|
|
819
|
+
GdhRead.as_float(value[2]),
|
|
820
|
+
GdhRead.as_float(value[3]) if value.size() == 4 else 1.0
|
|
754
821
|
)
|
|
755
822
|
|
|
756
823
|
var element_type: int = TYPE_NIL
|
|
757
824
|
if expected_type == TYPE_ARRAY and not elements.is_empty():
|
|
758
|
-
element_type =
|
|
825
|
+
element_type = GdhRead.as_int(elements[0], TYPE_NIL)
|
|
759
826
|
var items: Array = value.map(func(item: Variant) -> Variant: return parse(item, element_type))
|
|
760
827
|
if element_type == TYPE_NIL or element_type == TYPE_OBJECT:
|
|
761
828
|
return items
|
|
762
|
-
if not items.all(func(item: Variant) -> bool: return
|
|
829
|
+
if not items.all(func(item: Variant) -> bool: return GdhSerialisation.acceptable(item, element_type)):
|
|
763
830
|
return items
|
|
764
831
|
var fitted: Array = items.map(func(item: Variant) -> Variant: return type_convert(item, element_type))
|
|
765
832
|
return Array(fitted, element_type, &"", null)
|
|
@@ -139,3 +139,31 @@ static func missing_below(
|
|
|
139
139
|
"its children are " + shown if not children.is_empty() else "it has no children",
|
|
140
140
|
]
|
|
141
141
|
)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
## [param said], the refusal for a [param kind] the scene does not have at [param node_path], with
|
|
145
|
+
## what is there when the scene has a node there, [param found] its class, and [param holders], the
|
|
146
|
+
## paths of the nodes in the scene that are one, so the caller has the path to pass. Names up to
|
|
147
|
+
## [constant NAMED_CHILDREN] of them.
|
|
148
|
+
static func not_of_kind(
|
|
149
|
+
said: String, node_path: String, found: String, kind: String, holders: PackedStringArray
|
|
150
|
+
) -> String:
|
|
151
|
+
var told: String = said + ("" if said.ends_with(".") else ".")
|
|
152
|
+
if not found.is_empty():
|
|
153
|
+
told += ' The node at "%s" is %s, not %s.' % [node_path, with_article(found), with_article(kind)]
|
|
154
|
+
if holders.is_empty():
|
|
155
|
+
return told + " The scene holds no %s." % kind
|
|
156
|
+
var quoted: PackedStringArray = []
|
|
157
|
+
for holder: String in holders.slice(0, NAMED_CHILDREN):
|
|
158
|
+
var _quoted: bool = quoted.append('"%s"' % holder)
|
|
159
|
+
var shown: String = ", ".join(quoted)
|
|
160
|
+
if holders.size() > NAMED_CHILDREN:
|
|
161
|
+
shown += " and %d more" % (holders.size() - NAMED_CHILDREN)
|
|
162
|
+
if holders.size() == 1:
|
|
163
|
+
return told + " The scene's %s is at %s." % [kind, shown]
|
|
164
|
+
return told + " The scene's %ss are at %s." % [kind, shown]
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
## [param word] after the article it takes, "an AnimationTree" or "a Node2D".
|
|
168
|
+
static func with_article(word: String) -> String:
|
|
169
|
+
return ("an " if word.left(1).to_upper() in ["A", "E", "I", "O", "U"] else "a ") + word
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
extends RefCounted
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
const
|
|
3
|
+
const GdhLog = preload("logger.gd")
|
|
4
|
+
const GdhPatterns = preload("patterns.gd")
|
|
5
5
|
|
|
6
6
|
## Where the engine keeps a file's UID, by what kind of file it is. Measured on 4.7.2: a script or
|
|
7
7
|
## shader has a `.uid` file beside it, an imported file has it in its `.import`, and a text scene or
|
|
@@ -10,10 +10,10 @@ const Patterns = preload("patterns.gd")
|
|
|
10
10
|
const SIDECAR_KINDS: Array[String] = ["gd", "gdshader", "gdshaderinc", "cs"]
|
|
11
11
|
const TEXT_RESOURCE_KINDS: Array[String] = ["tscn", "tres", "escn"]
|
|
12
12
|
|
|
13
|
-
var _log:
|
|
13
|
+
var _log: GdhLog
|
|
14
14
|
|
|
15
15
|
|
|
16
|
-
func _init(p_log:
|
|
16
|
+
func _init(p_log: GdhLog) -> void:
|
|
17
17
|
_log = p_log
|
|
18
18
|
|
|
19
19
|
|
|
@@ -59,7 +59,7 @@ static func uid_of(file_path: String) -> Dictionary:
|
|
|
59
59
|
if file != null:
|
|
60
60
|
var header: String = file.get_line()
|
|
61
61
|
file.close()
|
|
62
|
-
var matched: RegExMatch =
|
|
62
|
+
var matched: RegExMatch = GdhPatterns.compiled('\\buid="(uid://[^"]+)"').search(header)
|
|
63
63
|
if matched != null:
|
|
64
64
|
return {"uid": matched.get_string(1), "from": "the file's header"}
|
|
65
65
|
var id: int = ResourceLoader.get_resource_uid(file_path)
|
|
@@ -13,17 +13,17 @@ extends RefCounted
|
|
|
13
13
|
## reaches, and every script loaded after it that depends on it compiles in silence, reload() and all.
|
|
14
14
|
## So a run ends at the first script that raised anything, and the rest go to a fresh engine.
|
|
15
15
|
|
|
16
|
-
const
|
|
17
|
-
const
|
|
16
|
+
const GdhRead = preload("reading.gd")
|
|
17
|
+
const GdhLog = preload("logger.gd")
|
|
18
18
|
|
|
19
19
|
## Where the engine raises a script's compile errors from.
|
|
20
20
|
const RELOAD: String = "GDScript::reload"
|
|
21
21
|
|
|
22
|
-
var _log:
|
|
22
|
+
var _log: GdhLog
|
|
23
23
|
|
|
24
24
|
|
|
25
25
|
## Every compile error the engine raises while it is attached, in order.
|
|
26
|
-
class
|
|
26
|
+
class GdhCaught:
|
|
27
27
|
extends Logger
|
|
28
28
|
|
|
29
29
|
var raised: Array[Dictionary] = []
|
|
@@ -50,7 +50,7 @@ class Caught:
|
|
|
50
50
|
)
|
|
51
51
|
|
|
52
52
|
|
|
53
|
-
func _init(p_log:
|
|
53
|
+
func _init(p_log: GdhLog) -> void:
|
|
54
54
|
_log = p_log
|
|
55
55
|
|
|
56
56
|
|
|
@@ -62,7 +62,7 @@ func _init(p_log: Log) -> void:
|
|
|
62
62
|
## resource cache, so a script loaded for an earlier one is compiled again for its own entry.
|
|
63
63
|
func check_scripts(params: Dictionary) -> Dictionary:
|
|
64
64
|
var asked: Array = params.get("paths", [])
|
|
65
|
-
var caught:
|
|
65
|
+
var caught: GdhCaught = GdhCaught.new()
|
|
66
66
|
OS.add_logger(caught)
|
|
67
67
|
var checked: Array[String] = []
|
|
68
68
|
var failed: Array[String] = []
|
|
@@ -80,7 +80,7 @@ func check_scripts(params: Dictionary) -> Dictionary:
|
|
|
80
80
|
if not at in others:
|
|
81
81
|
others.append(at)
|
|
82
82
|
continue
|
|
83
|
-
var found: Dictionary = {"line":
|
|
83
|
+
var found: Dictionary = {"line": GdhRead.as_int(error["line"]), "message": str(error["message"])}
|
|
84
84
|
if not found in errors:
|
|
85
85
|
errors.append(found)
|
|
86
86
|
if loaded == null and errors.is_empty():
|