gdharness 1.3.41 → 1.3.43
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/cli.js +131 -47
- package/build/godot/addons/auto_reload/auto_reload.gd +18 -19
- package/build/godot/addons/gdharness_editor/animation_reads.gd +84 -35
- package/build/godot/addons/gdharness_editor/bridge_client.gd +53 -58
- package/build/godot/addons/gdharness_editor/declared_values.gd +68 -32
- package/build/godot/addons/gdharness_editor/file_reads.gd +114 -39
- package/build/godot/addons/gdharness_editor/file_walk.gd +12 -5
- package/build/godot/addons/gdharness_editor/headless_check.gd +94 -41
- package/build/godot/addons/gdharness_editor/plugin.gd +2 -3
- package/build/godot/addons/gdharness_editor/property_paths.gd +22 -12
- package/build/godot/addons/gdharness_editor/property_values.gd +143 -78
- package/build/godot/addons/gdharness_editor/reading.gd +37 -12
- package/build/godot/addons/gdharness_editor/same_values.gd +4 -4
- package/build/godot/addons/gdharness_editor/scene_file.gd +136 -79
- package/build/godot/addons/gdharness_editor/scene_uids.gd +12 -6
- package/build/godot/addons/gdharness_editor/serialisation.gd +75 -51
- package/build/godot/addons/gdharness_editor/setting_reads.gd +7 -6
- package/build/godot/addons/gdharness_editor/tool_executor.gd +3 -6
- package/build/godot/addons/gdharness_editor/tools/animation_states.gd +41 -28
- package/build/godot/addons/gdharness_editor/tools/animation_tools.gd +84 -50
- package/build/godot/addons/gdharness_editor/tools/animation_tracks.gd +70 -40
- package/build/godot/addons/gdharness_editor/tools/class_tools.gd +34 -30
- package/build/godot/addons/gdharness_editor/tools/filesystem_tools.gd +24 -22
- package/build/godot/addons/gdharness_editor/tools/navigation_bake.gd +49 -29
- package/build/godot/addons/gdharness_editor/tools/play_tools.gd +26 -21
- package/build/godot/addons/gdharness_editor/tools/profile_tools.gd +40 -40
- package/build/godot/addons/gdharness_editor/tools/resource_tools.gd +181 -107
- package/build/godot/addons/gdharness_editor/tools/scene_groups.gd +16 -9
- package/build/godot/addons/gdharness_editor/tools/scene_kin.gd +75 -43
- package/build/godot/addons/gdharness_editor/tools/scene_paths.gd +35 -20
- package/build/godot/addons/gdharness_editor/tools/scene_placing.gd +28 -13
- package/build/godot/addons/gdharness_editor/tools/scene_sharing.gd +66 -0
- package/build/godot/addons/gdharness_editor/tools/scene_sharing.gd.uid +1 -0
- package/build/godot/addons/gdharness_editor/tools/scene_tools.gd +171 -62
- package/build/godot/addons/gdharness_editor/tools/script_paths.gd +20 -8
- package/build/godot/addons/gdharness_editor/tools/settings_tools.gd +11 -8
- package/build/godot/addons/gdharness_editor/tools/signal_tools.gd +47 -34
- package/build/godot/addons/gdharness_editor/tools/tileset_tools.gd +77 -50
- package/build/godot/addons/gdharness_editor/value_shapes.gd +5 -6
- package/build/godot/addons/gdharness_runtime/call_errors.gd +1 -2
- package/build/godot/addons/gdharness_runtime/declared_values.gd +68 -32
- package/build/godot/addons/gdharness_runtime/error_report.gd +1 -2
- package/build/godot/addons/gdharness_runtime/reading.gd +37 -12
- package/build/godot/addons/gdharness_runtime/runtime_accessibility.gd +84 -63
- package/build/godot/addons/gdharness_runtime/runtime_autoload.gd +153 -148
- package/build/godot/addons/gdharness_runtime/runtime_capture.gd +64 -42
- package/build/godot/addons/gdharness_runtime/runtime_changes.gd +184 -122
- package/build/godot/addons/gdharness_runtime/runtime_frames.gd +2 -2
- package/build/godot/addons/gdharness_runtime/runtime_hover.gd +24 -20
- package/build/godot/addons/gdharness_runtime/runtime_input.gd +134 -92
- package/build/godot/addons/gdharness_runtime/runtime_items.gd +29 -13
- package/build/godot/addons/gdharness_runtime/runtime_menus.gd +0 -1
- package/build/godot/addons/gdharness_runtime/runtime_mouse.gd +38 -20
- package/build/godot/addons/gdharness_runtime/runtime_pad.gd +17 -13
- package/build/godot/addons/gdharness_runtime/runtime_paths.gd +84 -46
- package/build/godot/addons/gdharness_runtime/runtime_queries.gd +117 -79
- package/build/godot/addons/gdharness_runtime/runtime_says.gd +14 -17
- package/build/godot/addons/gdharness_runtime/runtime_screen.gd +23 -16
- package/build/godot/addons/gdharness_runtime/runtime_targets.gd +117 -66
- package/build/godot/addons/gdharness_runtime/runtime_touch.gd +211 -0
- package/build/godot/addons/gdharness_runtime/runtime_touch.gd.uid +1 -0
- package/build/godot/addons/gdharness_runtime/runtime_typing.gd +44 -27
- package/build/godot/addons/gdharness_runtime/runtime_values.gd +108 -73
- package/build/godot/addons/gdharness_runtime/runtime_waits.gd +111 -84
- package/build/godot/addons/gdharness_runtime/runtime_words.gd +10 -7
- package/build/godot/addons/gdharness_runtime/serialisation.gd +75 -51
- package/build/godot/addons/gdharness_runtime/value_shapes.gd +5 -6
- package/build/godot/addons/gdharness_tests/bench.gd +4 -5
- package/build/godot/addons/gdharness_tests/event.gd +10 -5
- package/build/godot/addons/gdharness_tests/share.gd +38 -18
- package/build/godot/addons/gdharness_tests/suites.gd +3 -3
- package/build/godot/addons/gdharness_tests/wait.gd +3 -2
- package/build/godot/loader/loader.gd +9 -9
- package/build/godot/operations/animation_reads.gd +84 -35
- package/build/godot/operations/audio_buses.gd +48 -19
- package/build/godot/operations/class_cache.gd +16 -11
- package/build/godot/operations/classdb_queries.gd +89 -44
- package/build/godot/operations/declared_values.gd +68 -32
- package/build/godot/operations/dependencies.gd +139 -93
- package/build/godot/operations/editor_tools.gd +9 -10
- package/build/godot/operations/export_presets.gd +100 -54
- package/build/godot/operations/file_reads.gd +114 -39
- package/build/godot/operations/file_walk.gd +12 -5
- package/build/godot/operations/gdscript_analysis.gd +105 -66
- package/build/godot/operations/gdscript_authoring.gd +138 -39
- package/build/godot/operations/godot_operations.gd +17 -11
- package/build/godot/operations/import_pipeline.gd +110 -59
- package/build/godot/operations/input_actions.gd +63 -39
- package/build/godot/operations/patterns.gd +3 -1
- package/build/godot/operations/plugins.gd +36 -16
- package/build/godot/operations/project_config.gd +144 -27
- package/build/godot/operations/project_diagnostics.gd +24 -11
- package/build/godot/operations/project_file.gd +16 -16
- package/build/godot/operations/project_validation.gd +71 -44
- package/build/godot/operations/property_paths.gd +22 -12
- package/build/godot/operations/property_values.gd +143 -78
- package/build/godot/operations/reading.gd +37 -12
- package/build/godot/operations/rename_checks.gd +2 -2
- package/build/godot/operations/resource_files.gd +11 -9
- package/build/godot/operations/same_values.gd +4 -4
- package/build/godot/operations/scene_connections.gd +27 -13
- package/build/godot/operations/script_check.gd +52 -49
- package/build/godot/operations/serialisation.gd +75 -51
- package/build/godot/operations/setting_reads.gd +7 -6
- package/build/godot/operations/value_shapes.gd +5 -6
- package/build/godot/tests/listed.gd +321 -268
- package/package.json +1 -1
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
extends RefCounted
|
|
2
|
-
|
|
3
2
|
## The commands that take time: they let the game run and answer when what was waited for has
|
|
4
3
|
## happened, or when the time ran out, so the caller never sleeps for a guessed length.
|
|
5
4
|
|
|
@@ -10,31 +9,24 @@ const GdhQueries = preload("runtime_queries.gd")
|
|
|
10
9
|
const GdhRead = preload("reading.gd")
|
|
11
10
|
const GdhSays = preload("runtime_says.gd")
|
|
12
11
|
const GdhValues = preload("runtime_values.gd")
|
|
13
|
-
|
|
14
12
|
## The longest one wait may last, whatever the request says: past this the server has long
|
|
15
13
|
## since given up on the reply.
|
|
16
14
|
const CEILING_MSEC: int = 120000
|
|
17
|
-
|
|
18
15
|
## The most frames one wait may cover, which is about half a minute of a game drawing slowly.
|
|
19
16
|
const MOST_FRAMES: int = 600
|
|
20
|
-
|
|
21
17
|
## How long a wait for words leaves between looks, as a multiple of what the last look took: nine
|
|
22
18
|
## gives looking about a tenth of the time.
|
|
23
19
|
const LOOKS_APART: int = 9
|
|
24
|
-
|
|
25
20
|
## How a property wait compares what it reads with the value: equality by default, and order for a
|
|
26
21
|
## number that steps rather than lands, as a day counter on a fast clock goes from 31 to 34 between
|
|
27
22
|
## two looks and a wait for 32 never sees it.
|
|
28
23
|
const COMPARES: Array[String] = ["==", "!=", ">=", "<=", ">", "<"]
|
|
29
|
-
|
|
30
24
|
## The most signal watches kept at once; arming one past this lets the oldest go. Property watches
|
|
31
25
|
## are counted apart, to the same number.
|
|
32
26
|
const MOST_WATCHES: int = 16
|
|
33
|
-
|
|
34
27
|
## The most changes a property watch keeps. Past it, each new one takes the last place, so what is
|
|
35
28
|
## kept still ends on the latest value.
|
|
36
29
|
const MOST_CHANGES: int = 1000
|
|
37
|
-
|
|
38
30
|
## The commands that can change the game, by the tool call that sends each, which a property watch
|
|
39
31
|
## notes as they arrive.
|
|
40
32
|
const CALLS_NAMED: Dictionary[String, String] = {
|
|
@@ -51,10 +43,9 @@ const CALLS_NAMED: Dictionary[String, String] = {
|
|
|
51
43
|
"inject_joypad_button": "runtime_input joypad_button",
|
|
52
44
|
"inject_joypad_motion": "runtime_input joypad_motion",
|
|
53
45
|
}
|
|
54
|
-
|
|
55
46
|
## What a call noted by a property watch carries of its parameters, when it was given them.
|
|
56
47
|
const CALL_FIELDS: Array[String] = [
|
|
57
|
-
"path", "method", "property", "action", "keycode", "button", "pressed", "says"
|
|
48
|
+
"path", "method", "property", "action", "keycode", "button", "pressed", "says",
|
|
58
49
|
]
|
|
59
50
|
|
|
60
51
|
var _host: Node
|
|
@@ -76,7 +67,8 @@ func _init(host: Node, values: GdhValues) -> void:
|
|
|
76
67
|
## frames of the game having passed, and everything measured off it is out by that much.
|
|
77
68
|
static func _out_of_range(named: String, asked: int, least: int, most: int) -> Dictionary:
|
|
78
69
|
return {
|
|
79
|
-
"type": "error",
|
|
70
|
+
"type": "error",
|
|
71
|
+
"message": "%s is %d, and %s takes %d to %d." % [named, asked, named, least, most],
|
|
80
72
|
}
|
|
81
73
|
|
|
82
74
|
|
|
@@ -84,7 +76,10 @@ static func _out_of_range(named: String, asked: int, least: int, most: int) -> D
|
|
|
84
76
|
## or every one when none is near: a name to send instead.
|
|
85
77
|
static func _no_signal(node: Node, node_path: String, signal_name: String) -> Dictionary:
|
|
86
78
|
var said: String = "%s has no signal %s" % [node_path, signal_name]
|
|
87
|
-
return {
|
|
79
|
+
return {
|
|
80
|
+
"type": "error",
|
|
81
|
+
"message": GdhRead.offered(said, signal_name, GdhRead.signals_of(node)) + ".",
|
|
82
|
+
}
|
|
88
83
|
|
|
89
84
|
|
|
90
85
|
func wait_frames(params: Dictionary) -> Dictionary:
|
|
@@ -94,7 +89,7 @@ func wait_frames(params: Dictionary) -> Dictionary:
|
|
|
94
89
|
var started: int = Time.get_ticks_msec()
|
|
95
90
|
for _frame: int in frames:
|
|
96
91
|
await _host.get_tree().process_frame
|
|
97
|
-
return {"type": "waited", "frames": frames, "elapsed_ms": Time.get_ticks_msec() - started}
|
|
92
|
+
return { "type": "waited", "frames": frames, "elapsed_ms": Time.get_ticks_msec() - started }
|
|
98
93
|
|
|
99
94
|
|
|
100
95
|
## Waits for a signal to fire, and answers with what it carried, or that the time ran out. Given a
|
|
@@ -108,7 +103,7 @@ func wait_signal(params: Dictionary) -> Dictionary:
|
|
|
108
103
|
if params.has("watch"):
|
|
109
104
|
return await _collect(GdhRead.as_int(params["watch"], 0), timeout_ms)
|
|
110
105
|
if node_path.is_empty() or signal_name.is_empty():
|
|
111
|
-
return {"type": "error", "message": "Node path and signal name required"}
|
|
106
|
+
return { "type": "error", "message": "Node path and signal name required" }
|
|
112
107
|
|
|
113
108
|
var standing: Dictionary = GdhValues.node_at(_host.get_tree().root, node_path)
|
|
114
109
|
if standing.has("message"):
|
|
@@ -121,7 +116,7 @@ func wait_signal(params: Dictionary) -> Dictionary:
|
|
|
121
116
|
var callable: Callable = catcher._on_fired
|
|
122
117
|
var listening: Error = node.connect(signal_name, callable, CONNECT_ONE_SHOT)
|
|
123
118
|
if listening != OK:
|
|
124
|
-
return {"type": "error", "message": "Could not listen to signal: " + signal_name}
|
|
119
|
+
return { "type": "error", "message": "Could not listen to signal: " + signal_name }
|
|
125
120
|
var started: int = Time.get_ticks_msec()
|
|
126
121
|
while not catcher.fired and Time.get_ticks_msec() - started < timeout_ms:
|
|
127
122
|
await _host.get_tree().process_frame
|
|
@@ -138,10 +133,10 @@ func wait_signal(params: Dictionary) -> Dictionary:
|
|
|
138
133
|
}
|
|
139
134
|
if not catcher.fired:
|
|
140
135
|
answer["note"] = (
|
|
141
|
-
"A wait hears the signal only from when it begins, so one that fired before it is not"
|
|
142
|
-
+ "
|
|
143
|
-
+ " only after the wait answered, as a client can run its calls in turn: arm
|
|
144
|
-
+ " watch first, make the call, then wait with signal and that watch"
|
|
136
|
+
"A wait hears the signal only from when it begins, so one that fired before it is not "
|
|
137
|
+
+ "counted. If a call meant to set the signal off was sent beside this wait, it may "
|
|
138
|
+
+ "have run only after the wait answered, as a client can run its calls in turn: arm "
|
|
139
|
+
+ "runtime_wait watch first, make the call, then wait with signal and that watch"
|
|
145
140
|
)
|
|
146
141
|
return answer
|
|
147
142
|
|
|
@@ -149,8 +144,9 @@ func wait_signal(params: Dictionary) -> Dictionary:
|
|
|
149
144
|
## Starts recording a signal and answers at once with the watch to collect it by.
|
|
150
145
|
##
|
|
151
146
|
## A wait sent beside the call meant to set its signal off does not see it when the client runs the
|
|
152
|
-
## two in turn: Claude Code ran a key press only after the signal wait beside it had answered, so
|
|
153
|
-
## wait ran out and the press landed after it. A watch is armed before the call and collected
|
|
147
|
+
## two in turn: Claude Code ran a key press only after the signal wait beside it had answered, so
|
|
148
|
+
## the wait ran out and the press landed after it. A watch is armed before the call and collected
|
|
149
|
+
## after.
|
|
154
150
|
func watch_signal(params: Dictionary) -> Dictionary:
|
|
155
151
|
var node_path: String = str(params.get("path", ""))
|
|
156
152
|
var signal_name: String = str(params.get("signal", ""))
|
|
@@ -160,14 +156,17 @@ func watch_signal(params: Dictionary) -> Dictionary:
|
|
|
160
156
|
"type": "error",
|
|
161
157
|
"message":
|
|
162
158
|
(
|
|
163
|
-
|
|
159
|
+
(
|
|
160
|
+
"A watch records a signal or a property, not both: this one has signal %s and "
|
|
161
|
+
+ "property %s"
|
|
162
|
+
)
|
|
164
163
|
% [signal_name, property]
|
|
165
|
-
)
|
|
164
|
+
),
|
|
166
165
|
}
|
|
167
166
|
if not property.is_empty():
|
|
168
167
|
return _watch_property(node_path, property)
|
|
169
168
|
if node_path.is_empty() or signal_name.is_empty():
|
|
170
|
-
return {"type": "error", "message": "Node path and signal name required"}
|
|
169
|
+
return { "type": "error", "message": "Node path and signal name required" }
|
|
171
170
|
var standing: Dictionary = GdhValues.node_at(_host.get_tree().root, node_path)
|
|
172
171
|
if standing.has("message"):
|
|
173
172
|
return standing
|
|
@@ -177,14 +176,14 @@ func watch_signal(params: Dictionary) -> Dictionary:
|
|
|
177
176
|
var watch: GdhWatch = GdhWatch.new(node, node_path, signal_name)
|
|
178
177
|
var listening: Error = node.connect(signal_name, watch.catcher._on_fired)
|
|
179
178
|
if listening != OK:
|
|
180
|
-
return {"type": "error", "message": "Could not listen to signal: " + signal_name}
|
|
179
|
+
return { "type": "error", "message": "Could not listen to signal: " + signal_name }
|
|
181
180
|
while _watches.size() >= MOST_WATCHES:
|
|
182
181
|
var oldest: int = _watches.keys().min()
|
|
183
182
|
_let_go(oldest)
|
|
184
183
|
var id: int = _next_watch
|
|
185
184
|
_next_watch += 1
|
|
186
185
|
_watches[id] = watch
|
|
187
|
-
return {"type": "watching", "watch": id, "path": node_path, "signal": signal_name}
|
|
186
|
+
return { "type": "watching", "watch": id, "path": node_path, "signal": signal_name }
|
|
188
187
|
|
|
189
188
|
|
|
190
189
|
## Answers at once with the first emission recorded since watch [param id] was armed, otherwise
|
|
@@ -197,7 +196,7 @@ func _collect(id: int, timeout_ms: int) -> Dictionary:
|
|
|
197
196
|
(
|
|
198
197
|
"Watch %d records %s:%s, a property, and is collected with runtime_wait recorded"
|
|
199
198
|
% [id, _traces[id].node_path, _traces[id].property]
|
|
200
|
-
)
|
|
199
|
+
),
|
|
201
200
|
}
|
|
202
201
|
if not _watches.has(id):
|
|
203
202
|
return {
|
|
@@ -205,8 +204,8 @@ func _collect(id: int, timeout_ms: int) -> Dictionary:
|
|
|
205
204
|
"message":
|
|
206
205
|
(
|
|
207
206
|
(
|
|
208
|
-
"No watch %d is armed in this game: it was collected already, %d armed after
|
|
209
|
-
+ " go, or the game started again since"
|
|
207
|
+
"No watch %d is armed in this game: it was collected already, %d armed after "
|
|
208
|
+
+ "it let it go, or the game started again since"
|
|
210
209
|
)
|
|
211
210
|
% [id, MOST_WATCHES]
|
|
212
211
|
),
|
|
@@ -254,7 +253,7 @@ func _let_go(id: int) -> void:
|
|
|
254
253
|
## only by slowing the game's clock. Armed before the click, the watch has every frame of it.
|
|
255
254
|
func _watch_property(node_path: String, property: String) -> Dictionary:
|
|
256
255
|
if node_path.is_empty():
|
|
257
|
-
return {"type": "error", "message": "Node path required"}
|
|
256
|
+
return { "type": "error", "message": "Node path required" }
|
|
258
257
|
var standing: Dictionary = GdhValues.node_at(_host.get_tree().root, node_path)
|
|
259
258
|
if standing.has("message"):
|
|
260
259
|
return standing
|
|
@@ -270,7 +269,8 @@ func _watch_property(node_path: String, property: String) -> Dictionary:
|
|
|
270
269
|
_next_watch += 1
|
|
271
270
|
_traces[id] = trace
|
|
272
271
|
# Left to run across frames while the answer goes now. Deferred calls run once the frame's
|
|
273
|
-
# _process is done and before its tweens step, so the recording starts in the frame it was armed
|
|
272
|
+
# _process is done and before its tweens step, so the recording starts in the frame it was armed
|
|
273
|
+
# in.
|
|
274
274
|
_record.call_deferred(trace)
|
|
275
275
|
return {
|
|
276
276
|
"type": "watching",
|
|
@@ -303,7 +303,9 @@ func _record(trace: GdhTrace) -> void:
|
|
|
303
303
|
var held: Object = trace.node.get_ref()
|
|
304
304
|
var node: Node = held as Node
|
|
305
305
|
if node == null:
|
|
306
|
-
trace.ended =
|
|
306
|
+
trace.ended = (
|
|
307
|
+
"%s was freed %d frames after the watch was armed" % [trace.node_path, frame]
|
|
308
|
+
)
|
|
307
309
|
return
|
|
308
310
|
var watched: Dictionary = _watched(node, trace.node_path, trace.property)
|
|
309
311
|
if watched.has("message"):
|
|
@@ -314,7 +316,8 @@ func _record(trace: GdhTrace) -> void:
|
|
|
314
316
|
trace.elapsed_ms = elapsed
|
|
315
317
|
if elapsed >= CEILING_MSEC:
|
|
316
318
|
trace.ended = (
|
|
317
|
-
"recording stopped %d ms after the watch was armed, the longest a wait lasts"
|
|
319
|
+
"recording stopped %d ms after the watch was armed, the longest a wait lasts"
|
|
320
|
+
% elapsed
|
|
318
321
|
)
|
|
319
322
|
return
|
|
320
323
|
|
|
@@ -333,7 +336,7 @@ func saw_call(command: String, params: Dictionary) -> void:
|
|
|
333
336
|
if trace.calls.size() >= MOST_CHANGES:
|
|
334
337
|
trace.calls_dropped += 1
|
|
335
338
|
continue
|
|
336
|
-
var noted: Dictionary = {"frame": trace.frames, "call": named}
|
|
339
|
+
var noted: Dictionary = { "frame": trace.frames, "call": named }
|
|
337
340
|
for field: String in CALL_FIELDS:
|
|
338
341
|
if params.has(field):
|
|
339
342
|
noted[field] = params[field]
|
|
@@ -351,7 +354,7 @@ func recorded(params: Dictionary) -> Dictionary:
|
|
|
351
354
|
(
|
|
352
355
|
"Watch %d records the signal %s of %s, and is collected with runtime_wait signal"
|
|
353
356
|
% [id, _watches[id].signal_name, _watches[id].node_path]
|
|
354
|
-
)
|
|
357
|
+
),
|
|
355
358
|
}
|
|
356
359
|
if not _traces.has(id):
|
|
357
360
|
return {
|
|
@@ -359,8 +362,8 @@ func recorded(params: Dictionary) -> Dictionary:
|
|
|
359
362
|
"message":
|
|
360
363
|
(
|
|
361
364
|
(
|
|
362
|
-
"No property watch %d is armed in this game: it was collected already, %d
|
|
363
|
-
+ " it let it go, or the game started again since"
|
|
365
|
+
"No property watch %d is armed in this game: it was collected already, %d "
|
|
366
|
+
+ "armed after it let it go, or the game started again since"
|
|
364
367
|
)
|
|
365
368
|
% [id, MOST_WATCHES]
|
|
366
369
|
),
|
|
@@ -383,8 +386,8 @@ func recorded(params: Dictionary) -> Dictionary:
|
|
|
383
386
|
if trace.dropped > 0:
|
|
384
387
|
answer["dropped"] = (
|
|
385
388
|
(
|
|
386
|
-
"%d changes are left out: past %d, each new one took the last place, so the list
|
|
387
|
-
+ " the latest"
|
|
389
|
+
"%d changes are left out: past %d, each new one took the last place, so the list "
|
|
390
|
+
+ "ends on the latest"
|
|
388
391
|
)
|
|
389
392
|
% [trace.dropped, MOST_CHANGES]
|
|
390
393
|
)
|
|
@@ -403,7 +406,7 @@ func wait_until(params: Dictionary) -> Dictionary:
|
|
|
403
406
|
if timeout_ms < 1 or timeout_ms > CEILING_MSEC:
|
|
404
407
|
return _out_of_range("timeout_ms", timeout_ms, 1, CEILING_MSEC)
|
|
405
408
|
if node_path.is_empty():
|
|
406
|
-
return {"type": "error", "message": "Node path required"}
|
|
409
|
+
return { "type": "error", "message": "Node path required" }
|
|
407
410
|
var standing: Dictionary = GdhValues.node_at(_host.get_tree().root, node_path)
|
|
408
411
|
if standing.has("message"):
|
|
409
412
|
return standing
|
|
@@ -411,7 +414,10 @@ func wait_until(params: Dictionary) -> Dictionary:
|
|
|
411
414
|
# path, a property compares one value on one node. Taking says and dropping the other left a
|
|
412
415
|
# caller watching a screen while believing they were watching a property, and the answer says
|
|
413
416
|
# which of them it is about only if you already know that says wins.
|
|
414
|
-
if
|
|
417
|
+
if (
|
|
418
|
+
not says.is_empty()
|
|
419
|
+
and (not property.is_empty() or params.has("value") or params.has("compare"))
|
|
420
|
+
):
|
|
415
421
|
var other: String = (
|
|
416
422
|
property if not property.is_empty() else ("value" if params.has("value") else "compare")
|
|
417
423
|
)
|
|
@@ -420,18 +426,18 @@ func wait_until(params: Dictionary) -> Dictionary:
|
|
|
420
426
|
"message":
|
|
421
427
|
(
|
|
422
428
|
(
|
|
423
|
-
'A wait takes says or a property, not both: this one has says "%s" and %s as
|
|
424
|
-
+ " Ask for one of them."
|
|
429
|
+
'A wait takes says or a property, not both: this one has says "%s" and %s as '
|
|
430
|
+
+ "well. Ask for one of them."
|
|
425
431
|
)
|
|
426
432
|
% [says, other]
|
|
427
|
-
)
|
|
433
|
+
),
|
|
428
434
|
}
|
|
429
435
|
if not says.is_empty():
|
|
430
436
|
var include_hidden: bool = GdhRead.as_bool(params.get("include_hidden", false))
|
|
431
437
|
return await _wait_until_said(node_path, says, timeout_ms, include_hidden)
|
|
432
438
|
var unasked: String = _property_wait_unasked(property, params)
|
|
433
439
|
if not unasked.is_empty():
|
|
434
|
-
return {"type": "error", "message": unasked}
|
|
440
|
+
return { "type": "error", "message": unasked }
|
|
435
441
|
var compare: String = str(params.get("compare", "=="))
|
|
436
442
|
|
|
437
443
|
var node: Node = standing["node"]
|
|
@@ -451,13 +457,15 @@ func wait_until(params: Dictionary) -> Dictionary:
|
|
|
451
457
|
var sent: Variant = by_name[1] if not by_name.is_empty() and by_name[0] else params["value"]
|
|
452
458
|
var wanted: Variant = _values.fitted(sent, typeof(current))
|
|
453
459
|
# Read against what the property is declared as, not only against what it holds this frame: an
|
|
454
|
-
# object property holding null holds nothing typed, so any value was taken as it came and
|
|
455
|
-
# with null until the time ran out. #772 waited a minute for "null" that way and
|
|
456
|
-
# false with value null beside it.
|
|
460
|
+
# object property holding null holds nothing typed, so any value was taken as it came and
|
|
461
|
+
# compared with null until the time ran out. #772 waited a minute for "null" that way and
|
|
462
|
+
# answered met false with value null beside it.
|
|
457
463
|
var declared: int = watched["declared"]
|
|
458
464
|
# An untyped slot holding null or an object is read the same way, as set reads it: otherwise the
|
|
459
465
|
# two ops would disagree about one property, and the wait would run out on the text null.
|
|
460
|
-
var untyped_object: bool =
|
|
466
|
+
var untyped_object: bool = (
|
|
467
|
+
declared == TYPE_NIL and (current == null or typeof(current) == TYPE_OBJECT)
|
|
468
|
+
)
|
|
461
469
|
var holds_objects: bool = declared == TYPE_OBJECT or untyped_object
|
|
462
470
|
if holds_objects and GdhChanges.names_no_object(wanted):
|
|
463
471
|
wanted = null
|
|
@@ -480,15 +488,15 @@ func wait_until(params: Dictionary) -> Dictionary:
|
|
|
480
488
|
% [node_path, property, type_string(typeof(wanted))]
|
|
481
489
|
)
|
|
482
490
|
if not refused.is_empty():
|
|
483
|
-
return {"type": "error", "message": refused}
|
|
491
|
+
return { "type": "error", "message": refused }
|
|
484
492
|
|
|
485
493
|
var started: int = Time.get_ticks_msec()
|
|
486
494
|
var frames: int = 0
|
|
487
495
|
# The type is checked every time round, not only at the top: a property that holds an object a
|
|
488
|
-
# frame later is the same error arriving late.
|
|
489
|
-
#
|
|
490
|
-
#
|
|
491
|
-
#
|
|
496
|
+
# frame later is the same error arriving late. At the precision the property keeps, as set
|
|
497
|
+
# compares: a float arrives as 64 bits and most engine properties keep 32, so h_offset waited on
|
|
498
|
+
# as -1.1 read -1.10000002384186 every frame and the wait ran out on a value the game had
|
|
499
|
+
# reached.
|
|
492
500
|
var differs: bool = compare == "!="
|
|
493
501
|
while (
|
|
494
502
|
GdhValues.comparable(current, wanted)
|
|
@@ -498,7 +506,7 @@ func wait_until(params: Dictionary) -> Dictionary:
|
|
|
498
506
|
await _host.get_tree().process_frame
|
|
499
507
|
frames += 1
|
|
500
508
|
if not is_instance_valid(node):
|
|
501
|
-
return {"type": "error", "message": "%s was freed while waiting" % node_path}
|
|
509
|
+
return { "type": "error", "message": "%s was freed while waiting" % node_path }
|
|
502
510
|
watched = _watched(node, node_path, property)
|
|
503
511
|
if watched.has("message"):
|
|
504
512
|
return watched
|
|
@@ -509,13 +517,18 @@ func wait_until(params: Dictionary) -> Dictionary:
|
|
|
509
517
|
"path": node_path,
|
|
510
518
|
"property": property,
|
|
511
519
|
"compare": compare,
|
|
512
|
-
"met":
|
|
520
|
+
"met":
|
|
521
|
+
(
|
|
522
|
+
GdhValues.comparable(current, wanted)
|
|
523
|
+
and GdhChanges.held_already(wanted, current) != differs
|
|
524
|
+
),
|
|
513
525
|
"value": _values.serialize(current),
|
|
514
526
|
"elapsed_ms": Time.get_ticks_msec() - started,
|
|
515
527
|
"frames": frames,
|
|
516
528
|
}
|
|
517
529
|
# Said rather than refused, since a game may store a number no value has, as set says it stores
|
|
518
|
-
# one: a wait for 7 on an enum of three ran its whole timeout and answered only that it was not
|
|
530
|
+
# one: a wait for 7 on an enum of three ran its whole timeout and answered only that it was not
|
|
531
|
+
# met.
|
|
519
532
|
if watched["holder"] is Object:
|
|
520
533
|
var owner: Object = watched["holder"]
|
|
521
534
|
var shown: String = JSON.stringify(_values.serialize(wanted), "", false)
|
|
@@ -542,8 +555,11 @@ static func _property_wait_unasked(property: String, params: Dictionary) -> Stri
|
|
|
542
555
|
## is past the value, or level with it at the precision the property keeps, as `==` compares.
|
|
543
556
|
##
|
|
544
557
|
## The value is read as the number it is rather than fitted to the property's type, since fitting
|
|
545
|
-
## truncates: 31.5 on a whole-number property became 31, and a wait for "at least 31.5" was met at
|
|
546
|
-
|
|
558
|
+
## truncates: 31.5 on a whole-number property became 31, and a wait for "at least 31.5" was met at
|
|
559
|
+
## 31.
|
|
560
|
+
func _wait_in_order(
|
|
561
|
+
node: Node, params: Dictionary, current: Variant, timeout_ms: int
|
|
562
|
+
) -> Dictionary:
|
|
547
563
|
var node_path: String = str(params.get("path", ""))
|
|
548
564
|
var property: String = str(params.get("property", ""))
|
|
549
565
|
var compare: String = str(params.get("compare", ""))
|
|
@@ -552,7 +568,7 @@ func _wait_in_order(node: Node, params: Dictionary, current: Variant, timeout_ms
|
|
|
552
568
|
# came with it, since no number would make the wait answerable.
|
|
553
569
|
var unordered: String = _no_order(current, node_path, property, compare)
|
|
554
570
|
if not unordered.is_empty():
|
|
555
|
-
return {"type": "error", "message": unordered}
|
|
571
|
+
return { "type": "error", "message": unordered }
|
|
556
572
|
var number: Variant = given
|
|
557
573
|
if given is String:
|
|
558
574
|
var text: String = given
|
|
@@ -564,7 +580,7 @@ func _wait_in_order(node: Node, params: Dictionary, current: Variant, timeout_ms
|
|
|
564
580
|
(
|
|
565
581
|
"compare %s puts a number in order, and the value to wait for is %s: give a number."
|
|
566
582
|
% [compare, str(given)]
|
|
567
|
-
)
|
|
583
|
+
),
|
|
568
584
|
}
|
|
569
585
|
var limit: float = GdhRead.as_float(number)
|
|
570
586
|
|
|
@@ -574,14 +590,14 @@ func _wait_in_order(node: Node, params: Dictionary, current: Variant, timeout_ms
|
|
|
574
590
|
await _host.get_tree().process_frame
|
|
575
591
|
frames += 1
|
|
576
592
|
if not is_instance_valid(node):
|
|
577
|
-
return {"type": "error", "message": "%s was freed while waiting" % node_path}
|
|
593
|
+
return { "type": "error", "message": "%s was freed while waiting" % node_path }
|
|
578
594
|
var watched: Dictionary = _watched(node, node_path, property)
|
|
579
595
|
if watched.has("message"):
|
|
580
596
|
return watched
|
|
581
597
|
current = watched["value"]
|
|
582
598
|
unordered = _no_order(current, node_path, property, compare)
|
|
583
599
|
if not unordered.is_empty():
|
|
584
|
-
return {"type": "error", "message": unordered}
|
|
600
|
+
return { "type": "error", "message": unordered }
|
|
585
601
|
|
|
586
602
|
return {
|
|
587
603
|
"type": "condition",
|
|
@@ -596,7 +612,9 @@ func _wait_in_order(node: Node, params: Dictionary, current: Variant, timeout_ms
|
|
|
596
612
|
|
|
597
613
|
|
|
598
614
|
## Why [param current] cannot be put in order, or "" when it is a number.
|
|
599
|
-
static func _no_order(
|
|
615
|
+
static func _no_order(
|
|
616
|
+
current: Variant, node_path: String, property: String, compare: String
|
|
617
|
+
) -> String:
|
|
600
618
|
if typeof(current) == TYPE_INT or typeof(current) == TYPE_FLOAT:
|
|
601
619
|
return ""
|
|
602
620
|
return (
|
|
@@ -610,7 +628,8 @@ static func _no_order(current: Variant, node_path: String, property: String, com
|
|
|
610
628
|
static func _in_order(compare: String, limit: float, current: Variant) -> bool:
|
|
611
629
|
var reading: float = GdhRead.as_float(current)
|
|
612
630
|
var level: bool = (
|
|
613
|
-
reading == limit
|
|
631
|
+
reading == limit
|
|
632
|
+
or (typeof(current) == TYPE_FLOAT and GdhChanges.held_already(limit, current))
|
|
614
633
|
)
|
|
615
634
|
match compare:
|
|
616
635
|
">=":
|
|
@@ -633,8 +652,8 @@ static func _cut_to_fit(given: Variant, type: int, node_path: String, property:
|
|
|
633
652
|
return ""
|
|
634
653
|
return (
|
|
635
654
|
(
|
|
636
|
-
"%s.%s holds %s and the value to wait for is %s, which %s cannot hold without changing
|
|
637
|
-
+ " waiting on it would answer about a state nobody asked for."
|
|
655
|
+
"%s.%s holds %s and the value to wait for is %s, which %s cannot hold without changing "
|
|
656
|
+
+ "it: waiting on it would answer about a state nobody asked for."
|
|
638
657
|
)
|
|
639
658
|
% [node_path, property, type_string(type), str(given), GdhValues.cut_by(type)]
|
|
640
659
|
)
|
|
@@ -651,9 +670,11 @@ static func _cut_to_fit(given: Variant, type: int, node_path: String, property:
|
|
|
651
670
|
## where a number goes became 0.0, 0.0 already equalled the property, and the wait answered met the
|
|
652
671
|
## instant it started, about a state nobody asked about, with the real value beside it. A refusal
|
|
653
672
|
## is read and a met is acted on.
|
|
654
|
-
static func _not_comparable(
|
|
673
|
+
static func _not_comparable(
|
|
674
|
+
current: Variant, wanted: Variant, node_path: String, property: String
|
|
675
|
+
) -> String:
|
|
655
676
|
var types: Array[String] = [
|
|
656
|
-
node_path, property, type_string(typeof(current)), type_string(typeof(wanted))
|
|
677
|
+
node_path, property, type_string(typeof(current)), type_string(typeof(wanted)),
|
|
657
678
|
]
|
|
658
679
|
# A value that cannot become what the property holds is said as that, which names the remedy,
|
|
659
680
|
# before the comparison it would also fail; an object on either side is said as the comparison.
|
|
@@ -696,7 +717,9 @@ static func _not_comparable(current: Variant, wanted: Variant, node_path: String
|
|
|
696
717
|
## when the property is no enum or the value no word.
|
|
697
718
|
static func _enum_by_name(watched: Dictionary, sent: Variant, node_path: String) -> Array:
|
|
698
719
|
var declaration: Dictionary = watched["declaration"]
|
|
699
|
-
var enum_hint: bool =
|
|
720
|
+
var enum_hint: bool = (
|
|
721
|
+
GdhRead.as_int(declaration.get("hint", PROPERTY_HINT_NONE)) == PROPERTY_HINT_ENUM
|
|
722
|
+
)
|
|
700
723
|
if watched["declared"] != TYPE_INT or not enum_hint or not sent is String:
|
|
701
724
|
return []
|
|
702
725
|
if not watched["holder"] is Object:
|
|
@@ -721,7 +744,7 @@ static func _watched(node: Node, node_path: String, property: String) -> Diction
|
|
|
721
744
|
var named: String = reached["name"]
|
|
722
745
|
var missing: String = GdhPaths.nothing_under(holder, named, str(reached["called"]))
|
|
723
746
|
if not missing.is_empty():
|
|
724
|
-
return {"type": "error", "message": missing}
|
|
747
|
+
return { "type": "error", "message": missing }
|
|
725
748
|
var slot: Dictionary = _declared_slot(holder, named)
|
|
726
749
|
return {
|
|
727
750
|
"value": GdhPaths.read_under(holder, named),
|
|
@@ -731,14 +754,15 @@ static func _watched(node: Node, node_path: String, property: String) -> Diction
|
|
|
731
754
|
}
|
|
732
755
|
|
|
733
756
|
|
|
734
|
-
## What [param named] is declared with on [param holder], as [method GdhValues.slot_declared]
|
|
735
|
-
## read the way a set reads it, so the two refuse the same values: a property's
|
|
736
|
-
## typed list's or map's element type. Read off properties alone, an element of an
|
|
737
|
-
## null declares nothing, and a wait for 5 on it runs out the timeout while a
|
|
738
|
-
## at once. TYPE_NIL for anything declaring none, and for a call, whose
|
|
757
|
+
## What [param named] is declared with on [param holder], as [method GdhValues.slot_declared]
|
|
758
|
+
## answers, read the way a set reads it, so the two refuse the same values: a property's
|
|
759
|
+
## declaration, or a typed list's or map's element type. Read off properties alone, an element of an
|
|
760
|
+
## Array[Node] holding null declares nothing, and a wait for 5 on it runs out the timeout while a
|
|
761
|
+
## set of 5 on it is refused at once. TYPE_NIL for anything declaring none, and for a call, whose
|
|
762
|
+
## answer nothing declares.
|
|
739
763
|
static func _declared_slot(holder: Variant, named: String) -> Dictionary:
|
|
740
764
|
if not GdhPaths.method_of(named).is_empty():
|
|
741
|
-
return {"type": TYPE_NIL, "class": "", "script": null, "declared": {}}
|
|
765
|
+
return { "type": TYPE_NIL, "class": "", "script": null, "declared": {} }
|
|
742
766
|
return GdhValues.slot_declared(holder, named)
|
|
743
767
|
|
|
744
768
|
|
|
@@ -756,15 +780,17 @@ static func _declared_slot(holder: Variant, named: String) -> Dictionary:
|
|
|
756
780
|
## screen fell back to counting frames. [param include_hidden] asks the other question, for a
|
|
757
781
|
## caller waiting on words a hidden node holds.
|
|
758
782
|
##
|
|
759
|
-
## Paced by what a look costs, so the watching leaves the game its speed: every frame while a look
|
|
760
|
-
## cheap, and on a screen large enough for one to cost a real share of a frame, often enough that
|
|
783
|
+
## Paced by what a look costs, so the watching leaves the game its speed: every frame while a look
|
|
784
|
+
## is cheap, and on a screen large enough for one to cost a real share of a frame, often enough that
|
|
761
785
|
## looking takes about a tenth of the time. Every frame on a hall of 3,500 nodes held the game at a
|
|
762
786
|
## sixth of its frame rate, and its clock, which advances by the frame's delta, lost three quarters
|
|
763
787
|
## of its time, so a wait for a date timed out on a clock that turned a day every thirty seconds.
|
|
764
788
|
## The answer says how many frames the wait spanned, how many looks it took and how long they took
|
|
765
789
|
## between them, which is how a caller sees what it cost and whether the game lost its speed to the
|
|
766
790
|
## looks or to something else.
|
|
767
|
-
func _wait_until_said(
|
|
791
|
+
func _wait_until_said(
|
|
792
|
+
node_path: String, said: String, timeout_ms: int, include_hidden: bool
|
|
793
|
+
) -> Dictionary:
|
|
768
794
|
var started: int = Time.get_ticks_msec()
|
|
769
795
|
var frames: int = 0
|
|
770
796
|
var looks: int = 0
|
|
@@ -814,8 +840,9 @@ func _wait_until_said(node_path: String, said: String, timeout_ms: int, include_
|
|
|
814
840
|
if not found and not include_hidden and _anything_says(node_path, said, true):
|
|
815
841
|
answer["unseen_note"] = (
|
|
816
842
|
(
|
|
817
|
-
"Something under %s says it, but is hidden or drawn outside the viewport or a"
|
|
818
|
-
+ "
|
|
843
|
+
"Something under %s says it, but is hidden or drawn outside the viewport or a "
|
|
844
|
+
+ "ScrollContainer, where the player cannot read it; includeHidden true counts it "
|
|
845
|
+
+ "there too"
|
|
819
846
|
)
|
|
820
847
|
% node_path
|
|
821
848
|
)
|
|
@@ -900,8 +927,8 @@ class GdhTrace:
|
|
|
900
927
|
elif value is Dictionary:
|
|
901
928
|
var map: Dictionary = value
|
|
902
929
|
_last = map.duplicate(true)
|
|
903
|
-
# Game time unrounded: a frame can be a tenth of a millisecond long, and a rate worked out
|
|
904
|
-
# one from times cut to a ten-thousandth was off by its own size.
|
|
930
|
+
# Game time unrounded: a frame can be a tenth of a millisecond long, and a rate worked out
|
|
931
|
+
# over one from times cut to a ten-thousandth was off by its own size.
|
|
905
932
|
var change: Dictionary = {
|
|
906
933
|
"frame": frame,
|
|
907
934
|
"elapsed_ms": elapsed,
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
extends RefCounted
|
|
2
|
-
|
|
3
2
|
## What a node says to a player, read the way it is drawn: the parsed words of rich text, as much of
|
|
4
3
|
## a line as is typed out so far, the translation rather than its key, a mask over a secret, and the
|
|
5
4
|
## items of the controls that draw a list.
|
|
@@ -21,9 +20,9 @@ extends RefCounted
|
|
|
21
20
|
## for each of them on every look a wait takes.
|
|
22
21
|
static func said_by(node: Node) -> String:
|
|
23
22
|
var own: String = drawn_by(node)
|
|
24
|
-
# A control showing a picture rather than words is named for a player who cannot see it, and
|
|
25
|
-
# name is the one the words a caller asks by are meant for: the rail's "Out" tab is a
|
|
26
|
-
# an icon on it, and a click on "Out" found only the words around it.
|
|
23
|
+
# A control showing a picture rather than words is named for a player who cannot see it, and
|
|
24
|
+
# that name is the one the words a caller asks by are meant for: the rail's "Out" tab is a
|
|
25
|
+
# square with an icon on it, and a click on "Out" found only the words around it.
|
|
27
26
|
if own.is_empty() and node is Control and not node is Label and not draws_a_list(node):
|
|
28
27
|
var control: Control = node
|
|
29
28
|
return control.atr(control.accessibility_name).strip_edges()
|
|
@@ -141,7 +140,9 @@ static func masked(field: LineEdit) -> String:
|
|
|
141
140
|
## Whether [param node] draws a list of items rather than holding words in a `text`; see
|
|
142
141
|
## [method items_of].
|
|
143
142
|
static func draws_a_list(node: Node) -> bool:
|
|
144
|
-
return
|
|
143
|
+
return (
|
|
144
|
+
node is TabBar or node is MenuBar or node is ItemList or node is PopupMenu or node is Tree
|
|
145
|
+
)
|
|
145
146
|
|
|
146
147
|
|
|
147
148
|
## The words of the enabled buttons under [param from], quoted, each once and in the order they are
|
|
@@ -154,7 +155,9 @@ static func pressable_words(from: Node, open: Callable, most: int) -> String:
|
|
|
154
155
|
while not pending.is_empty():
|
|
155
156
|
var node: Node = pending.pop_front()
|
|
156
157
|
var button: BaseButton = node as BaseButton
|
|
157
|
-
var words: String =
|
|
158
|
+
var words: String = (
|
|
159
|
+
"" if button == null or button.disabled else said_by(button).strip_edges()
|
|
160
|
+
)
|
|
158
161
|
var quoted: String = '"%s"' % words
|
|
159
162
|
if not words.is_empty() and not offered.has(quoted) and open.call(button):
|
|
160
163
|
if offered.size() == most:
|
|
@@ -175,7 +178,7 @@ static func _keep(line: String, into: Array[String]) -> void:
|
|
|
175
178
|
static func _keep_item(line: String, item: Variant, into: Array[Dictionary]) -> void:
|
|
176
179
|
var trimmed: String = line.strip_edges()
|
|
177
180
|
if not trimmed.is_empty():
|
|
178
|
-
into.append({"says": trimmed, "item": item})
|
|
181
|
+
into.append({ "says": trimmed, "item": item })
|
|
179
182
|
|
|
180
183
|
|
|
181
184
|
## [param words] as [param node] draws them, which for a game with translations is the translation
|