gdharness 1.3.0 → 1.3.2
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 +322 -96
- 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/declared_values.gd +116 -0
- package/build/godot/addons/gdharness_editor/declared_values.gd.uid +1 -0
- package/build/godot/addons/gdharness_editor/file_reads.gd +65 -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 +222 -59
- package/build/godot/addons/gdharness_editor/reading.gd +65 -0
- package/build/godot/addons/gdharness_editor/scene_file.gd +28 -23
- package/build/godot/addons/gdharness_editor/serialisation.gd +49 -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 +175 -74
- 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 +88 -84
- package/build/godot/addons/gdharness_editor/tools/settings_tools.gd +4 -4
- package/build/godot/addons/gdharness_editor/value_shapes.gd +118 -0
- package/build/godot/addons/gdharness_editor/value_shapes.gd.uid +1 -0
- package/build/godot/addons/gdharness_runtime/reading.gd +65 -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 +52 -25
- package/build/godot/addons/gdharness_runtime/runtime_waits.gd +56 -50
- package/build/godot/addons/gdharness_runtime/serialisation.gd +49 -94
- package/build/godot/addons/gdharness_runtime/value_shapes.gd +118 -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 +25 -15
- package/build/godot/operations/declared_values.gd +116 -0
- package/build/godot/operations/dependencies.gd +141 -33
- package/build/godot/operations/editor_tools.gd +12 -12
- package/build/godot/operations/export_presets.gd +14 -14
- package/build/godot/operations/file_reads.gd +65 -25
- package/build/godot/operations/file_walk.gd +34 -0
- package/build/godot/operations/gdscript_analysis.gd +27 -22
- package/build/godot/operations/gdscript_authoring.gd +122 -17
- package/build/godot/operations/godot_operations.gd +66 -63
- package/build/godot/operations/import_pipeline.gd +59 -20
- package/build/godot/operations/input_actions.gd +90 -18
- package/build/godot/operations/logger.gd +1 -1
- package/build/godot/operations/plugins.gd +8 -8
- package/build/godot/operations/project_config.gd +83 -20
- package/build/godot/operations/project_diagnostics.gd +36 -11
- package/build/godot/operations/property_values.gd +222 -59
- package/build/godot/operations/reading.gd +65 -0
- package/build/godot/operations/rename_checks.gd +3 -3
- package/build/godot/operations/resource_files.gd +9 -6
- package/build/godot/operations/script_check.gd +23 -7
- package/build/godot/operations/serialisation.gd +49 -94
- package/build/godot/operations/value_shapes.gd +118 -0
- package/package.json +1 -1
|
@@ -8,7 +8,8 @@ extends RefCounted
|
|
|
8
8
|
## before: the same Vector2 came back three ways depending on which tool was asked, and a caller
|
|
9
9
|
## reading a property off the editor could not feed it back to the game.
|
|
10
10
|
|
|
11
|
-
const
|
|
11
|
+
const GdhRead = preload("reading.gd")
|
|
12
|
+
const GdhShapes = preload("value_shapes.gd")
|
|
12
13
|
|
|
13
14
|
# What the engine carries from one to another without complaint, which is what a value written to a
|
|
14
15
|
# property or handed to a parameter may arrive as. Numbers include bool because the engine counts it
|
|
@@ -120,35 +121,6 @@ const BUILDERS: Dictionary = {
|
|
|
120
121
|
"Dictionary": "_build_dictionary",
|
|
121
122
|
}
|
|
122
123
|
|
|
123
|
-
# What each tag has to carry before it is built. A tag naming a type whose keys are not there is a
|
|
124
|
-
# tag somebody wrote by mistake or wrote for a different shape, and building one out of defaults
|
|
125
|
-
# answers with a Transform2D of zeros where the caller meant something else entirely. Unbuilt, it
|
|
126
|
-
# comes back as it arrived, and whoever asked can read it against the type they were expecting.
|
|
127
|
-
const REQUIRED: Dictionary = {
|
|
128
|
-
"Vector2": [["x", "y"]],
|
|
129
|
-
"Vector2i": [["x", "y"]],
|
|
130
|
-
"Vector3": [["x", "y", "z"]],
|
|
131
|
-
"Vector3i": [["x", "y", "z"]],
|
|
132
|
-
"Vector4": [["x", "y", "z", "w"]],
|
|
133
|
-
"Vector4i": [["x", "y", "z", "w"]],
|
|
134
|
-
"Rect2": [["position", "size"], ["x", "y", "width", "height"]],
|
|
135
|
-
"Rect2i": [["position", "size"], ["x", "y", "width", "height"]],
|
|
136
|
-
"Plane": [["normal", "d"]],
|
|
137
|
-
"Quaternion": [["x", "y", "z", "w"]],
|
|
138
|
-
"AABB": [["position", "size"]],
|
|
139
|
-
"Basis": [["x", "y", "z"]],
|
|
140
|
-
"Transform2D": [["x", "y", "origin"]],
|
|
141
|
-
"Transform3D": [["basis", "origin"]],
|
|
142
|
-
"Projection": [["x", "y", "z", "w"]],
|
|
143
|
-
"Color": [["r", "g", "b"]],
|
|
144
|
-
"NodePath": [["path"]],
|
|
145
|
-
"PackedByteArray": [["base64"]],
|
|
146
|
-
"float": [["value"]],
|
|
147
|
-
"int": [["value"]],
|
|
148
|
-
"RID": [["id"]],
|
|
149
|
-
"Dictionary": [["entries"]],
|
|
150
|
-
}
|
|
151
|
-
|
|
152
124
|
|
|
153
125
|
# Converts a Godot value into something JSON can carry. A type with no entry in the table
|
|
154
126
|
# passes through as itself, which is what the JSON-native ones want.
|
|
@@ -246,8 +218,8 @@ func deserialize_value(value: Variant) -> Variant:
|
|
|
246
218
|
# under the target project's warning levels, so one such project lost every headless call.
|
|
247
219
|
var tag: String = str(fields["_type"])
|
|
248
220
|
var builder: String = BUILDERS.get(tag, "")
|
|
249
|
-
var
|
|
250
|
-
if builder.is_empty() or not
|
|
221
|
+
var spare: Array = GdhShapes.spare(fields, tag)
|
|
222
|
+
if builder.is_empty() or not spare.any(func(left: Array) -> bool: return left.is_empty()):
|
|
251
223
|
return fields
|
|
252
224
|
if tag == "float" and str(fields["value"]) not in ["inf", "-inf", "nan"]:
|
|
253
225
|
return fields
|
|
@@ -430,9 +402,18 @@ func fitted(value: Variant, type: int) -> Variant:
|
|
|
430
402
|
if rebuilt is Array and LIST_TYPES.has(type):
|
|
431
403
|
return type_convert(rebuilt, type)
|
|
432
404
|
|
|
405
|
+
# The inspector and every colour picker give a colour as its html code. Anything else stays the
|
|
406
|
+
# text it is, for the refusal to name.
|
|
407
|
+
if type == TYPE_COLOR and rebuilt is String and Color.html_is_valid(str(rebuilt)):
|
|
408
|
+
return Color.html(str(rebuilt))
|
|
409
|
+
|
|
433
410
|
var simple: Array[int] = [TYPE_BOOL, TYPE_INT, TYPE_FLOAT, TYPE_STRING]
|
|
434
411
|
if not simple.has(type) or not simple.has(typeof(rebuilt)):
|
|
435
412
|
return rebuilt
|
|
413
|
+
# A number or a truth value stays as sent where text is wanted, for the refusal to name. Converted,
|
|
414
|
+
# 1 arrived as the double it is in JSON and was saved as "1.0", which is not the text anyone sends.
|
|
415
|
+
if type == TYPE_STRING and not rebuilt is String:
|
|
416
|
+
return rebuilt
|
|
436
417
|
|
|
437
418
|
# Digits are read as digits, since read as JSON they become a double and one past 2^53 a
|
|
438
419
|
# neighbour of the number written.
|
|
@@ -441,7 +422,7 @@ func fitted(value: Variant, type: int) -> Variant:
|
|
|
441
422
|
|
|
442
423
|
if rebuilt is String and type != TYPE_STRING:
|
|
443
424
|
var text: String = rebuilt
|
|
444
|
-
var parsed: Variant =
|
|
425
|
+
var parsed: Variant = GdhRead.json_or_null(text)
|
|
445
426
|
# A string that does not read as the type wanted is handed back as the string it is, so
|
|
446
427
|
# whoever asked can refuse it. type_convert answers 0 for "not an index" and true for any
|
|
447
428
|
# text at all, and a caller who sent a word got back a number they never sent: get_child
|
|
@@ -461,7 +442,7 @@ func fitted(value: Variant, type: int) -> Variant:
|
|
|
461
442
|
static func inexact(given: Variant, type: int) -> String:
|
|
462
443
|
if type != TYPE_INT or (typeof(given) != TYPE_FLOAT and typeof(given) != TYPE_INT):
|
|
463
444
|
return ""
|
|
464
|
-
var number: float =
|
|
445
|
+
var number: float = GdhRead.as_float(given)
|
|
465
446
|
if not is_finite(number) or absf(number) < EXACT:
|
|
466
447
|
return ""
|
|
467
448
|
return (
|
|
@@ -474,34 +455,6 @@ static func inexact(given: Variant, type: int) -> String:
|
|
|
474
455
|
)
|
|
475
456
|
|
|
476
457
|
|
|
477
|
-
## How a value of [param type] is written to be read as one, for a refusal to name, or empty for a
|
|
478
|
-
## type that is not built from fields. A refusal naming only the two types left a caller to find
|
|
479
|
-
## the shape in the description of another op.
|
|
480
|
-
static func written_as(type: int) -> String:
|
|
481
|
-
var tag: String = type_string(type)
|
|
482
|
-
var alternatives: Array = REQUIRED.get(tag, [])
|
|
483
|
-
if alternatives.is_empty() or tag in ["float", "int", "Dictionary"]:
|
|
484
|
-
return ""
|
|
485
|
-
var keys: Array = alternatives[0]
|
|
486
|
-
var parts: Array[String] = []
|
|
487
|
-
for key: Variant in keys:
|
|
488
|
-
parts.append('"%s": ...' % key)
|
|
489
|
-
return "{%s}" % ", ".join(parts)
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
## Whether [param fields] carries every key of any one of [param alternatives].
|
|
493
|
-
##
|
|
494
|
-
## More than one spelling because a rectangle has two: the two corners it is answered with, and the
|
|
495
|
-
## four numbers the editor's own tools answered with before the serialisers were made one, which a
|
|
496
|
-
## caller may have kept.
|
|
497
|
-
func _carries(fields: Dictionary, alternatives: Array) -> bool:
|
|
498
|
-
for alternative: Variant in alternatives:
|
|
499
|
-
var keys: Array = alternative
|
|
500
|
-
if keys.all(func(key: Variant) -> bool: return fields.has(key)):
|
|
501
|
-
return true
|
|
502
|
-
return false
|
|
503
|
-
|
|
504
|
-
|
|
505
458
|
func _serialize_nil(_value: Variant) -> Variant:
|
|
506
459
|
return null
|
|
507
460
|
|
|
@@ -769,42 +722,44 @@ func _describe_object(value: Object) -> Dictionary:
|
|
|
769
722
|
|
|
770
723
|
|
|
771
724
|
func _build_vector2(fields: Dictionary) -> Vector2:
|
|
772
|
-
return Vector2(
|
|
725
|
+
return Vector2(GdhRead.as_float(fields.get("x", 0)), GdhRead.as_float(fields.get("y", 0)))
|
|
773
726
|
|
|
774
727
|
|
|
775
728
|
func _build_vector2i(fields: Dictionary) -> Vector2i:
|
|
776
|
-
return Vector2i(
|
|
729
|
+
return Vector2i(GdhRead.as_int(fields.get("x", 0)), GdhRead.as_int(fields.get("y", 0)))
|
|
777
730
|
|
|
778
731
|
|
|
779
732
|
func _build_vector3(fields: Dictionary) -> Vector3:
|
|
780
733
|
return Vector3(
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
734
|
+
GdhRead.as_float(fields.get("x", 0)),
|
|
735
|
+
GdhRead.as_float(fields.get("y", 0)),
|
|
736
|
+
GdhRead.as_float(fields.get("z", 0))
|
|
784
737
|
)
|
|
785
738
|
|
|
786
739
|
|
|
787
740
|
func _build_vector3i(fields: Dictionary) -> Vector3i:
|
|
788
741
|
return Vector3i(
|
|
789
|
-
|
|
742
|
+
GdhRead.as_int(fields.get("x", 0)),
|
|
743
|
+
GdhRead.as_int(fields.get("y", 0)),
|
|
744
|
+
GdhRead.as_int(fields.get("z", 0))
|
|
790
745
|
)
|
|
791
746
|
|
|
792
747
|
|
|
793
748
|
func _build_vector4(fields: Dictionary) -> Vector4:
|
|
794
749
|
return Vector4(
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
750
|
+
GdhRead.as_float(fields.get("x", 0)),
|
|
751
|
+
GdhRead.as_float(fields.get("y", 0)),
|
|
752
|
+
GdhRead.as_float(fields.get("z", 0)),
|
|
753
|
+
GdhRead.as_float(fields.get("w", 0))
|
|
799
754
|
)
|
|
800
755
|
|
|
801
756
|
|
|
802
757
|
func _build_vector4i(fields: Dictionary) -> Vector4i:
|
|
803
758
|
return Vector4i(
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
759
|
+
GdhRead.as_int(fields.get("x", 0)),
|
|
760
|
+
GdhRead.as_int(fields.get("y", 0)),
|
|
761
|
+
GdhRead.as_int(fields.get("z", 0)),
|
|
762
|
+
GdhRead.as_int(fields.get("w", 0))
|
|
808
763
|
)
|
|
809
764
|
|
|
810
765
|
|
|
@@ -813,10 +768,10 @@ func _build_vector4i(fields: Dictionary) -> Vector4i:
|
|
|
813
768
|
func _build_rect2(fields: Dictionary) -> Rect2:
|
|
814
769
|
if not fields.has("position"):
|
|
815
770
|
return Rect2(
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
771
|
+
GdhRead.as_float(fields.get("x", 0)),
|
|
772
|
+
GdhRead.as_float(fields.get("y", 0)),
|
|
773
|
+
GdhRead.as_float(fields.get("width", 0)),
|
|
774
|
+
GdhRead.as_float(fields.get("height", 0))
|
|
820
775
|
)
|
|
821
776
|
return Rect2(_vector2_at(fields, "position"), _vector2_at(fields, "size"))
|
|
822
777
|
|
|
@@ -824,24 +779,24 @@ func _build_rect2(fields: Dictionary) -> Rect2:
|
|
|
824
779
|
func _build_rect2i(fields: Dictionary) -> Rect2i:
|
|
825
780
|
if not fields.has("position"):
|
|
826
781
|
return Rect2i(
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
782
|
+
GdhRead.as_int(fields.get("x", 0)),
|
|
783
|
+
GdhRead.as_int(fields.get("y", 0)),
|
|
784
|
+
GdhRead.as_int(fields.get("width", 0)),
|
|
785
|
+
GdhRead.as_int(fields.get("height", 0))
|
|
831
786
|
)
|
|
832
787
|
return Rect2i(_vector2i_at(fields, "position"), _vector2i_at(fields, "size"))
|
|
833
788
|
|
|
834
789
|
|
|
835
790
|
func _build_plane(fields: Dictionary) -> Plane:
|
|
836
|
-
return Plane(_vector3_at(fields, "normal"),
|
|
791
|
+
return Plane(_vector3_at(fields, "normal"), GdhRead.as_float(fields.get("d", 0)))
|
|
837
792
|
|
|
838
793
|
|
|
839
794
|
func _build_quaternion(fields: Dictionary) -> Quaternion:
|
|
840
795
|
return Quaternion(
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
796
|
+
GdhRead.as_float(fields.get("x", 0)),
|
|
797
|
+
GdhRead.as_float(fields.get("y", 0)),
|
|
798
|
+
GdhRead.as_float(fields.get("z", 0)),
|
|
799
|
+
GdhRead.as_float(fields.get("w", 0))
|
|
845
800
|
)
|
|
846
801
|
|
|
847
802
|
|
|
@@ -881,10 +836,10 @@ func _build_projection(fields: Dictionary) -> Projection:
|
|
|
881
836
|
|
|
882
837
|
func _build_color(fields: Dictionary) -> Color:
|
|
883
838
|
return Color(
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
839
|
+
GdhRead.as_float(fields.get("r", 0)),
|
|
840
|
+
GdhRead.as_float(fields.get("g", 0)),
|
|
841
|
+
GdhRead.as_float(fields.get("b", 0)),
|
|
842
|
+
GdhRead.as_float(fields.get("a", 1), 1.0)
|
|
888
843
|
)
|
|
889
844
|
|
|
890
845
|
|
|
@@ -897,7 +852,7 @@ func _build_bytes(fields: Dictionary) -> PackedByteArray:
|
|
|
897
852
|
|
|
898
853
|
|
|
899
854
|
func _build_float(fields: Dictionary) -> float:
|
|
900
|
-
return
|
|
855
|
+
return GdhRead.as_float(fields)
|
|
901
856
|
|
|
902
857
|
|
|
903
858
|
func _build_int(fields: Dictionary) -> int:
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
extends RefCounted
|
|
2
|
+
|
|
3
|
+
## How each engine type built from fields is written: the fields it needs, the ones it reads when
|
|
4
|
+
## they are there, and what a refusal says about a value that is not one.
|
|
5
|
+
##
|
|
6
|
+
## This file is the one beside the operations, and `bun run sync:gd` copies it into each addon.
|
|
7
|
+
|
|
8
|
+
# What each tag has to carry before it is built. A tag naming a type whose keys are not there is a
|
|
9
|
+
# tag somebody wrote by mistake or wrote for a different shape, and building one out of defaults
|
|
10
|
+
# answers with a Transform2D of zeros where the caller meant something else entirely. Unbuilt, it
|
|
11
|
+
# comes back as it arrived, and whoever asked can read it against the type they were expecting.
|
|
12
|
+
const REQUIRED: Dictionary = {
|
|
13
|
+
"Vector2": [["x", "y"]],
|
|
14
|
+
"Vector2i": [["x", "y"]],
|
|
15
|
+
"Vector3": [["x", "y", "z"]],
|
|
16
|
+
"Vector3i": [["x", "y", "z"]],
|
|
17
|
+
"Vector4": [["x", "y", "z", "w"]],
|
|
18
|
+
"Vector4i": [["x", "y", "z", "w"]],
|
|
19
|
+
"Rect2": [["position", "size"], ["x", "y", "width", "height"]],
|
|
20
|
+
"Rect2i": [["position", "size"], ["x", "y", "width", "height"]],
|
|
21
|
+
"Plane": [["normal", "d"]],
|
|
22
|
+
"Quaternion": [["x", "y", "z", "w"]],
|
|
23
|
+
"AABB": [["position", "size"]],
|
|
24
|
+
"Basis": [["x", "y", "z"]],
|
|
25
|
+
"Transform2D": [["x", "y", "origin"]],
|
|
26
|
+
"Transform3D": [["basis", "origin"]],
|
|
27
|
+
"Projection": [["x", "y", "z", "w"]],
|
|
28
|
+
"Color": [["r", "g", "b"]],
|
|
29
|
+
"NodePath": [["path"]],
|
|
30
|
+
"PackedByteArray": [["base64"]],
|
|
31
|
+
"float": [["value"]],
|
|
32
|
+
"int": [["value"]],
|
|
33
|
+
"RID": [["id"]],
|
|
34
|
+
"Dictionary": [["entries"]],
|
|
35
|
+
}
|
|
36
|
+
## The fields a builder reads when they are there and does without when not.
|
|
37
|
+
const OPTIONAL: Dictionary = {
|
|
38
|
+
"Color": ["a"],
|
|
39
|
+
"PackedByteArray": ["size"],
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
## How a value of [param type] is written to be read as one, for a refusal to name, or empty for a
|
|
44
|
+
## type that is not built from fields. A refusal naming only the two types leaves a caller to find
|
|
45
|
+
## the shape in the description of another op.
|
|
46
|
+
static func written_as(type: int) -> String:
|
|
47
|
+
var tag: String = type_string(type)
|
|
48
|
+
var alternatives: Array = REQUIRED.get(tag, [])
|
|
49
|
+
if alternatives.is_empty() or tag in ["float", "int", "Dictionary"]:
|
|
50
|
+
return ""
|
|
51
|
+
var keys: Array = alternatives[0]
|
|
52
|
+
var optional: Array = OPTIONAL.get(tag, [])
|
|
53
|
+
var parts: Array[String] = []
|
|
54
|
+
for key: Variant in keys + optional:
|
|
55
|
+
parts.append('"%s": ...' % key)
|
|
56
|
+
var shape: String = "{%s}" % ", ".join(parts)
|
|
57
|
+
if type == TYPE_COLOR:
|
|
58
|
+
return shape + ' or as its html code, "#ff8000"'
|
|
59
|
+
return shape
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
## The keys of [param fields] that a [param tag] built from them would not read, one list for each
|
|
63
|
+
## spelling of its fields carried whole. A rectangle has two: its corners, and four numbers.
|
|
64
|
+
##
|
|
65
|
+
## A builder drops a key it does not know without a word, so {"x": 1, "y": 2, "z": 3} for a Vector2
|
|
66
|
+
## would be written as (1, 2) and answered as written: a value is built only when a list is empty.
|
|
67
|
+
static func spare(fields: Dictionary, tag: String) -> Array:
|
|
68
|
+
var optional: Array = OPTIONAL.get(tag, [])
|
|
69
|
+
var lists: Array = []
|
|
70
|
+
var alternatives: Array = REQUIRED.get(tag, [])
|
|
71
|
+
for alternative: Variant in alternatives:
|
|
72
|
+
var keys: Array = alternative
|
|
73
|
+
if keys.all(func(key: Variant) -> bool: return fields.has(key)):
|
|
74
|
+
var over: Callable = func(key: Variant) -> bool:
|
|
75
|
+
return str(key) != "_type" and not keys.has(key) and not optional.has(key)
|
|
76
|
+
lists.append(fields.keys().filter(over))
|
|
77
|
+
return lists
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
## The sentences a refusal of [param value] where a [param type] goes ends with: which of its keys
|
|
81
|
+
## kept it from being read as one, and how one is written. Empty for a type not built from fields.
|
|
82
|
+
##
|
|
83
|
+
## The refusal names only the two types, and the key is what the caller has to take out.
|
|
84
|
+
static func remedy(value: Variant, type: int) -> String:
|
|
85
|
+
if type == TYPE_STRING and typeof(value) in [TYPE_BOOL, TYPE_INT, TYPE_FLOAT]:
|
|
86
|
+
var number: Variant = value
|
|
87
|
+
if value is float:
|
|
88
|
+
var whole: float = value
|
|
89
|
+
if is_finite(whole) and whole == floorf(whole):
|
|
90
|
+
number = int(whole)
|
|
91
|
+
return ' Text is sent in quotes, as "%s".' % str(number)
|
|
92
|
+
var shape: String = written_as(type)
|
|
93
|
+
if shape.is_empty():
|
|
94
|
+
return ""
|
|
95
|
+
return _unread(value, type) + " A %s is sent as %s." % [type_string(type), shape]
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
static func _unread(value: Variant, type: int) -> String:
|
|
99
|
+
if not value is Dictionary:
|
|
100
|
+
return ""
|
|
101
|
+
var fields: Dictionary = value
|
|
102
|
+
var tag: String = type_string(type)
|
|
103
|
+
var lists: Array = spare(fields, tag)
|
|
104
|
+
var built: bool = lists.any(func(over: Array) -> bool: return over.is_empty())
|
|
105
|
+
if lists.is_empty() or built or str(fields.get("_type", tag)) != tag:
|
|
106
|
+
return ""
|
|
107
|
+
lists.sort_custom(func(a: Array, b: Array) -> bool: return a.size() < b.size())
|
|
108
|
+
var left: Array = lists[0]
|
|
109
|
+
var named: PackedStringArray = []
|
|
110
|
+
for key: Variant in left:
|
|
111
|
+
var _named: bool = named.append('"%s"' % key)
|
|
112
|
+
var fields_called: String = "field called" if named.size() == 1 else "fields called"
|
|
113
|
+
var note: String = (
|
|
114
|
+
" A %s has no %s %s, so the value is not read as one" % [tag, fields_called, " or ".join(named)]
|
|
115
|
+
)
|
|
116
|
+
if left.has("type") and not fields.has("_type"):
|
|
117
|
+
return note + ': a value names its type under "_type".'
|
|
118
|
+
return note + "."
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
uid://c1s610pw8xorl
|
|
@@ -4,9 +4,8 @@ extends RefCounted
|
|
|
4
4
|
## and by a headless engine with none connected from a bare node of the class given what the file
|
|
5
5
|
## stores, which runs no script of the scene's, so a read answers the same either way.
|
|
6
6
|
|
|
7
|
-
const
|
|
8
|
-
const
|
|
9
|
-
const Serialisation = preload("serialisation.gd")
|
|
7
|
+
const GdhFileReads = preload("file_reads.gd")
|
|
8
|
+
const GdhSerialisation = preload("serialisation.gd")
|
|
10
9
|
|
|
11
10
|
const LOOP_MODE_NAMES: Dictionary[int, String] = {
|
|
12
11
|
Animation.LOOP_NONE: "none",
|
|
@@ -56,21 +55,10 @@ static func scene_states(scene_path: String, tree_path: String, state_machine_pa
|
|
|
56
55
|
## A node of the class the file gives the node at [param node_path], holding every property the
|
|
57
56
|
## files set but its script, as [node, null] or [null, refusal]. The caller frees it.
|
|
58
57
|
static func _bare(scene_path: String, node_path: String, kind: String) -> Array:
|
|
59
|
-
var read: Dictionary =
|
|
60
|
-
if not read.
|
|
58
|
+
var read: Dictionary = GdhFileReads.scene_node_of_kind(scene_path, node_path, kind)
|
|
59
|
+
if not read.get("ok", false):
|
|
61
60
|
return [null, read]
|
|
62
|
-
var
|
|
63
|
-
if not ClassDB.class_exists(type) or not ClassDB.is_parent_class(type, kind):
|
|
64
|
-
var said: String = "%s not found at: %s" % [kind, node_path]
|
|
65
|
-
if read.is_empty():
|
|
66
|
-
var hinted: String = said
|
|
67
|
-
if FileReads.scene_node(scene_path, node_path.get_slice("/", 0)).is_empty():
|
|
68
|
-
hinted = Read.from_the_root(
|
|
69
|
-
said, node_path, str(FileReads.scene_node(scene_path, ".").get("name", ""))
|
|
70
|
-
)
|
|
71
|
-
said = hinted if hinted != said else FileReads.missing_from(said, scene_path, node_path)
|
|
72
|
-
return [null, {"ok": false, "error": said}]
|
|
73
|
-
var bare: Node = ClassDB.instantiate(type)
|
|
61
|
+
var bare: Node = ClassDB.instantiate(str(read["type"]))
|
|
74
62
|
bare.name = str(read["name"])
|
|
75
63
|
var given: Dictionary = read["properties"]
|
|
76
64
|
for property: Variant in given:
|
|
@@ -107,7 +95,7 @@ static func animations(player: AnimationPlayer, animation_name: String) -> Dicti
|
|
|
107
95
|
answer["ok"] = true
|
|
108
96
|
answer["animationName"] = animation_name
|
|
109
97
|
var tracks: Array[Dictionary] = []
|
|
110
|
-
var values:
|
|
98
|
+
var values: GdhSerialisation = GdhSerialisation.new()
|
|
111
99
|
for track: int in anim.get_track_count():
|
|
112
100
|
tracks.append(_track(anim, track, values))
|
|
113
101
|
answer["tracks"] = tracks
|
|
@@ -122,7 +110,7 @@ static func _summary(anim: Animation) -> Dictionary:
|
|
|
122
110
|
}
|
|
123
111
|
|
|
124
112
|
|
|
125
|
-
static func _track(anim: Animation, track: int, values:
|
|
113
|
+
static func _track(anim: Animation, track: int, values: GdhSerialisation) -> Dictionary:
|
|
126
114
|
var kind: int = anim.track_get_type(track)
|
|
127
115
|
var path: NodePath = anim.track_get_path(track)
|
|
128
116
|
var shaped: Dictionary = {
|
|
@@ -3,13 +3,13 @@ extends RefCounted
|
|
|
3
3
|
# The bus layout is a project resource the engine loads at startup and this process holds in
|
|
4
4
|
# memory, so every change is written back to that file or it is gone with the process.
|
|
5
5
|
|
|
6
|
-
const
|
|
7
|
-
const
|
|
6
|
+
const GdhRead = preload("reading.gd")
|
|
7
|
+
const GdhLog = preload("logger.gd")
|
|
8
8
|
|
|
9
|
-
var _log:
|
|
9
|
+
var _log: GdhLog
|
|
10
10
|
|
|
11
11
|
|
|
12
|
-
func _init(p_log:
|
|
12
|
+
func _init(p_log: GdhLog) -> void:
|
|
13
13
|
_log = p_log
|
|
14
14
|
|
|
15
15
|
|
|
@@ -17,7 +17,7 @@ func create_audio_bus(params: Dictionary) -> Dictionary:
|
|
|
17
17
|
var bus_name: String = str(params.get("bus_name", ""))
|
|
18
18
|
if bus_name.is_empty():
|
|
19
19
|
return _log.failure("bus_name is required")
|
|
20
|
-
var parent_idx: int =
|
|
20
|
+
var parent_idx: int = GdhRead.as_int(params.get("parent_bus_index", 0))
|
|
21
21
|
if parent_idx < 0 or parent_idx >= AudioServer.bus_count:
|
|
22
22
|
return _log.failure("No bus at index " + str(parent_idx))
|
|
23
23
|
# Sends are by name, so a second bus with one would leave every send to it meaning either.
|
|
@@ -100,10 +100,10 @@ func get_audio_buses(_params: Dictionary) -> Dictionary:
|
|
|
100
100
|
|
|
101
101
|
|
|
102
102
|
func set_audio_bus_effect(params: Dictionary) -> Dictionary:
|
|
103
|
-
var bus_idx: int =
|
|
104
|
-
var effect_idx: int =
|
|
103
|
+
var bus_idx: int = GdhRead.as_int(params.get("bus_index", 0))
|
|
104
|
+
var effect_idx: int = GdhRead.as_int(params.get("effect_index", 0))
|
|
105
105
|
var effect_type: String = str(params.get("effect_type", ""))
|
|
106
|
-
var enabled: bool =
|
|
106
|
+
var enabled: bool = GdhRead.as_bool(params.get("enabled", true), true)
|
|
107
107
|
if bus_idx < 0 or bus_idx >= AudioServer.bus_count:
|
|
108
108
|
return _log.failure("No bus at index " + str(bus_idx))
|
|
109
109
|
|
|
@@ -150,8 +150,8 @@ func set_audio_bus_effect(params: Dictionary) -> Dictionary:
|
|
|
150
150
|
|
|
151
151
|
|
|
152
152
|
func set_audio_bus_volume(params: Dictionary) -> Dictionary:
|
|
153
|
-
var bus_idx: int =
|
|
154
|
-
var volume_db: float =
|
|
153
|
+
var bus_idx: int = GdhRead.as_int(params.get("bus_index", 0))
|
|
154
|
+
var volume_db: float = GdhRead.as_float(params.get("volume_db", 0.0))
|
|
155
155
|
if bus_idx < 0 or bus_idx >= AudioServer.bus_count:
|
|
156
156
|
return _log.failure("No bus at index " + str(bus_idx))
|
|
157
157
|
|
|
@@ -5,18 +5,18 @@ extends RefCounted
|
|
|
5
5
|
# A game started from a stale editor then cannot resolve any class_name written since, and the
|
|
6
6
|
# engine reads the same file headless, so the list is rebuilt here from the scripts themselves.
|
|
7
7
|
|
|
8
|
-
const
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
const
|
|
8
|
+
const GdhPatterns = preload("patterns.gd")
|
|
9
|
+
const GdhRead = preload("reading.gd")
|
|
10
|
+
const GdhFileWalk = preload("file_walk.gd")
|
|
11
|
+
const GdhLog = preload("logger.gd")
|
|
12
12
|
|
|
13
13
|
const CACHE_PATH: String = "res://.godot/global_script_class_cache.cfg"
|
|
14
14
|
|
|
15
|
-
var _log:
|
|
16
|
-
var _files:
|
|
15
|
+
var _log: GdhLog
|
|
16
|
+
var _files: GdhFileWalk = GdhFileWalk.new()
|
|
17
17
|
|
|
18
18
|
|
|
19
|
-
func _init(p_log:
|
|
19
|
+
func _init(p_log: GdhLog) -> void:
|
|
20
20
|
_log = p_log
|
|
21
21
|
|
|
22
22
|
|
|
@@ -102,8 +102,8 @@ func _entry_for(path: String, skipped: Array[Dictionary]) -> Dictionary:
|
|
|
102
102
|
"base": StringName(str(base)),
|
|
103
103
|
"class": StringName(declared),
|
|
104
104
|
"icon": str(header.get("icon", "")),
|
|
105
|
-
"is_abstract":
|
|
106
|
-
"is_tool":
|
|
105
|
+
"is_abstract": GdhRead.as_bool(header.get("abstract", false)),
|
|
106
|
+
"is_tool": GdhRead.as_bool(header.get("tool", false)),
|
|
107
107
|
"language": StringName("GDScript"),
|
|
108
108
|
"path": path,
|
|
109
109
|
}
|
|
@@ -166,7 +166,7 @@ static func _extends_parts(text: String) -> Dictionary:
|
|
|
166
166
|
path = rest.substr(1, closing - 1)
|
|
167
167
|
rest = rest.substr(closing + 1)
|
|
168
168
|
var names: Array[String] = []
|
|
169
|
-
var name_at: RegEx =
|
|
169
|
+
var name_at: RegEx = GdhPatterns.compiled("^([A-Za-z_][A-Za-z0-9_]*)")
|
|
170
170
|
var dotted: bool = not path.is_empty()
|
|
171
171
|
while true:
|
|
172
172
|
if dotted:
|
|
@@ -195,8 +195,8 @@ static func _as_the_engine_opens(path: String) -> String:
|
|
|
195
195
|
# previous, as {"found": true, "extends": the text after its keyword} with no "extends" for one that
|
|
196
196
|
# extends nothing, or {} when one of them is not there.
|
|
197
197
|
func _inner_class(lines: PackedStringArray, names: Array[String]) -> Dictionary:
|
|
198
|
-
var class_line: RegEx =
|
|
199
|
-
var inline_extends: RegEx =
|
|
198
|
+
var class_line: RegEx = GdhPatterns.compiled("^class\\s+([A-Za-z_][A-Za-z0-9_]*)\\s*(.*)$")
|
|
199
|
+
var inline_extends: RegEx = GdhPatterns.compiled("^extends\\s+(.*)$")
|
|
200
200
|
var start: int = 0
|
|
201
201
|
var end: int = lines.size()
|
|
202
202
|
var depth: int = 0
|
|
@@ -255,9 +255,9 @@ func _header_of(path: String) -> Dictionary:
|
|
|
255
255
|
# which is where the body begins.
|
|
256
256
|
func _header_in(lines: PackedStringArray) -> Dictionary:
|
|
257
257
|
var header: Dictionary = {}
|
|
258
|
-
var annotation: RegEx =
|
|
259
|
-
var extends_line: RegEx =
|
|
260
|
-
var same_line: RegEx =
|
|
258
|
+
var annotation: RegEx = GdhPatterns.compiled('^@([a-z_]+)(?:\\(\\s*(?:"([^"]*)")?[^)]*\\))?\\s*')
|
|
259
|
+
var extends_line: RegEx = GdhPatterns.compiled("^extends\\s+(.*)$")
|
|
260
|
+
var same_line: RegEx = GdhPatterns.compiled("^\\s+extends\\s+(.*)$")
|
|
261
261
|
for line: String in lines:
|
|
262
262
|
var rest: String = line.strip_edges()
|
|
263
263
|
# Annotations may share a line with what they annotate, as in `@abstract class_name X`.
|
|
@@ -275,7 +275,7 @@ func _header_in(lines: PackedStringArray) -> Dictionary:
|
|
|
275
275
|
if rest.is_empty() or rest.begins_with("#"):
|
|
276
276
|
continue
|
|
277
277
|
if rest.begins_with("class_name"):
|
|
278
|
-
var declared: RegExMatch =
|
|
278
|
+
var declared: RegExMatch = GdhPatterns.declared_class(rest)
|
|
279
279
|
if declared != null:
|
|
280
280
|
header["class_name"] = declared.get_string(1)
|
|
281
281
|
var after: RegExMatch = same_line.search(rest.substr(declared.get_end(1)))
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
extends RefCounted
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
const
|
|
5
|
-
const
|
|
3
|
+
const GdhRead = preload("reading.gd")
|
|
4
|
+
const GdhLog = preload("logger.gd")
|
|
5
|
+
const GdhSerialisation = preload("serialisation.gd")
|
|
6
6
|
|
|
7
7
|
# The base classes each category name stands for. Kept to the list the tool's schema offers, which
|
|
8
8
|
# a case holds: the schema offered physics3d and ui, which this refused, and physics answered the 3D
|
|
@@ -33,11 +33,11 @@ const METHOD_FLAG_NAMES: Dictionary[int, String] = {
|
|
|
33
33
|
METHOD_FLAG_VIRTUAL_REQUIRED: "virtual_required",
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
var _log:
|
|
37
|
-
var _values:
|
|
36
|
+
var _log: GdhLog
|
|
37
|
+
var _values: GdhSerialisation = GdhSerialisation.new()
|
|
38
38
|
|
|
39
39
|
|
|
40
|
-
func _init(p_log:
|
|
40
|
+
func _init(p_log: GdhLog) -> void:
|
|
41
41
|
_log = p_log
|
|
42
42
|
|
|
43
43
|
|
|
@@ -53,7 +53,7 @@ static func _under_any(name: String, bases: Array) -> bool:
|
|
|
53
53
|
func query_classes(params: Dictionary) -> Dictionary:
|
|
54
54
|
var filter: String = str(params.get("filter", ""))
|
|
55
55
|
var category: String = str(params.get("category", ""))
|
|
56
|
-
var instantiable_only: bool =
|
|
56
|
+
var instantiable_only: bool = GdhRead.as_bool(params.get("instantiable_only", false))
|
|
57
57
|
|
|
58
58
|
_log.info(
|
|
59
59
|
(
|
|
@@ -107,7 +107,7 @@ func query_classes(params: Dictionary) -> Dictionary:
|
|
|
107
107
|
# Query detailed info about a specific class from ClassDB
|
|
108
108
|
func query_class_info(params: Dictionary) -> Dictionary:
|
|
109
109
|
var class_name_str: String = str(params.get("class_name", ""))
|
|
110
|
-
var include_inherited: bool =
|
|
110
|
+
var include_inherited: bool = GdhRead.as_bool(params.get("include_inherited", false))
|
|
111
111
|
|
|
112
112
|
_log.info(
|
|
113
113
|
"Querying class info for: " + class_name_str + " (include_inherited: " + str(include_inherited) + ")"
|
|
@@ -183,13 +183,23 @@ func _not_found(wanted: String) -> Dictionary:
|
|
|
183
183
|
)
|
|
184
184
|
)
|
|
185
185
|
)
|
|
186
|
+
# The usual miss is a spelling, one letter off a class the engine has.
|
|
187
|
+
var close: PackedStringArray = GdhRead.nearest(wanted, ClassDB.get_class_list())
|
|
188
|
+
var offered: String = (
|
|
189
|
+
""
|
|
190
|
+
if close.is_empty()
|
|
191
|
+
else (
|
|
192
|
+
" The nearest engine %s %s."
|
|
193
|
+
% ["class is" if close.size() == 1 else "classes are", ", ".join(close)]
|
|
194
|
+
)
|
|
195
|
+
)
|
|
186
196
|
return _log.failure(
|
|
187
197
|
(
|
|
188
198
|
(
|
|
189
|
-
"Class not found: %s
|
|
190
|
-
+ "script declares is not in it either; script_info structure reads one."
|
|
199
|
+
"Class not found: %s.%s editor_classes reads the engine's ClassDB, so a class_name a "
|
|
200
|
+
+ "project script declares is not in it either; script_info structure reads one."
|
|
191
201
|
)
|
|
192
|
-
% wanted
|
|
202
|
+
% [wanted, offered]
|
|
193
203
|
)
|
|
194
204
|
)
|
|
195
205
|
|
|
@@ -344,7 +354,7 @@ func _methods_of(class_name_str: String, include_inherited: bool) -> Array[Dicti
|
|
|
344
354
|
"class_name": return_info.get("class_name", "")
|
|
345
355
|
},
|
|
346
356
|
"flags": m.get("flags", 0),
|
|
347
|
-
"flag_names": _flag_names(
|
|
357
|
+
"flag_names": _flag_names(GdhRead.as_int(m.get("flags", 0))),
|
|
348
358
|
# As values, tagged the way every other answer tags one: JSON writes a Vector2i it is
|
|
349
359
|
# handed as its text, so TileMapLayer.set_cell's default atlas coords read "(-1, -1)".
|
|
350
360
|
"default_args": _values.serialize_value(m.get("default_args", []))
|
|
@@ -370,12 +380,12 @@ static func _flag_names(flags: int) -> Array[String]:
|
|
|
370
380
|
# Nil is a value that can be anything when the usage says so, and [param nil_name] otherwise: void
|
|
371
381
|
# for what a method returns, and Variant for an argument or property with no type.
|
|
372
382
|
static func _type_name(info: Dictionary, nil_name: String) -> String:
|
|
373
|
-
var type: int =
|
|
383
|
+
var type: int = GdhRead.as_int(info.get("type", 0))
|
|
374
384
|
var named: String = str(info.get("class_name", ""))
|
|
375
385
|
if type == TYPE_OBJECT and not named.is_empty():
|
|
376
386
|
return named
|
|
377
387
|
if type == TYPE_NIL:
|
|
378
|
-
return "Variant" if
|
|
388
|
+
return "Variant" if GdhRead.as_int(info.get("usage", 0)) & PROPERTY_USAGE_NIL_IS_VARIANT else nil_name
|
|
379
389
|
return type_string(type)
|
|
380
390
|
|
|
381
391
|
|
|
@@ -384,7 +394,7 @@ func _properties_of(class_name_str: String, include_inherited: bool) -> Array[Di
|
|
|
384
394
|
|
|
385
395
|
for p: Dictionary in ClassDB.class_get_property_list(class_name_str, not include_inherited):
|
|
386
396
|
# A category, group or subgroup is an editor heading rather than a property.
|
|
387
|
-
var usage: int =
|
|
397
|
+
var usage: int = GdhRead.as_int(p.get("usage", 0))
|
|
388
398
|
if usage & PROPERTY_USAGE_CATEGORY or usage & PROPERTY_USAGE_GROUP or usage & PROPERTY_USAGE_SUBGROUP:
|
|
389
399
|
continue
|
|
390
400
|
properties.append(
|