gdharness 1.2.42 → 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 +571 -363
- package/build/godot/addons/gdharness_editor/animation_reads.gd +7 -19
- package/build/godot/addons/gdharness_editor/bridge_client.gd +9 -9
- 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 +124 -62
- 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 +57 -50
- 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 +82 -78
- 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 +32 -32
- package/build/godot/addons/gdharness_runtime/runtime_capture.gd +50 -18
- package/build/godot/addons/gdharness_runtime/runtime_changes.gd +86 -80
- package/build/godot/addons/gdharness_runtime/runtime_input.gd +65 -69
- 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 +13 -21
- 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 +124 -62
- 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/build/keeper.js +10 -4
- package/package.json +1 -2
- package/build/index.js +0 -45101
|
@@ -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)
|
|
@@ -600,18 +665,13 @@ func shown(value: Variant) -> String:
|
|
|
600
665
|
## not mine". A tag that names a type but lacks the keys to build it is left unhandled on purpose,
|
|
601
666
|
## so the caller can still read it against the type the property declares.
|
|
602
667
|
func _parse_tagged_dictionary(value: Dictionary) -> Array:
|
|
603
|
-
var type_tag: String = str(value.get("_type",
|
|
668
|
+
var type_tag: String = str(value.get("_type", ""))
|
|
604
669
|
if type_tag == "Resource":
|
|
605
670
|
return _parse_resource_tag(value)
|
|
606
671
|
|
|
607
672
|
# The shared builder is the same one the answer was written by, so every shape it writes comes
|
|
608
|
-
# back as itself.
|
|
609
|
-
|
|
610
|
-
var tagged: Dictionary = value
|
|
611
|
-
if not value.has("_type") and not type_tag.is_empty():
|
|
612
|
-
tagged = value.duplicate()
|
|
613
|
-
tagged["_type"] = type_tag
|
|
614
|
-
var rebuilt: Variant = _values.deserialize_value(tagged)
|
|
673
|
+
# back as itself.
|
|
674
|
+
var rebuilt: Variant = _values.deserialize_value(value)
|
|
615
675
|
if not rebuilt is Dictionary:
|
|
616
676
|
return [true, rebuilt]
|
|
617
677
|
|
|
@@ -660,7 +720,7 @@ func _parse_new_resource(type_tag: String, value: Dictionary) -> Array:
|
|
|
660
720
|
var wanted: Dictionary = {}
|
|
661
721
|
for key: Variant in value:
|
|
662
722
|
var property: String = str(key)
|
|
663
|
-
if property != "_type"
|
|
723
|
+
if property != "_type":
|
|
664
724
|
wanted[property] = value[key]
|
|
665
725
|
var refused: String = write_all(built, wanted)
|
|
666
726
|
if not refused.is_empty():
|
|
@@ -680,7 +740,7 @@ func _parse_shaped_dictionary(value: Dictionary, expected_type: int, elements: A
|
|
|
680
740
|
if expected_type == TYPE_NIL or expected_type == TYPE_DICTIONARY:
|
|
681
741
|
return _parse_entries(value, elements)
|
|
682
742
|
var tag: String = type_string(expected_type)
|
|
683
|
-
if
|
|
743
|
+
if GdhShapes.REQUIRED.has(tag):
|
|
684
744
|
var tagged: Dictionary = value.duplicate()
|
|
685
745
|
tagged["_type"] = tag
|
|
686
746
|
var built: Variant = _values.deserialize_value(tagged)
|
|
@@ -695,8 +755,8 @@ func _parse_entries(value: Dictionary, elements: Array) -> Dictionary:
|
|
|
695
755
|
var key_type: int = TYPE_NIL
|
|
696
756
|
var value_type: int = TYPE_NIL
|
|
697
757
|
if elements.size() == 2:
|
|
698
|
-
key_type =
|
|
699
|
-
value_type =
|
|
758
|
+
key_type = GdhRead.as_int(elements[0], TYPE_NIL)
|
|
759
|
+
value_type = GdhRead.as_int(elements[1], TYPE_NIL)
|
|
700
760
|
var entries: Dictionary = {}
|
|
701
761
|
for key: Variant in value:
|
|
702
762
|
entries[_values.fitted(key, key_type)] = parse(value[key], value_type)
|
|
@@ -714,7 +774,7 @@ func _typed_dictionary(entries: Dictionary, key_type: int, value_type: int) -> D
|
|
|
714
774
|
for key: Variant in entries:
|
|
715
775
|
if key_type != TYPE_NIL and typeof(key) != key_type:
|
|
716
776
|
return entries
|
|
717
|
-
if not
|
|
777
|
+
if not GdhSerialisation.acceptable(entries[key], value_type):
|
|
718
778
|
return entries
|
|
719
779
|
var fitted: Dictionary = {}
|
|
720
780
|
for key: Variant in entries:
|
|
@@ -725,46 +785,48 @@ func _typed_dictionary(entries: Dictionary, key_type: int, value_type: int) -> D
|
|
|
725
785
|
## An array, either positional for a vector the property declares, or a list to parse per item.
|
|
726
786
|
func _parse_array(value: Array, expected_type: int, elements: Array) -> Variant:
|
|
727
787
|
if PACKED_ELEMENTS.has(expected_type):
|
|
728
|
-
var element: int =
|
|
788
|
+
var element: int = GdhRead.as_int(PACKED_ELEMENTS[expected_type], TYPE_NIL)
|
|
729
789
|
return value.map(func(item: Variant) -> Variant: return parse(item, element))
|
|
730
790
|
|
|
731
791
|
match expected_type:
|
|
732
792
|
TYPE_VECTOR2:
|
|
733
793
|
if value.size() == 2:
|
|
734
|
-
return Vector2(
|
|
794
|
+
return Vector2(GdhRead.as_float(value[0]), GdhRead.as_float(value[1]))
|
|
735
795
|
TYPE_VECTOR2I:
|
|
736
796
|
if value.size() == 2:
|
|
737
|
-
return Vector2i(
|
|
797
|
+
return Vector2i(GdhRead.as_int(value[0]), GdhRead.as_int(value[1]))
|
|
738
798
|
TYPE_VECTOR3:
|
|
739
799
|
if value.size() == 3:
|
|
740
|
-
return Vector3(
|
|
800
|
+
return Vector3(
|
|
801
|
+
GdhRead.as_float(value[0]), GdhRead.as_float(value[1]), GdhRead.as_float(value[2])
|
|
802
|
+
)
|
|
741
803
|
TYPE_VECTOR3I:
|
|
742
804
|
if value.size() == 3:
|
|
743
|
-
return Vector3i(
|
|
805
|
+
return Vector3i(GdhRead.as_int(value[0]), GdhRead.as_int(value[1]), GdhRead.as_int(value[2]))
|
|
744
806
|
TYPE_VECTOR4:
|
|
745
807
|
if value.size() == 4:
|
|
746
808
|
return Vector4(
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
809
|
+
GdhRead.as_float(value[0]),
|
|
810
|
+
GdhRead.as_float(value[1]),
|
|
811
|
+
GdhRead.as_float(value[2]),
|
|
812
|
+
GdhRead.as_float(value[3])
|
|
751
813
|
)
|
|
752
814
|
TYPE_COLOR:
|
|
753
815
|
if value.size() == 3 or value.size() == 4:
|
|
754
816
|
return Color(
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
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
|
|
759
821
|
)
|
|
760
822
|
|
|
761
823
|
var element_type: int = TYPE_NIL
|
|
762
824
|
if expected_type == TYPE_ARRAY and not elements.is_empty():
|
|
763
|
-
element_type =
|
|
825
|
+
element_type = GdhRead.as_int(elements[0], TYPE_NIL)
|
|
764
826
|
var items: Array = value.map(func(item: Variant) -> Variant: return parse(item, element_type))
|
|
765
827
|
if element_type == TYPE_NIL or element_type == TYPE_OBJECT:
|
|
766
828
|
return items
|
|
767
|
-
if not items.all(func(item: Variant) -> bool: return
|
|
829
|
+
if not items.all(func(item: Variant) -> bool: return GdhSerialisation.acceptable(item, element_type)):
|
|
768
830
|
return items
|
|
769
831
|
var fitted: Array = items.map(func(item: Variant) -> Variant: return type_convert(item, element_type))
|
|
770
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
|
|
@@ -23,14 +23,14 @@ extends RefCounted
|
|
|
23
23
|
## setter setting scale were both saved into the file by a write to another node, and a root whose
|
|
24
24
|
## script failed in [code]_init[/code] was saved without it. Outside the editor, then, what a node
|
|
25
25
|
## stores is put back to what the file holds wherever the tool did not ask for a change, before
|
|
26
|
-
## anything is saved, and a write is refused when that does not hold. See [
|
|
26
|
+
## anything is saved, and a write is refused when that does not hold. See [GdhHeadlessCheck].
|
|
27
27
|
|
|
28
|
-
const
|
|
28
|
+
const GdhHeadlessCheck = preload("headless_check.gd")
|
|
29
29
|
|
|
30
30
|
## How many times what scripts changed is put back before a write is refused for it.
|
|
31
31
|
const PUTTING_BACK_PASSES: int = 4
|
|
32
|
-
const
|
|
33
|
-
const
|
|
32
|
+
const GdhPropertyValues = preload("property_values.gd")
|
|
33
|
+
const GdhRead = preload("reading.gd")
|
|
34
34
|
## The names Object.ConnectFlags gives each bit. The number alone left a reader guessing that 2 is
|
|
35
35
|
## the persistence every connection in a saved scene carries.
|
|
36
36
|
const CONNECT_FLAG_NAMES: Dictionary[int, String] = {
|
|
@@ -135,12 +135,12 @@ func asked(node: Node, names: Array) -> void:
|
|
|
135
135
|
var id: int = node.get_instance_id()
|
|
136
136
|
var on: Dictionary = _asked.get_or_add(id, {})
|
|
137
137
|
if not _before.has(id):
|
|
138
|
-
_before[id] =
|
|
138
|
+
_before[id] = GdhHeadlessCheck.stored(node)
|
|
139
139
|
for name: Variant in names:
|
|
140
140
|
var property: String = str(name)
|
|
141
141
|
on[property] = true
|
|
142
142
|
on[property.get_slice(":", 0)] = true
|
|
143
|
-
_reached.merge(
|
|
143
|
+
_reached.merge(GdhHeadlessCheck.reached(node, property))
|
|
144
144
|
|
|
145
145
|
|
|
146
146
|
## Takes [param resource], built into the scene, as one the tool changed, which a write saves as it is.
|
|
@@ -199,7 +199,7 @@ func not_found(said: String, node_path: String) -> String:
|
|
|
199
199
|
if root == null:
|
|
200
200
|
return said
|
|
201
201
|
if not root.has_node(NodePath(node_path.get_slice("/", 0))):
|
|
202
|
-
var hinted: String =
|
|
202
|
+
var hinted: String = GdhRead.from_the_root(said, node_path, str(root.name))
|
|
203
203
|
if hinted != said:
|
|
204
204
|
return hinted
|
|
205
205
|
var parts: PackedStringArray = node_path.trim_prefix("./").split("/", false)
|
|
@@ -216,7 +216,7 @@ func not_found(said: String, node_path: String) -> String:
|
|
|
216
216
|
var names: PackedStringArray = []
|
|
217
217
|
for child: Node in at.get_children():
|
|
218
218
|
var _named: bool = names.append(str(child.name))
|
|
219
|
-
return
|
|
219
|
+
return GdhRead.missing_below(said, str(root.name), reached, parts[reached.size()], names)
|
|
220
220
|
|
|
221
221
|
|
|
222
222
|
func close() -> void:
|
|
@@ -477,7 +477,7 @@ func kept(saved: Node, loaded: Variant, given: Variant) -> bool:
|
|
|
477
477
|
var loaded_node: Node = loaded
|
|
478
478
|
var given_node: Node = given
|
|
479
479
|
return str(saved.get_path_to(loaded_node)) == str(root.get_path_to(given_node))
|
|
480
|
-
return
|
|
480
|
+
return GdhPropertyValues.same(loaded, given)
|
|
481
481
|
|
|
482
482
|
|
|
483
483
|
## Packs [member root], has [param check] read the result as the file will hold it, and writes the
|
|
@@ -508,7 +508,7 @@ func write(check: Callable = Callable()) -> Dictionary:
|
|
|
508
508
|
)
|
|
509
509
|
% path
|
|
510
510
|
)
|
|
511
|
-
+
|
|
511
|
+
+ GdhHeadlessCheck.REMEDY
|
|
512
512
|
)
|
|
513
513
|
)
|
|
514
514
|
if check.is_valid():
|
|
@@ -530,12 +530,14 @@ func write(check: Callable = Callable()) -> Dictionary:
|
|
|
530
530
|
var values: Array = []
|
|
531
531
|
for index: int in state.get_node_count():
|
|
532
532
|
values.append_array(_stored_at(state, index).values())
|
|
533
|
-
var unkept: String =
|
|
533
|
+
var unkept: String = GdhHeadlessCheck.settle_saved(
|
|
534
534
|
path, _text, values, null, {}, _reached, func() -> Error: return save_keeping_uid(packed, path)
|
|
535
535
|
)
|
|
536
536
|
if unkept.is_empty():
|
|
537
537
|
return {}
|
|
538
|
-
return {
|
|
538
|
+
return {
|
|
539
|
+
"ok": false, "error": "Nothing was written: " + unkept + GdhHeadlessCheck.put_back(path, previous)
|
|
540
|
+
}
|
|
539
541
|
|
|
540
542
|
|
|
541
543
|
func _holds_scripted_embedded() -> bool:
|
|
@@ -543,7 +545,7 @@ func _holds_scripted_embedded() -> bool:
|
|
|
543
545
|
var nodes: Array[Node] = [root]
|
|
544
546
|
while not nodes.is_empty():
|
|
545
547
|
var node: Node = nodes.pop_back()
|
|
546
|
-
if
|
|
548
|
+
if GdhHeadlessCheck.holds_scripted_embedded(node, seen):
|
|
547
549
|
return true
|
|
548
550
|
nodes.append_array(node.get_children())
|
|
549
551
|
return false
|
|
@@ -742,7 +744,7 @@ func _settled(packed: PackedScene) -> Array:
|
|
|
742
744
|
var node: Node = found[0]
|
|
743
745
|
var before: Dictionary = _before[node.get_instance_id()]
|
|
744
746
|
for name: String in found[1]:
|
|
745
|
-
node.set(name, before.get(name,
|
|
747
|
+
node.set(name, before.get(name, GdhHeadlessCheck.default_of(node, name)))
|
|
746
748
|
current = PackedScene.new()
|
|
747
749
|
var packing: Error = current.pack(root)
|
|
748
750
|
if packing != OK:
|
|
@@ -766,10 +768,10 @@ func _moved_by_scripts() -> Array[Array]:
|
|
|
766
768
|
if node == null or not root.is_ancestor_of(node) and node != root:
|
|
767
769
|
continue
|
|
768
770
|
var on: Dictionary = _asked[id]
|
|
769
|
-
if not
|
|
771
|
+
if not GdhHeadlessCheck.through_script(node, on):
|
|
770
772
|
continue
|
|
771
773
|
var before: Dictionary = _before[id]
|
|
772
|
-
var changed: Array[String] =
|
|
774
|
+
var changed: Array[String] = GdhHeadlessCheck.differing(node, before, on)
|
|
773
775
|
if not changed.is_empty():
|
|
774
776
|
moved.append([node, changed])
|
|
775
777
|
return moved
|
|
@@ -792,7 +794,10 @@ func _strays(state: SceneState) -> Array[Array]:
|
|
|
792
794
|
# changes is Godot's doing and the editor's pack does it too: a Control saved without its
|
|
793
795
|
# grow directions gets them back from anchors_preset's setter, and loses an anchors_preset
|
|
794
796
|
# its layout mode does not store.
|
|
795
|
-
if
|
|
797
|
+
if (
|
|
798
|
+
not GdhHeadlessCheck.untooled(node.get_script())
|
|
799
|
+
and not GdhHeadlessCheck.untooled(filed.get("script"))
|
|
800
|
+
):
|
|
796
801
|
continue
|
|
797
802
|
var stored: Dictionary = _stored_at(state, index)
|
|
798
803
|
var names: Dictionary = filed.duplicate()
|
|
@@ -800,9 +805,9 @@ func _strays(state: SceneState) -> Array[Array]:
|
|
|
800
805
|
for key: Variant in names:
|
|
801
806
|
var name: String = str(key)
|
|
802
807
|
var held: bool = filed.has(name)
|
|
803
|
-
if held == stored.has(name) and (not held or
|
|
808
|
+
if held == stored.has(name) and (not held or GdhPropertyValues.same(stored[name], filed[name])):
|
|
804
809
|
continue
|
|
805
|
-
if not held and
|
|
810
|
+
if not held and GdhHeadlessCheck.at_default(node, name):
|
|
806
811
|
continue
|
|
807
812
|
stray.append([node, name, held, filed.get(name)])
|
|
808
813
|
return stray
|
|
@@ -819,7 +824,7 @@ func _stray_text(node: Node, name: String) -> String:
|
|
|
819
824
|
)
|
|
820
825
|
% [at, path]
|
|
821
826
|
)
|
|
822
|
-
+
|
|
827
|
+
+ GdhHeadlessCheck.REMEDY
|
|
823
828
|
)
|
|
824
829
|
return (
|
|
825
830
|
(
|
|
@@ -830,7 +835,7 @@ func _stray_text(node: Node, name: String) -> String:
|
|
|
830
835
|
)
|
|
831
836
|
% [at, name, path]
|
|
832
837
|
)
|
|
833
|
-
+
|
|
838
|
+
+ GdhHeadlessCheck.REMEDY
|
|
834
839
|
)
|
|
835
840
|
|
|
836
841
|
|
|
@@ -845,7 +850,7 @@ func _unset_value(node: Node, name: String) -> Variant:
|
|
|
845
850
|
while top != root and top.owner != root:
|
|
846
851
|
top = top.get_parent()
|
|
847
852
|
if top == root or top.scene_file_path.is_empty():
|
|
848
|
-
return
|
|
853
|
+
return GdhHeadlessCheck.default_of(node, name)
|
|
849
854
|
if _base_paths.has(path_of(top)):
|
|
850
855
|
return _value_in(base, path_of(node), node, name)
|
|
851
856
|
var instanced: PackedScene = ResourceLoader.load(top.scene_file_path) as PackedScene
|
|
@@ -881,7 +886,7 @@ func _value_in(state: SceneState, at: String, node: Node, name: String) -> Varia
|
|
|
881
886
|
state = instanced.get_state()
|
|
882
887
|
else:
|
|
883
888
|
state = state.get_base_scene_state()
|
|
884
|
-
return
|
|
889
|
+
return GdhHeadlessCheck.default_of(node, name)
|
|
885
890
|
|
|
886
891
|
|
|
887
892
|
func _node_at(node_path: String) -> Node:
|