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,8 +8,9 @@ extends RefCounted
|
|
|
8
8
|
## only true of a running game: reaching a node by path, and fitting an argument to the type the
|
|
9
9
|
## method it is going to declares.
|
|
10
10
|
|
|
11
|
-
const
|
|
12
|
-
const
|
|
11
|
+
const GdhRead = preload("reading.gd")
|
|
12
|
+
const GdhSerialisation = preload("serialisation.gd")
|
|
13
|
+
const GdhShapes = preload("value_shapes.gd")
|
|
13
14
|
|
|
14
15
|
## The engine singletons a call, a write or a read may name instead of a node: what the engine
|
|
15
16
|
## measures, and the engine settings that are the game's own. Not every singleton: OS runs programs
|
|
@@ -30,7 +31,7 @@ const SINGLETONS: Array[String] = [
|
|
|
30
31
|
## The most frames a press is held in one call, as many as a frames wait takes.
|
|
31
32
|
const MOST_HELD: int = 600
|
|
32
33
|
|
|
33
|
-
var _shared:
|
|
34
|
+
var _shared: GdhSerialisation = GdhSerialisation.new()
|
|
34
35
|
|
|
35
36
|
|
|
36
37
|
## Converts a Godot value into something JSON can carry.
|
|
@@ -70,10 +71,36 @@ static func node_at(root: Node, path: String) -> Dictionary:
|
|
|
70
71
|
|
|
71
72
|
var node: Node = root.get_node_or_null(path)
|
|
72
73
|
if node == null:
|
|
73
|
-
return {"type": "error", "message":
|
|
74
|
+
return {"type": "error", "message": _not_found(root, path)}
|
|
74
75
|
return {"node": node}
|
|
75
76
|
|
|
76
77
|
|
|
78
|
+
## "Node not found" for [param path], with the deepest node of it the game has and the children that
|
|
79
|
+
## node has instead, as a scene's refusal names them: a mistyped step was otherwise found by reading
|
|
80
|
+
## the whole tree.
|
|
81
|
+
static func _not_found(root: Node, path: String) -> String:
|
|
82
|
+
var said: String = "Node not found: " + path
|
|
83
|
+
var parts: PackedStringArray = path.split("/", false)
|
|
84
|
+
var at: Node = root
|
|
85
|
+
var step: int = 0
|
|
86
|
+
if path.begins_with("/"):
|
|
87
|
+
if parts.is_empty() or parts[0] != str(root.name):
|
|
88
|
+
return said
|
|
89
|
+
step = 1
|
|
90
|
+
while step < parts.size():
|
|
91
|
+
var below: Node = at.get_node_or_null(NodePath(parts[step]))
|
|
92
|
+
if below == null:
|
|
93
|
+
break
|
|
94
|
+
at = below
|
|
95
|
+
step += 1
|
|
96
|
+
if step >= parts.size():
|
|
97
|
+
return said
|
|
98
|
+
var names: PackedStringArray = []
|
|
99
|
+
for child: Node in at.get_children():
|
|
100
|
+
var _named: bool = names.append(str(child.name))
|
|
101
|
+
return GdhRead.missing_below(said, "", PackedStringArray([str(at.get_path())]), parts[step], names)
|
|
102
|
+
|
|
103
|
+
|
|
77
104
|
## How many process frames a whole press holds before its release, from `holdFrames`: one unless
|
|
78
105
|
## asked, as the halves of a press have always been a frame apart. Held inside the game because the
|
|
79
106
|
## time between a press, a wait and a release sent as three calls counts as held too: a 60-frame
|
|
@@ -86,7 +113,7 @@ static func held_for(params: Dictionary) -> Variant:
|
|
|
86
113
|
"holdFrames holds a whole press and then lets go, and pressed asks for one half of one:"
|
|
87
114
|
+ " give one or the other."
|
|
88
115
|
)
|
|
89
|
-
var frames: int =
|
|
116
|
+
var frames: int = GdhRead.as_int(params["holdFrames"], 0)
|
|
90
117
|
if frames < 1 or frames > MOST_HELD:
|
|
91
118
|
return "holdFrames is %d, and it takes 1 to %d." % [frames, MOST_HELD]
|
|
92
119
|
return frames
|
|
@@ -126,12 +153,12 @@ static func target_at(root: Node, path: String) -> Dictionary:
|
|
|
126
153
|
|
|
127
154
|
## Whether two values can be compared without the comparison itself failing.
|
|
128
155
|
static func comparable(one: Variant, other: Variant) -> bool:
|
|
129
|
-
return
|
|
156
|
+
return GdhSerialisation.comparable(one, other)
|
|
130
157
|
|
|
131
158
|
|
|
132
159
|
## Whether a value can be handed to a parameter declared as [param type] without the call failing.
|
|
133
160
|
static func acceptable(value: Variant, type: int) -> bool:
|
|
134
|
-
return
|
|
161
|
+
return GdhSerialisation.acceptable(value, type)
|
|
135
162
|
|
|
136
163
|
|
|
137
164
|
## Whether [param given] would have to be cut to become a [param type]: a fraction made a whole
|
|
@@ -146,10 +173,10 @@ static func cuts(given: Variant, type: int) -> bool:
|
|
|
146
173
|
var number: Variant = given
|
|
147
174
|
if given is String:
|
|
148
175
|
var text: String = given
|
|
149
|
-
number =
|
|
176
|
+
number = GdhRead.json_or_null(text)
|
|
150
177
|
if typeof(number) != TYPE_FLOAT and typeof(number) != TYPE_INT:
|
|
151
178
|
return false
|
|
152
|
-
var value: float =
|
|
179
|
+
var value: float = GdhRead.as_float(number)
|
|
153
180
|
var whole: bool = value == floorf(value)
|
|
154
181
|
if type == TYPE_INT:
|
|
155
182
|
return not whole
|
|
@@ -161,14 +188,14 @@ static func cut_by(type: int) -> String:
|
|
|
161
188
|
return "an int" if type == TYPE_INT else "a bool"
|
|
162
189
|
|
|
163
190
|
|
|
164
|
-
## Why [param given] may not be the whole number sent, or "": see [method
|
|
191
|
+
## Why [param given] may not be the whole number sent, or "": see [method GdhSerialisation.inexact].
|
|
165
192
|
static func inexact(given: Variant, type: int) -> String:
|
|
166
|
-
return
|
|
193
|
+
return GdhSerialisation.inexact(given, type)
|
|
167
194
|
|
|
168
195
|
|
|
169
|
-
##
|
|
170
|
-
static func
|
|
171
|
-
return
|
|
196
|
+
## What a refusal of [param value] where a [param type] goes ends with: see [method GdhShapes.remedy].
|
|
197
|
+
static func remedy(value: Variant, type: int) -> String:
|
|
198
|
+
return GdhShapes.remedy(value, type)
|
|
172
199
|
|
|
173
200
|
|
|
174
201
|
## The declared type of one parameter of a method, or TYPE_NIL when the method or the parameter
|
|
@@ -181,7 +208,7 @@ func parameter_type(object: Object, method: String, index: int) -> int:
|
|
|
181
208
|
if index < 0 or index >= params.size():
|
|
182
209
|
return TYPE_NIL
|
|
183
210
|
var parameter: Dictionary = params[index]
|
|
184
|
-
return
|
|
211
|
+
return GdhRead.as_int(parameter.get("type", TYPE_NIL), TYPE_NIL)
|
|
185
212
|
return TYPE_NIL
|
|
186
213
|
|
|
187
214
|
|
|
@@ -214,7 +241,7 @@ func parameter_class(object: Object, method: String, index: int) -> String:
|
|
|
214
241
|
static func slot_declared(holder: Variant, named: String) -> Dictionary:
|
|
215
242
|
# A component holds one kind of value and no other: a vector's axis is a number, its integer
|
|
216
243
|
# form's an integer, a rectangle's size a vector of the same form.
|
|
217
|
-
if named in
|
|
244
|
+
if named in GdhSerialisation.components_of(holder):
|
|
218
245
|
var component: Variant = holder[named]
|
|
219
246
|
return {"type": typeof(component), "class": "", "script": null}
|
|
220
247
|
if holder is Array:
|
|
@@ -238,7 +265,7 @@ static func slot_declared(holder: Variant, named: String) -> Dictionary:
|
|
|
238
265
|
for entry: Dictionary in object.get_property_list():
|
|
239
266
|
if str(entry.get("name", "")) == named:
|
|
240
267
|
return {
|
|
241
|
-
"type":
|
|
268
|
+
"type": GdhRead.as_int(entry.get("type", TYPE_NIL), TYPE_NIL),
|
|
242
269
|
"class": str(entry.get("class_name", "")),
|
|
243
270
|
"script": null,
|
|
244
271
|
}
|
|
@@ -336,7 +363,7 @@ func parameter_container(object: Object, method: String, index: int) -> Variant:
|
|
|
336
363
|
if index < 0 or index >= params.size():
|
|
337
364
|
return null
|
|
338
365
|
var parameter: Dictionary = params[index]
|
|
339
|
-
var hint: int =
|
|
366
|
+
var hint: int = GdhRead.as_int(parameter.get("hint", 0), 0)
|
|
340
367
|
var named: String = str(parameter.get("hint_string", ""))
|
|
341
368
|
if hint == PROPERTY_HINT_ARRAY_TYPE:
|
|
342
369
|
var element: Dictionary = _element_type(named)
|
|
@@ -414,7 +441,7 @@ func typed_like(given: Variant, like: Variant, resolve: Callable) -> Dictionary:
|
|
|
414
441
|
built, template.get_typed_builtin(), template.get_typed_class_name(), template.get_typed_script()
|
|
415
442
|
)
|
|
416
443
|
if typed.size() != built.size():
|
|
417
|
-
return {"message": "the engine would not take its elements as what the list holds"}
|
|
444
|
+
return {"message": "the engine would not take its elements as what the list holds."}
|
|
418
445
|
return {"value": typed}
|
|
419
446
|
if like is Dictionary and given is Dictionary:
|
|
420
447
|
var map_like: Dictionary = like
|
|
@@ -452,13 +479,14 @@ func typed_like(given: Variant, like: Variant, resolve: Callable) -> Dictionary:
|
|
|
452
479
|
map_like.get_typed_value_script()
|
|
453
480
|
)
|
|
454
481
|
if typed_map.size() != rebuilt.size():
|
|
455
|
-
return {"message": "the engine would not take its entries as what the map holds"}
|
|
482
|
+
return {"message": "the engine would not take its entries as what the map holds."}
|
|
456
483
|
return {"value": typed_map}
|
|
457
484
|
return {"value": given}
|
|
458
485
|
|
|
459
486
|
|
|
460
487
|
## One element fitted to a container's element type, as `{"value"}` or `{"message"}` saying what it
|
|
461
|
-
## was and what goes there. The message is the part after "element N"
|
|
488
|
+
## was and what goes there. The message is the part after "element N", whole sentences, since it
|
|
489
|
+
## can end with how a value of that type is sent.
|
|
462
490
|
##
|
|
463
491
|
## An object element given as a record goes to [param resolve] as a path would, which says how an
|
|
464
492
|
## object is named, rather than being refused as a map that cannot become the class.
|
|
@@ -468,7 +496,7 @@ func _element(item: Variant, builtin: int, declared: String, script: Script, res
|
|
|
468
496
|
if builtin == TYPE_OBJECT and (item is String or item is Dictionary):
|
|
469
497
|
var named: Dictionary = resolve.call(item, declared, script)
|
|
470
498
|
if named.has("message"):
|
|
471
|
-
return {"message": str(named["message"])}
|
|
499
|
+
return {"message": str(named["message"]) + "."}
|
|
472
500
|
return {"value": named["object"]}
|
|
473
501
|
var fitted_item: Variant = fitted(item, builtin)
|
|
474
502
|
if not acceptable(fitted_item, builtin):
|
|
@@ -477,7 +505,6 @@ func _element(item: Variant, builtin: int, declared: String, script: Script, res
|
|
|
477
505
|
var wanted: String = type_string(builtin)
|
|
478
506
|
if builtin == TYPE_OBJECT:
|
|
479
507
|
wanted = declared if script == null else script_named(script)
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
}
|
|
508
|
+
var sent: String = JSON.stringify(serialize(item), "", false)
|
|
509
|
+
return {"message": "is %s, which cannot become %s.%s" % [sent, wanted, remedy(item, builtin)]}
|
|
483
510
|
return {"value": fitted_item}
|
|
@@ -3,12 +3,12 @@ extends RefCounted
|
|
|
3
3
|
## The commands that take time: they let the game run and answer when what was waited for has
|
|
4
4
|
## happened, or when the time ran out, so the caller never sleeps for a guessed length.
|
|
5
5
|
|
|
6
|
-
const
|
|
7
|
-
const
|
|
8
|
-
const
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
const
|
|
6
|
+
const GdhChanges = preload("runtime_changes.gd")
|
|
7
|
+
const GdhPaths = preload("runtime_paths.gd")
|
|
8
|
+
const GdhQueries = preload("runtime_queries.gd")
|
|
9
|
+
const GdhRead = preload("reading.gd")
|
|
10
|
+
const GdhSays = preload("runtime_says.gd")
|
|
11
|
+
const GdhValues = preload("runtime_values.gd")
|
|
12
12
|
|
|
13
13
|
## The longest one wait may last, whatever the request says: past this the server has long
|
|
14
14
|
## since given up on the reply.
|
|
@@ -30,12 +30,12 @@ const COMPARES: Array[String] = ["==", "!=", ">=", "<=", ">", "<"]
|
|
|
30
30
|
const MOST_WATCHES: int = 16
|
|
31
31
|
|
|
32
32
|
var _host: Node
|
|
33
|
-
var _values:
|
|
34
|
-
var _watches: Dictionary[int,
|
|
33
|
+
var _values: GdhValues
|
|
34
|
+
var _watches: Dictionary[int, GdhWatch] = {}
|
|
35
35
|
var _next_watch: int = 1
|
|
36
36
|
|
|
37
37
|
|
|
38
|
-
func _init(host: Node, values:
|
|
38
|
+
func _init(host: Node, values: GdhValues) -> void:
|
|
39
39
|
_host = host
|
|
40
40
|
_values = values
|
|
41
41
|
|
|
@@ -52,7 +52,7 @@ static func _out_of_range(named: String, asked: int, least: int, most: int) -> D
|
|
|
52
52
|
|
|
53
53
|
|
|
54
54
|
func wait_frames(params: Dictionary) -> Dictionary:
|
|
55
|
-
var frames: int =
|
|
55
|
+
var frames: int = GdhRead.as_int(params.get("frames", 1), 1)
|
|
56
56
|
if frames < 1 or frames > MOST_FRAMES:
|
|
57
57
|
return _out_of_range("frames", frames, 1, MOST_FRAMES)
|
|
58
58
|
var started: int = Time.get_ticks_msec()
|
|
@@ -66,22 +66,22 @@ func wait_frames(params: Dictionary) -> Dictionary:
|
|
|
66
66
|
func wait_signal(params: Dictionary) -> Dictionary:
|
|
67
67
|
var node_path: String = str(params.get("path", ""))
|
|
68
68
|
var signal_name: String = str(params.get("signal", ""))
|
|
69
|
-
var timeout_ms: int =
|
|
69
|
+
var timeout_ms: int = GdhRead.as_int(params.get("timeout_ms", 5000), 5000)
|
|
70
70
|
if timeout_ms < 1 or timeout_ms > CEILING_MSEC:
|
|
71
71
|
return _out_of_range("timeout_ms", timeout_ms, 1, CEILING_MSEC)
|
|
72
72
|
if params.has("watch"):
|
|
73
|
-
return await _collect(
|
|
73
|
+
return await _collect(GdhRead.as_int(params["watch"], 0), timeout_ms)
|
|
74
74
|
if node_path.is_empty() or signal_name.is_empty():
|
|
75
75
|
return {"type": "error", "message": "Node path and signal name required"}
|
|
76
76
|
|
|
77
|
-
var standing: Dictionary =
|
|
77
|
+
var standing: Dictionary = GdhValues.node_at(_host.get_tree().root, node_path)
|
|
78
78
|
if standing.has("message"):
|
|
79
79
|
return standing
|
|
80
80
|
var node: Node = standing["node"]
|
|
81
81
|
if not node.has_signal(signal_name):
|
|
82
82
|
return {"type": "error", "message": "%s has no signal %s" % [node_path, signal_name]}
|
|
83
83
|
|
|
84
|
-
var catcher:
|
|
84
|
+
var catcher: GdhSignalCatcher = GdhSignalCatcher.new()
|
|
85
85
|
var callable: Callable = catcher._on_fired
|
|
86
86
|
var listening: Error = node.connect(signal_name, callable, CONNECT_ONE_SHOT)
|
|
87
87
|
if listening != OK:
|
|
@@ -119,13 +119,13 @@ func watch_signal(params: Dictionary) -> Dictionary:
|
|
|
119
119
|
var signal_name: String = str(params.get("signal", ""))
|
|
120
120
|
if node_path.is_empty() or signal_name.is_empty():
|
|
121
121
|
return {"type": "error", "message": "Node path and signal name required"}
|
|
122
|
-
var standing: Dictionary =
|
|
122
|
+
var standing: Dictionary = GdhValues.node_at(_host.get_tree().root, node_path)
|
|
123
123
|
if standing.has("message"):
|
|
124
124
|
return standing
|
|
125
125
|
var node: Node = standing["node"]
|
|
126
126
|
if not node.has_signal(signal_name):
|
|
127
127
|
return {"type": "error", "message": "%s has no signal %s" % [node_path, signal_name]}
|
|
128
|
-
var watch:
|
|
128
|
+
var watch: GdhWatch = GdhWatch.new(node, node_path, signal_name)
|
|
129
129
|
var listening: Error = node.connect(signal_name, watch.catcher._on_fired)
|
|
130
130
|
if listening != OK:
|
|
131
131
|
return {"type": "error", "message": "Could not listen to signal: " + signal_name}
|
|
@@ -153,7 +153,7 @@ func _collect(id: int, timeout_ms: int) -> Dictionary:
|
|
|
153
153
|
% [id, MOST_WATCHES]
|
|
154
154
|
),
|
|
155
155
|
}
|
|
156
|
-
var watch:
|
|
156
|
+
var watch: GdhWatch = _watches[id]
|
|
157
157
|
var started: int = Time.get_ticks_msec()
|
|
158
158
|
var before: bool = watch.catcher.fired
|
|
159
159
|
while not watch.catcher.fired and Time.get_ticks_msec() - started < timeout_ms:
|
|
@@ -175,7 +175,7 @@ func _collect(id: int, timeout_ms: int) -> Dictionary:
|
|
|
175
175
|
|
|
176
176
|
|
|
177
177
|
func _let_go(id: int) -> void:
|
|
178
|
-
var watch:
|
|
178
|
+
var watch: GdhWatch = _watches.get(id)
|
|
179
179
|
var _gone: bool = _watches.erase(id)
|
|
180
180
|
if watch == null:
|
|
181
181
|
return
|
|
@@ -192,12 +192,12 @@ func wait_until(params: Dictionary) -> Dictionary:
|
|
|
192
192
|
var node_path: String = str(params.get("path", ""))
|
|
193
193
|
var property: String = str(params.get("property", ""))
|
|
194
194
|
var says: String = str(params.get("says", ""))
|
|
195
|
-
var timeout_ms: int =
|
|
195
|
+
var timeout_ms: int = GdhRead.as_int(params.get("timeout_ms", 5000), 5000)
|
|
196
196
|
if timeout_ms < 1 or timeout_ms > CEILING_MSEC:
|
|
197
197
|
return _out_of_range("timeout_ms", timeout_ms, 1, CEILING_MSEC)
|
|
198
198
|
if node_path.is_empty():
|
|
199
199
|
return {"type": "error", "message": "Node path required"}
|
|
200
|
-
var standing: Dictionary =
|
|
200
|
+
var standing: Dictionary = GdhValues.node_at(_host.get_tree().root, node_path)
|
|
201
201
|
if standing.has("message"):
|
|
202
202
|
return standing
|
|
203
203
|
# Two different questions, not two ways of asking one: says looks for words anywhere under the
|
|
@@ -220,7 +220,7 @@ func wait_until(params: Dictionary) -> Dictionary:
|
|
|
220
220
|
)
|
|
221
221
|
}
|
|
222
222
|
if not says.is_empty():
|
|
223
|
-
var include_hidden: bool =
|
|
223
|
+
var include_hidden: bool = GdhRead.as_bool(params.get("include_hidden", false))
|
|
224
224
|
return await _wait_until_said(node_path, says, timeout_ms, include_hidden)
|
|
225
225
|
var unasked: String = _property_wait_unasked(property, params)
|
|
226
226
|
if not unasked.is_empty():
|
|
@@ -250,12 +250,12 @@ func wait_until(params: Dictionary) -> Dictionary:
|
|
|
250
250
|
# two ops would disagree about one property, and the wait would run out on the text null.
|
|
251
251
|
var untyped_object: bool = declared == TYPE_NIL and (current == null or typeof(current) == TYPE_OBJECT)
|
|
252
252
|
var holds_objects: bool = declared == TYPE_OBJECT or untyped_object
|
|
253
|
-
if holds_objects and
|
|
253
|
+
if holds_objects and GdhChanges.names_no_object(wanted):
|
|
254
254
|
wanted = null
|
|
255
255
|
var refused: String = _not_comparable(current, wanted, node_path, property)
|
|
256
256
|
if refused.is_empty():
|
|
257
257
|
refused = _cut_to_fit(params["value"], typeof(current), node_path, property)
|
|
258
|
-
var rounded: String =
|
|
258
|
+
var rounded: String = GdhValues.inexact(params["value"], typeof(current))
|
|
259
259
|
if refused.is_empty() and not rounded.is_empty():
|
|
260
260
|
refused = "%s.%s holds int: %s." % [node_path, property, rounded]
|
|
261
261
|
var never_held: bool = typeof(wanted) != TYPE_NIL and typeof(wanted) != TYPE_OBJECT
|
|
@@ -279,8 +279,8 @@ func wait_until(params: Dictionary) -> Dictionary:
|
|
|
279
279
|
# the wait ran out on a value the game had reached.
|
|
280
280
|
var differs: bool = compare == "!="
|
|
281
281
|
while (
|
|
282
|
-
|
|
283
|
-
and
|
|
282
|
+
GdhValues.comparable(current, wanted)
|
|
283
|
+
and GdhChanges.held_already(wanted, current) == differs
|
|
284
284
|
and Time.get_ticks_msec() - started < timeout_ms
|
|
285
285
|
):
|
|
286
286
|
await _host.get_tree().process_frame
|
|
@@ -297,7 +297,7 @@ func wait_until(params: Dictionary) -> Dictionary:
|
|
|
297
297
|
"path": node_path,
|
|
298
298
|
"property": property,
|
|
299
299
|
"compare": compare,
|
|
300
|
-
"met":
|
|
300
|
+
"met": GdhValues.comparable(current, wanted) and GdhChanges.held_already(wanted, current) != differs,
|
|
301
301
|
"value": _values.serialize(current),
|
|
302
302
|
"elapsed_ms": Time.get_ticks_msec() - started,
|
|
303
303
|
"frames": frames,
|
|
@@ -334,7 +334,7 @@ func _wait_in_order(node: Node, params: Dictionary, current: Variant, timeout_ms
|
|
|
334
334
|
var number: Variant = given
|
|
335
335
|
if given is String:
|
|
336
336
|
var text: String = given
|
|
337
|
-
number =
|
|
337
|
+
number = GdhRead.json_or_null(text)
|
|
338
338
|
if typeof(number) != TYPE_INT and typeof(number) != TYPE_FLOAT:
|
|
339
339
|
return {
|
|
340
340
|
"type": "error",
|
|
@@ -344,7 +344,7 @@ func _wait_in_order(node: Node, params: Dictionary, current: Variant, timeout_ms
|
|
|
344
344
|
% [compare, str(given)]
|
|
345
345
|
)
|
|
346
346
|
}
|
|
347
|
-
var limit: float =
|
|
347
|
+
var limit: float = GdhRead.as_float(number)
|
|
348
348
|
|
|
349
349
|
var started: int = Time.get_ticks_msec()
|
|
350
350
|
var frames: int = 0
|
|
@@ -386,9 +386,9 @@ static func _no_order(current: Variant, node_path: String, property: String, com
|
|
|
386
386
|
## Whether [param current], a number, stands to [param limit] as [param compare] says. Level counts
|
|
387
387
|
## at the precision the property keeps, so a 32-bit float holding 0.30000001 is at least 0.3.
|
|
388
388
|
static func _in_order(compare: String, limit: float, current: Variant) -> bool:
|
|
389
|
-
var reading: float =
|
|
389
|
+
var reading: float = GdhRead.as_float(current)
|
|
390
390
|
var level: bool = (
|
|
391
|
-
reading == limit or (typeof(current) == TYPE_FLOAT and
|
|
391
|
+
reading == limit or (typeof(current) == TYPE_FLOAT and GdhChanges.held_already(limit, current))
|
|
392
392
|
)
|
|
393
393
|
match compare:
|
|
394
394
|
">=":
|
|
@@ -407,14 +407,14 @@ static func _in_order(compare: String, limit: float, current: Variant) -> bool:
|
|
|
407
407
|
## conversion truncates: 3.5 waited on an int became 3 and 0.2 on a bool became true, and a property
|
|
408
408
|
## already holding that answered met at once, about a state nobody asked for.
|
|
409
409
|
static func _cut_to_fit(given: Variant, type: int, node_path: String, property: String) -> String:
|
|
410
|
-
if not
|
|
410
|
+
if not GdhValues.cuts(given, type):
|
|
411
411
|
return ""
|
|
412
412
|
return (
|
|
413
413
|
(
|
|
414
414
|
"%s.%s holds %s and the value to wait for is %s, which %s cannot hold without changing it:"
|
|
415
415
|
+ " waiting on it would answer about a state nobody asked for."
|
|
416
416
|
)
|
|
417
|
-
% [node_path, property, type_string(type), str(given),
|
|
417
|
+
% [node_path, property, type_string(type), str(given), GdhValues.cut_by(type)]
|
|
418
418
|
)
|
|
419
419
|
|
|
420
420
|
|
|
@@ -436,15 +436,18 @@ static func _not_comparable(current: Variant, wanted: Variant, node_path: String
|
|
|
436
436
|
# A value that cannot become what the property holds is said as that, which names the remedy,
|
|
437
437
|
# before the comparison it would also fail; an object on either side is said as the comparison.
|
|
438
438
|
var objects: bool = typeof(current) == TYPE_OBJECT or typeof(wanted) == TYPE_OBJECT
|
|
439
|
-
if not objects and not
|
|
439
|
+
if not objects and not GdhValues.acceptable(wanted, typeof(current)):
|
|
440
440
|
return (
|
|
441
441
|
(
|
|
442
|
-
|
|
443
|
-
|
|
442
|
+
(
|
|
443
|
+
"%s.%s holds %s and the value to wait for is %s, which cannot become one:"
|
|
444
|
+
+ " waiting on it would answer about a state nobody asked for."
|
|
445
|
+
)
|
|
446
|
+
% types
|
|
444
447
|
)
|
|
445
|
-
|
|
448
|
+
+ GdhValues.remedy(wanted, typeof(current))
|
|
446
449
|
)
|
|
447
|
-
if not
|
|
450
|
+
if not GdhValues.comparable(current, wanted):
|
|
448
451
|
return (
|
|
449
452
|
(
|
|
450
453
|
"%s.%s holds %s and the value to wait for is %s. Those cannot be compared, and"
|
|
@@ -452,13 +455,16 @@ static func _not_comparable(current: Variant, wanted: Variant, node_path: String
|
|
|
452
455
|
)
|
|
453
456
|
% types
|
|
454
457
|
)
|
|
455
|
-
if not
|
|
458
|
+
if not GdhValues.acceptable(wanted, typeof(current)):
|
|
456
459
|
return (
|
|
457
460
|
(
|
|
458
|
-
|
|
459
|
-
|
|
461
|
+
(
|
|
462
|
+
"%s.%s holds %s and the value to wait for is %s, which cannot become one:"
|
|
463
|
+
+ " waiting on it would answer about a state nobody asked for."
|
|
464
|
+
)
|
|
465
|
+
% types
|
|
460
466
|
)
|
|
461
|
-
|
|
467
|
+
+ GdhValues.remedy(wanted, typeof(current))
|
|
462
468
|
)
|
|
463
469
|
return ""
|
|
464
470
|
|
|
@@ -472,15 +478,15 @@ static func _not_comparable(current: Variant, wanted: Variant, node_path: String
|
|
|
472
478
|
## its nodes hold: a run object built afresh each day would leave a wait watching the day before. A
|
|
473
479
|
## step that has gone in the meantime ends the wait with what went.
|
|
474
480
|
static func _watched(node: Node, node_path: String, property: String) -> Dictionary:
|
|
475
|
-
var reached: Dictionary =
|
|
481
|
+
var reached: Dictionary = GdhPaths.walk_to(node, node_path, property)
|
|
476
482
|
if reached.has("message"):
|
|
477
483
|
return reached
|
|
478
484
|
var holder: Variant = reached["holder"]
|
|
479
485
|
var named: String = reached["name"]
|
|
480
|
-
var missing: String =
|
|
486
|
+
var missing: String = GdhPaths.nothing_under(holder, named, str(reached["called"]))
|
|
481
487
|
if not missing.is_empty():
|
|
482
488
|
return {"type": "error", "message": missing}
|
|
483
|
-
return {"value":
|
|
489
|
+
return {"value": GdhPaths.read_under(holder, named), "declared": _declared_type(holder, named)}
|
|
484
490
|
|
|
485
491
|
|
|
486
492
|
## The type [param named] is declared with on [param holder], read the way a set reads it, so the two
|
|
@@ -489,9 +495,9 @@ static func _watched(node: Node, node_path: String, property: String) -> Diction
|
|
|
489
495
|
## on it runs out the timeout while a set of 5 on it is refused at once. TYPE_NIL for anything
|
|
490
496
|
## declaring none, and for a call, whose answer nothing declares.
|
|
491
497
|
static func _declared_type(holder: Variant, named: String) -> int:
|
|
492
|
-
if not
|
|
498
|
+
if not GdhPaths.method_of(named).is_empty():
|
|
493
499
|
return TYPE_NIL
|
|
494
|
-
return
|
|
500
|
+
return GdhValues.slot_declared(holder, named)["type"]
|
|
495
501
|
|
|
496
502
|
|
|
497
503
|
## Waits until something under [param node_path] has [param said] written on it.
|
|
@@ -574,7 +580,7 @@ func _anything_says(node_path: String, said: String, include_hidden: bool) -> bo
|
|
|
574
580
|
var pending: Array[Node] = [root]
|
|
575
581
|
while not pending.is_empty():
|
|
576
582
|
var node: Node = pending.pop_back()
|
|
577
|
-
if
|
|
583
|
+
if GdhSays.says(node, said) and (include_hidden or GdhQueries.shown(node)):
|
|
578
584
|
return true
|
|
579
585
|
pending.append_array(node.get_children(true))
|
|
580
586
|
return false
|
|
@@ -584,7 +590,7 @@ func _anything_says(node_path: String, said: String, include_hidden: bool) -> bo
|
|
|
584
590
|
## awaits the signal directly, so the wait can also give up. Takes any number of arguments: a
|
|
585
591
|
## handler of five fixed ones covered the engine's signals, and a game's own signal with six was
|
|
586
592
|
## never delivered to it, so the wait ran out and said the signal had not fired.
|
|
587
|
-
class
|
|
593
|
+
class GdhSignalCatcher:
|
|
588
594
|
extends RefCounted
|
|
589
595
|
var fired: bool = false
|
|
590
596
|
## How many times it fired; [member args] holds what the first carried.
|
|
@@ -599,12 +605,12 @@ class SignalCatcher:
|
|
|
599
605
|
|
|
600
606
|
|
|
601
607
|
## A signal recorded from when it was armed until it is collected.
|
|
602
|
-
class
|
|
608
|
+
class GdhWatch:
|
|
603
609
|
extends RefCounted
|
|
604
610
|
var node: WeakRef
|
|
605
611
|
var node_path: String
|
|
606
612
|
var signal_name: String
|
|
607
|
-
var catcher:
|
|
613
|
+
var catcher: GdhSignalCatcher = GdhSignalCatcher.new()
|
|
608
614
|
|
|
609
615
|
func _init(watched: Node, path: String, named: String) -> void:
|
|
610
616
|
node = weakref(watched)
|