gdharness 1.3.42 → 1.3.44
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 +383 -243
- package/build/godot/addons/auto_reload/auto_reload.gd +18 -19
- package/build/godot/addons/gdharness_editor/animation_reads.gd +55 -33
- 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 +83 -42
- package/build/godot/addons/gdharness_editor/file_walk.gd +12 -5
- package/build/godot/addons/gdharness_editor/headless_check.gd +68 -44
- 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 +51 -17
- package/build/godot/addons/gdharness_editor/same_values.gd +4 -4
- package/build/godot/addons/gdharness_editor/scene_file.gd +127 -80
- 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 +37 -27
- package/build/godot/addons/gdharness_editor/tools/animation_tools.gd +79 -49
- package/build/godot/addons/gdharness_editor/tools/animation_tracks.gd +70 -40
- package/build/godot/addons/gdharness_editor/tools/class_tools.gd +65 -42
- 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 +166 -107
- package/build/godot/addons/gdharness_editor/tools/scene_groups.gd +16 -9
- package/build/godot/addons/gdharness_editor/tools/scene_kin.gd +68 -41
- 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 +12 -5
- package/build/godot/addons/gdharness_editor/tools/scene_tools.gd +113 -67
- 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 +51 -17
- package/build/godot/addons/gdharness_runtime/runtime_accessibility.gd +84 -63
- package/build/godot/addons/gdharness_runtime/runtime_autoload.gd +149 -147
- 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 +34 -17
- 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 +15 -15
- 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 +55 -33
- 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 +190 -109
- package/build/godot/operations/editor_tools.gd +10 -10
- package/build/godot/operations/export_presets.gd +100 -54
- package/build/godot/operations/file_reads.gd +83 -42
- package/build/godot/operations/file_walk.gd +12 -5
- package/build/godot/operations/gdscript_analysis.gd +86 -65
- package/build/godot/operations/gdscript_authoring.gd +83 -36
- 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 +62 -39
- package/build/godot/operations/patterns.gd +3 -1
- package/build/godot/operations/plugins.gd +36 -16
- package/build/godot/operations/project_config.gd +61 -32
- 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 +51 -17
- 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/build/keeper.js +43 -43
- package/package.json +1 -1
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
extends RefCounted
|
|
2
|
-
|
|
3
2
|
## What the server asks about the running tree: its shape, one node, the nodes matching a
|
|
4
3
|
## question, where one is on screen, a property's value, the metrics. What it changes, a property
|
|
5
4
|
## written or a method called, is in runtime_changes.gd.
|
|
@@ -11,19 +10,15 @@ const GdhSays = preload("runtime_says.gd")
|
|
|
11
10
|
const GdhScreen = preload("runtime_screen.gd")
|
|
12
11
|
const GdhValues = preload("runtime_values.gd")
|
|
13
12
|
const GdhWords = preload("runtime_words.gd")
|
|
14
|
-
|
|
15
13
|
## The most nodes one find answers with, unless asked for fewer: enough for any real query and
|
|
16
14
|
## far short of the tree dump a query exists to avoid.
|
|
17
15
|
const FIND_LIMIT: int = 100
|
|
18
|
-
|
|
19
16
|
## What a find can be narrowed by, which is also what it refuses to answer without. Named once so
|
|
20
17
|
## the refusal lists the same set the walk reads.
|
|
21
18
|
const FILTERS: PackedStringArray = ["class", "script", "name", "group", "says"]
|
|
22
|
-
|
|
23
19
|
## The most lines one read answers with, unless asked for fewer. A screen is a few dozen; a
|
|
24
20
|
## thousand is a tree somebody pointed this at by mistake.
|
|
25
21
|
const READ_LIMIT: int = 500
|
|
26
|
-
|
|
27
22
|
## The most either can be asked for. A limit above it is refused rather than cut to it: the ceiling
|
|
28
23
|
## was the default for a read, so the remedy its own answer gave, raising the limit, changed nothing
|
|
29
24
|
## and said nothing, and a caller told to raise a number that stays put concludes the screen moved.
|
|
@@ -59,7 +54,7 @@ func get_tree(params: Dictionary) -> Dictionary:
|
|
|
59
54
|
for each: Variant in listed:
|
|
60
55
|
named.append(str(each))
|
|
61
56
|
else:
|
|
62
|
-
return {"type": "error", "message": "properties must be a list of property names"}
|
|
57
|
+
return { "type": "error", "message": "properties must be a list of property names" }
|
|
63
58
|
|
|
64
59
|
var reached: Dictionary = GdhValues.node_at(_host.get_tree().root, root_path)
|
|
65
60
|
if reached.has("message"):
|
|
@@ -100,19 +95,24 @@ func find_nodes(params: Dictionary) -> Dictionary:
|
|
|
100
95
|
var limit: int = GdhRead.as_int(params.get("limit", FIND_LIMIT), FIND_LIMIT)
|
|
101
96
|
if limit < 1 or limit > LIMIT_CEILING:
|
|
102
97
|
return {
|
|
103
|
-
"type": "error",
|
|
98
|
+
"type": "error",
|
|
99
|
+
"message": "limit is from 1 to %d, and %d was asked for" % [LIMIT_CEILING, limit],
|
|
104
100
|
}
|
|
105
101
|
|
|
106
102
|
if not _anything_asked(wanted):
|
|
107
|
-
return {
|
|
103
|
+
return {
|
|
104
|
+
"type": "error", "message": "find_nodes needs at least one of " + ", ".join(FILTERS),
|
|
105
|
+
}
|
|
108
106
|
if not wanted["script"].is_empty() and not wanted["script"].begins_with("res://"):
|
|
109
107
|
wanted["script"] = "res://" + wanted["script"]
|
|
110
108
|
|
|
111
109
|
# Split once here rather than for each node the walk passes.
|
|
112
110
|
var names: Array[String] = GdhSays.split_at_bars(wanted["name"])
|
|
113
111
|
if not wanted["name"].is_empty() and names.is_empty():
|
|
114
|
-
var bars_only: String =
|
|
115
|
-
|
|
112
|
+
var bars_only: String = (
|
|
113
|
+
'name "%s" is only bars, so it names no node; write the names between them'
|
|
114
|
+
)
|
|
115
|
+
return { "type": "error", "message": bars_only % wanted["name"] }
|
|
116
116
|
|
|
117
117
|
var reached: Dictionary = GdhValues.node_at(_host.get_tree().root, root_path)
|
|
118
118
|
if reached.has("message"):
|
|
@@ -164,7 +164,11 @@ func find_nodes(params: Dictionary) -> Dictionary:
|
|
|
164
164
|
nearly += 1
|
|
165
165
|
else:
|
|
166
166
|
nearly_hidden += 1
|
|
167
|
-
elif
|
|
167
|
+
elif (
|
|
168
|
+
not widened_says.is_empty()
|
|
169
|
+
and _named(node, names)
|
|
170
|
+
and _matches_apart_from_name(node, widened)
|
|
171
|
+
):
|
|
168
172
|
if include_hidden or readable(node):
|
|
169
173
|
nearly_said += 1
|
|
170
174
|
else:
|
|
@@ -179,11 +183,13 @@ func find_nodes(params: Dictionary) -> Dictionary:
|
|
|
179
183
|
for index: int in range(children.size() - 1, -1, -1):
|
|
180
184
|
pending.push_front(children[index])
|
|
181
185
|
|
|
182
|
-
var answer: Dictionary = {
|
|
186
|
+
var answer: Dictionary = {
|
|
187
|
+
"type": "nodes", "count": found.size(), "truncated": truncated, "nodes": found,
|
|
188
|
+
}
|
|
183
189
|
var notes: Array[String] = []
|
|
184
|
-
# Each still listed, since a caller may mean one of them, and marked, so one sign drawn as a
|
|
185
|
-
# and its outline copies is not read as four signs. The outermost match it copies, since a
|
|
186
|
-
# under one of the copies has left the label they all copy outside it.
|
|
190
|
+
# Each still listed, since a caller may mean one of them, and marked, so one sign drawn as a
|
|
191
|
+
# label and its outline copies is not read as four signs. The outermost match it copies, since a
|
|
192
|
+
# find under one of the copies has left the label they all copy outside it.
|
|
187
193
|
var copies: int = 0
|
|
188
194
|
for index: int in range(found_nodes.size()):
|
|
189
195
|
var at: int = -1
|
|
@@ -201,8 +207,8 @@ func find_nodes(params: Dictionary) -> Dictionary:
|
|
|
201
207
|
. append(
|
|
202
208
|
(
|
|
203
209
|
(
|
|
204
|
-
"%d of the %d matches %s drawn under a match saying the same words, as an
|
|
205
|
-
+ " glow is drawn, and %s it under copyOf: %d distinct"
|
|
210
|
+
"%d of the %d matches %s drawn under a match saying the same words, as an "
|
|
211
|
+
+ "outline or a glow is drawn, and %s it under copyOf: %d distinct"
|
|
206
212
|
)
|
|
207
213
|
% [
|
|
208
214
|
copies,
|
|
@@ -230,7 +236,10 @@ func find_nodes(params: Dictionary) -> Dictionary:
|
|
|
230
236
|
notes
|
|
231
237
|
. append(
|
|
232
238
|
(
|
|
233
|
-
|
|
239
|
+
(
|
|
240
|
+
"name is matched as a glob against the whole name; %d %snode %s %s, which "
|
|
241
|
+
+ '"%s"%s would find'
|
|
242
|
+
)
|
|
234
243
|
% [
|
|
235
244
|
named_near,
|
|
236
245
|
"" if nearly > 0 else "hidden or out-of-view ",
|
|
@@ -248,7 +257,10 @@ func find_nodes(params: Dictionary) -> Dictionary:
|
|
|
248
257
|
notes
|
|
249
258
|
. append(
|
|
250
259
|
(
|
|
251
|
-
|
|
260
|
+
(
|
|
261
|
+
"says with a wildcard is matched as a glob against a node\'s whole text; "
|
|
262
|
+
+ '"%s"%s would find %d %snode%s'
|
|
263
|
+
)
|
|
252
264
|
% [
|
|
253
265
|
widened_says,
|
|
254
266
|
"" if nearly_said > 0 else " with includeHidden true",
|
|
@@ -271,13 +283,16 @@ func find_nodes(params: Dictionary) -> Dictionary:
|
|
|
271
283
|
)
|
|
272
284
|
if out_of_view > 0:
|
|
273
285
|
answer["out_of_view"] = out_of_view
|
|
274
|
-
|
|
275
|
-
|
|
286
|
+
(
|
|
287
|
+
notes
|
|
288
|
+
. append(
|
|
276
289
|
(
|
|
277
|
-
|
|
278
|
-
|
|
290
|
+
(
|
|
291
|
+
"%d matching node%s drawn outside the viewport or scrolled out of a "
|
|
292
|
+
+ "ScrollContainer and left out; includeHidden true answers with them too"
|
|
293
|
+
)
|
|
294
|
+
% [out_of_view, "" if out_of_view == 1 else "s"]
|
|
279
295
|
)
|
|
280
|
-
% [out_of_view, "" if out_of_view == 1 else "s"]
|
|
281
296
|
)
|
|
282
297
|
)
|
|
283
298
|
if not notes.is_empty():
|
|
@@ -289,11 +304,11 @@ func find_nodes(params: Dictionary) -> Dictionary:
|
|
|
289
304
|
##
|
|
290
305
|
## A CanvasItem's visibility comes down through CanvasItem parents only, and one whose parent is not
|
|
291
306
|
## a CanvasItem starts again from its canvas: measured on 4.7.2, a Label under a plain Node under a
|
|
292
|
-
## hidden panel, and one under a hidden Node3D, both answer visible in tree, while one straight
|
|
293
|
-
## the panel does not. Walking every ancestor called those two hidden, so their words were
|
|
294
|
-
## a screen that showed them and a wait on them ran out. What a CanvasItem's rule leaves
|
|
295
|
-
## canvas: a hidden layer hides everything drawn on it while each item keeps its own
|
|
296
|
-
## hidden window everything in it. A wait on words asks the same, so it reads this too.
|
|
307
|
+
## hidden panel, and one under a hidden Node3D, both answer visible in tree, while one straight
|
|
308
|
+
## under the panel does not. Walking every ancestor called those two hidden, so their words were
|
|
309
|
+
## left off a screen that showed them and a wait on them ran out. What a CanvasItem's rule leaves
|
|
310
|
+
## out is its canvas: a hidden layer hides everything drawn on it while each item keeps its own
|
|
311
|
+
## flag, and a hidden window everything in it. A wait on words asks the same, so it reads this too.
|
|
297
312
|
static func shown(node: Node) -> bool:
|
|
298
313
|
var item: CanvasItem = node as CanvasItem
|
|
299
314
|
if item != null:
|
|
@@ -355,9 +370,9 @@ func _found(node: Node, wanted_property: String) -> Dictionary:
|
|
|
355
370
|
if wanted_property.is_empty():
|
|
356
371
|
return entry
|
|
357
372
|
entry["property"] = wanted_property
|
|
358
|
-
# Through a path as well, for the reason [method GdhPaths.walk_to] gives. A step that is not
|
|
359
|
-
# this node reads as not having the property rather than as a refusal, which is the
|
|
360
|
-
# whole function is written to: a find matches nodes of several classes on purpose.
|
|
373
|
+
# Through a path as well, for the reason [method GdhPaths.walk_to] gives. A step that is not
|
|
374
|
+
# there on this node reads as not having the property rather than as a refusal, which is the
|
|
375
|
+
# rule this whole function is written to: a find matches nodes of several classes on purpose.
|
|
361
376
|
var reached: Dictionary = GdhPaths.walk_to(node, str(entry["path"]), wanted_property)
|
|
362
377
|
if reached.has("message"):
|
|
363
378
|
entry["has_property"] = false
|
|
@@ -463,7 +478,8 @@ func read_text(params: Dictionary) -> Dictionary:
|
|
|
463
478
|
var limit: int = GdhRead.as_int(params.get("limit", READ_LIMIT), READ_LIMIT)
|
|
464
479
|
if limit < 1 or limit > LIMIT_CEILING:
|
|
465
480
|
return {
|
|
466
|
-
"type": "error",
|
|
481
|
+
"type": "error",
|
|
482
|
+
"message": "limit is from 1 to %d, and %d was asked for" % [LIMIT_CEILING, limit],
|
|
467
483
|
}
|
|
468
484
|
|
|
469
485
|
var reached: Dictionary = GdhValues.node_at(_host.get_tree().root, root_path)
|
|
@@ -477,7 +493,7 @@ func read_text(params: Dictionary) -> Dictionary:
|
|
|
477
493
|
# and a bare `truncated: true` was looked straight past twice before the limit was suspected.
|
|
478
494
|
# A number is what sends somebody back.
|
|
479
495
|
var lines: Array[String] = []
|
|
480
|
-
var tally: Dictionary[String, int] = {"omitted": 0, "out_of_view": 0, "copies": 0}
|
|
496
|
+
var tally: Dictionary[String, int] = { "omitted": 0, "out_of_view": 0, "copies": 0 }
|
|
481
497
|
_read_into(root, include_hidden, limit, lines, tally)
|
|
482
498
|
var answer: Dictionary = {
|
|
483
499
|
"type": "text",
|
|
@@ -490,28 +506,35 @@ func read_text(params: Dictionary) -> Dictionary:
|
|
|
490
506
|
var notes: Array[String] = []
|
|
491
507
|
if tally["out_of_view"] > 0:
|
|
492
508
|
answer["out_of_view"] = tally["out_of_view"]
|
|
493
|
-
|
|
494
|
-
|
|
509
|
+
(
|
|
510
|
+
notes
|
|
511
|
+
. append(
|
|
495
512
|
(
|
|
496
|
-
|
|
497
|
-
|
|
513
|
+
(
|
|
514
|
+
"%d line%s drawn outside the viewport or scrolled out of a "
|
|
515
|
+
+ "ScrollContainer, where the player cannot read them, left out; "
|
|
516
|
+
+ "includeHidden true answers with them too"
|
|
517
|
+
)
|
|
518
|
+
% [tally["out_of_view"], "" if tally["out_of_view"] == 1 else "s"]
|
|
498
519
|
)
|
|
499
|
-
% [tally["out_of_view"], "" if tally["out_of_view"] == 1 else "s"]
|
|
500
520
|
)
|
|
501
521
|
)
|
|
502
522
|
if tally["copies"] > 0:
|
|
503
523
|
answer["copies"] = tally["copies"]
|
|
504
|
-
|
|
505
|
-
|
|
524
|
+
(
|
|
525
|
+
notes
|
|
526
|
+
. append(
|
|
506
527
|
(
|
|
507
|
-
|
|
508
|
-
|
|
528
|
+
(
|
|
529
|
+
"%d line%s drawn as a copy under a node saying the same words, as an "
|
|
530
|
+
+ "outline or a glow is drawn, read once with the words %s"
|
|
531
|
+
)
|
|
532
|
+
% [
|
|
533
|
+
tally["copies"],
|
|
534
|
+
"" if tally["copies"] == 1 else "s",
|
|
535
|
+
"it copies" if tally["copies"] == 1 else "they copy",
|
|
536
|
+
]
|
|
509
537
|
)
|
|
510
|
-
% [
|
|
511
|
-
tally["copies"],
|
|
512
|
-
"" if tally["copies"] == 1 else "s",
|
|
513
|
-
"it copies" if tally["copies"] == 1 else "they copy"
|
|
514
|
-
]
|
|
515
538
|
)
|
|
516
539
|
)
|
|
517
540
|
if not notes.is_empty():
|
|
@@ -522,14 +545,14 @@ func read_text(params: Dictionary) -> Dictionary:
|
|
|
522
545
|
## Walks [param node] depth first, which is the order the screen is laid out in and the order a
|
|
523
546
|
## person reads it.
|
|
524
547
|
##
|
|
525
|
-
## Internal children as well, because the number in a SpinBox is one: the field a player reads it
|
|
526
|
-
##
|
|
527
|
-
##
|
|
528
|
-
##
|
|
529
|
-
##
|
|
530
|
-
##
|
|
531
|
-
##
|
|
532
|
-
##
|
|
548
|
+
## Internal children as well, because the number in a SpinBox is one: the field a player reads it in
|
|
549
|
+
## is a LineEdit the engine builds inside the box and leaves out of `get_children()`, so a screen
|
|
550
|
+
## full of forms answered with every label on it and none of the values in it. The hidden check
|
|
551
|
+
## covers a [Window] for the same walk: a dropdown's popup is a child that is not drawn until it is
|
|
552
|
+
## opened, and reading a closed menu would put every item on the screen. Fills [param into] up to
|
|
553
|
+
## [param most] lines and counts in [param tally] how many further lines the rest of the subtree
|
|
554
|
+
## says, under omitted, and how many it says out of view, under out_of_view, which is what the
|
|
555
|
+
## caller is told rather than left to infer.
|
|
533
556
|
##
|
|
534
557
|
## Asked of each node rather than cut off at the first hidden one, since a hidden panel does not
|
|
535
558
|
## hide a label reached through a plain Node: see [method shown].
|
|
@@ -539,8 +562,8 @@ func _read_into(
|
|
|
539
562
|
if include_hidden or shown(node):
|
|
540
563
|
var said: Array[String] = GdhWords.lines_said_by(node)
|
|
541
564
|
# A copy drawn under the words it copies, as an outline or a glow is, reads once. Only while
|
|
542
|
-
# what it copies is itself read, so an original hidden or drawn out of view does not take
|
|
543
|
-
# words with it.
|
|
565
|
+
# what it copies is itself read, so an original hidden or drawn out of view does not take
|
|
566
|
+
# the words with it.
|
|
544
567
|
var original: Node = GdhWords.copied_from(node) if not said.is_empty() else null
|
|
545
568
|
if original != null and (include_hidden or readable(original)):
|
|
546
569
|
tally["copies"] += said.size()
|
|
@@ -563,7 +586,7 @@ func _read_into(
|
|
|
563
586
|
func get_rect(params: Dictionary) -> Dictionary:
|
|
564
587
|
var node_path: String = str(params.get("path", ""))
|
|
565
588
|
if node_path.is_empty():
|
|
566
|
-
return {"type": "error", "message": "Node path required"}
|
|
589
|
+
return { "type": "error", "message": "Node path required" }
|
|
567
590
|
|
|
568
591
|
var standing: Dictionary = GdhValues.node_at(_host.get_tree().root, node_path)
|
|
569
592
|
if standing.has("message"):
|
|
@@ -613,14 +636,15 @@ func get_rect(params: Dictionary) -> Dictionary:
|
|
|
613
636
|
var spatial: Node3D = node
|
|
614
637
|
return _in_the_frame(node_path, spatial)
|
|
615
638
|
return {
|
|
616
|
-
"type": "error",
|
|
639
|
+
"type": "error",
|
|
640
|
+
"message": "%s is a %s, which has no place on screen" % [node_path, node.get_class()],
|
|
617
641
|
}
|
|
618
642
|
|
|
619
643
|
|
|
620
644
|
## The rectangle a 2D node's own geometry takes up in its own coordinates, as the one entry of the
|
|
621
645
|
## list, which is empty for a node that has none of its own: a sprite's texture, a collision shape's
|
|
622
|
-
## shape, the points of a polygon or a line. A Node2D answered only where it stands, so the extent
|
|
623
|
-
## a floor or a sprite to aim inside was a read of its shape and some arithmetic.
|
|
646
|
+
## shape, the points of a polygon or a line. A Node2D answered only where it stands, so the extent
|
|
647
|
+
## of a floor or a sprite to aim inside was a read of its shape and some arithmetic.
|
|
624
648
|
static func _drawn_rect(item: Node2D) -> Array[Rect2]:
|
|
625
649
|
var drawn: Array[Rect2] = []
|
|
626
650
|
var sprite: Sprite2D = item as Sprite2D
|
|
@@ -668,7 +692,8 @@ func _in_the_frame(node_path: String, item: Node3D) -> Dictionary:
|
|
|
668
692
|
if found.is_empty():
|
|
669
693
|
return {
|
|
670
694
|
"type": "error",
|
|
671
|
-
"message":
|
|
695
|
+
"message":
|
|
696
|
+
"%s is not in a viewport with a current Camera3D, so nothing is drawing it" % node_path,
|
|
672
697
|
}
|
|
673
698
|
|
|
674
699
|
var drawn_in: Viewport = item.get_viewport()
|
|
@@ -713,7 +738,9 @@ static func in_frame(item: Node3D) -> Dictionary:
|
|
|
713
738
|
return {}
|
|
714
739
|
|
|
715
740
|
var origin: Vector3 = item.global_transform.origin
|
|
716
|
-
var found: Dictionary = {
|
|
741
|
+
var found: Dictionary = {
|
|
742
|
+
"camera": str(camera.get_path()), "behind": camera.is_position_behind(origin),
|
|
743
|
+
}
|
|
717
744
|
var box: AABB = drawn_box(item)
|
|
718
745
|
var draws: bool = box.size != Vector3.ZERO
|
|
719
746
|
var middle: Vector3 = box.get_center() if draws else origin
|
|
@@ -740,7 +767,7 @@ static func _around(box: AABB, camera: Camera3D) -> Dictionary:
|
|
|
740
767
|
return {}
|
|
741
768
|
var point: Vector2 = camera.unproject_position(corner)
|
|
742
769
|
seen = Rect2(point, Vector2.ZERO) if index == 0 else seen.expand(point)
|
|
743
|
-
return {"rect": seen}
|
|
770
|
+
return { "rect": seen }
|
|
744
771
|
|
|
745
772
|
|
|
746
773
|
## The box everything drawn under [param item] fits in, in world space, or a box with no size when
|
|
@@ -770,7 +797,7 @@ func get_property(params: Dictionary) -> Dictionary:
|
|
|
770
797
|
var property: String = str(params.get("property", ""))
|
|
771
798
|
|
|
772
799
|
if node_path.is_empty() or property.is_empty():
|
|
773
|
-
return {"type": "error", "message": "Node path and property required"}
|
|
800
|
+
return { "type": "error", "message": "Node path and property required" }
|
|
774
801
|
|
|
775
802
|
var standing: Dictionary = GdhValues.target_at(_host.get_tree().root, node_path)
|
|
776
803
|
if standing.has("message"):
|
|
@@ -786,7 +813,10 @@ func get_property(params: Dictionary) -> Dictionary:
|
|
|
786
813
|
# Asked of the holder rather than read and compared to null, because a property the holder does
|
|
787
814
|
# not have and a property that is null both read as null.
|
|
788
815
|
if not GdhPaths.can_read(holder, named):
|
|
789
|
-
return {
|
|
816
|
+
return {
|
|
817
|
+
"type": "error",
|
|
818
|
+
"message": GdhPaths.nothing_there(holder, named, str(reached["called"])),
|
|
819
|
+
}
|
|
790
820
|
|
|
791
821
|
return {
|
|
792
822
|
"type": "property",
|
|
@@ -828,10 +858,10 @@ func get_metrics(params: Dictionary) -> Dictionary:
|
|
|
828
858
|
var frame_time_max: Variant = null
|
|
829
859
|
var physics_steps_per_second: Variant = null
|
|
830
860
|
if timed > 0:
|
|
831
|
-
frame_time = float(total_usec) / timed /
|
|
832
|
-
frame_time_max = slowest_usec /
|
|
861
|
+
frame_time = float(total_usec) / timed / 1_000_000.0
|
|
862
|
+
frame_time_max = slowest_usec / 1_000_000.0
|
|
833
863
|
if total_usec > 0:
|
|
834
|
-
physics_steps_per_second = steps *
|
|
864
|
+
physics_steps_per_second = steps * 1_000_000.0 / total_usec
|
|
835
865
|
var all: Dictionary = {
|
|
836
866
|
"fps": Engine.get_frames_per_second(),
|
|
837
867
|
"frame_time": frame_time,
|
|
@@ -854,9 +884,11 @@ func get_metrics(params: Dictionary) -> Dictionary:
|
|
|
854
884
|
"object_node_count": Performance.get_monitor(Performance.OBJECT_NODE_COUNT),
|
|
855
885
|
"object_orphan_node_count": Performance.get_monitor(Performance.OBJECT_ORPHAN_NODE_COUNT),
|
|
856
886
|
"render_total_objects": Performance.get_monitor(Performance.RENDER_TOTAL_OBJECTS_IN_FRAME),
|
|
857
|
-
"render_total_primitives":
|
|
858
|
-
|
|
887
|
+
"render_total_primitives":
|
|
888
|
+
Performance.get_monitor(Performance.RENDER_TOTAL_PRIMITIVES_IN_FRAME),
|
|
889
|
+
"render_total_draw_calls":
|
|
859
890
|
# The GPU's half of the game's memory, in bytes, which memory_static does not count: a game
|
|
891
|
+
Performance.get_monitor(Performance.RENDER_TOTAL_DRAW_CALLS_IN_FRAME),
|
|
860
892
|
# committing 2.3 GB answered 298 MB there, and the rest could not be asked about.
|
|
861
893
|
"render_video_mem_used": Performance.get_monitor(Performance.RENDER_VIDEO_MEM_USED),
|
|
862
894
|
"render_texture_mem_used": Performance.get_monitor(Performance.RENDER_TEXTURE_MEM_USED),
|
|
@@ -864,7 +896,7 @@ func get_metrics(params: Dictionary) -> Dictionary:
|
|
|
864
896
|
}
|
|
865
897
|
|
|
866
898
|
if metrics.is_empty():
|
|
867
|
-
return {"type": "metrics", "data": all}
|
|
899
|
+
return { "type": "metrics", "data": all }
|
|
868
900
|
|
|
869
901
|
# A caller that names metrics gets those and no others, and hears about a name that is
|
|
870
902
|
# not one rather than getting everything back as if the list had not been sent.
|
|
@@ -878,9 +910,10 @@ func get_metrics(params: Dictionary) -> Dictionary:
|
|
|
878
910
|
if not unknown.is_empty():
|
|
879
911
|
return {
|
|
880
912
|
"type": "error",
|
|
881
|
-
"message":
|
|
913
|
+
"message":
|
|
914
|
+
"Unknown metrics: %s. Available: %s" % [", ".join(unknown), ", ".join(all.keys())],
|
|
882
915
|
}
|
|
883
|
-
return {"type": "metrics", "data": selected}
|
|
916
|
+
return { "type": "metrics", "data": selected }
|
|
884
917
|
|
|
885
918
|
|
|
886
919
|
func _serialize_node_tree(
|
|
@@ -897,7 +930,9 @@ func _serialize_node_tree(
|
|
|
897
930
|
# it is wrong, and it is what sends somebody looking for another way to press the button.
|
|
898
931
|
# `depth` is what keeps the answer a size worth reading.
|
|
899
932
|
for child: Node in node.get_children(true):
|
|
900
|
-
children.append(
|
|
933
|
+
children.append(
|
|
934
|
+
_serialize_node_tree(child, depth + 1, max_depth, include_properties, named)
|
|
935
|
+
)
|
|
901
936
|
result["children"] = children
|
|
902
937
|
|
|
903
938
|
return result
|
|
@@ -920,7 +955,9 @@ func _named_properties_of(node: Node, path: String, named: Array[String]) -> Dic
|
|
|
920
955
|
|
|
921
956
|
|
|
922
957
|
func _serialize_node(node: Node, include_properties: bool) -> Dictionary:
|
|
923
|
-
var result: Dictionary = {
|
|
958
|
+
var result: Dictionary = {
|
|
959
|
+
"name": node.name, "type": node.get_class(), "path": str(node.get_path()),
|
|
960
|
+
}
|
|
924
961
|
|
|
925
962
|
var script: Variant = node.get_script()
|
|
926
963
|
if script is Script:
|
|
@@ -929,8 +966,9 @@ func _serialize_node(node: Node, include_properties: bool) -> Dictionary:
|
|
|
929
966
|
|
|
930
967
|
if include_properties:
|
|
931
968
|
var properties: Dictionary = {}
|
|
932
|
-
# A script's own variables as well as what the node stores: a plain `var health` is not
|
|
933
|
-
# so a tree read of a running game answered every engine property and none of the
|
|
969
|
+
# A script's own variables as well as what the node stores: a plain `var health` is not
|
|
970
|
+
# stored, so a tree read of a running game answered every engine property and none of the
|
|
971
|
+
# game's state.
|
|
934
972
|
for prop: Dictionary in node.get_property_list():
|
|
935
973
|
if prop["usage"] & (PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_SCRIPT_VARIABLE):
|
|
936
974
|
var property_name: String = prop["name"]
|
|
@@ -1,24 +1,19 @@
|
|
|
1
1
|
extends RefCounted
|
|
2
|
-
|
|
3
2
|
## Whether a node says what a caller asked for in `says`: the one set of rules a find, a click and a
|
|
4
3
|
## wait all match words by, so the three never disagree about which control a word names.
|
|
5
4
|
|
|
6
5
|
const GdhWords = preload("runtime_words.gd")
|
|
7
|
-
|
|
8
6
|
## Stands in for a bar written as `\|` while `says` is split into its alternatives: a character from
|
|
9
7
|
## the private use area, which no game's text uses.
|
|
10
8
|
const ESCAPED_BAR: String = ""
|
|
11
|
-
|
|
12
|
-
##
|
|
13
|
-
## and the same words widened, and anything past a handful is a caller that has moved on.
|
|
9
|
+
## How many splits [method alternatives] keeps. A find asks about two at once, the words it was
|
|
10
|
+
## given and the same words widened, and anything past a handful is a caller that has moved on.
|
|
14
11
|
const SPLITS_KEPT: int = 8
|
|
15
12
|
|
|
16
13
|
## Each `says` split into alternatives, by what was written; see [method alternatives].
|
|
17
14
|
static var _splits: Dictionary[String, Array] = {}
|
|
18
|
-
|
|
19
15
|
## Each `says` as the globs [method matches] tries first, by what was written; see [method _loose].
|
|
20
16
|
static var _globs: Dictionary[String, Array] = {}
|
|
21
|
-
|
|
22
17
|
## Every space separator Unicode has besides the plain space, built once; see [method spaced].
|
|
23
18
|
static var _spaces: RegEx = null
|
|
24
19
|
|
|
@@ -37,9 +32,9 @@ static func says(node: Node, wanted: String) -> bool:
|
|
|
37
32
|
## screen: twice in one session here, over a button that was.
|
|
38
33
|
##
|
|
39
34
|
## Several alternatives separated by `|` match when any one of them does, each by those rules: a
|
|
40
|
-
## wait for the end of a turn is a wait for whichever of "won", "lost" or the next turn's words
|
|
41
|
-
## first, and read as one glob with the bars in it, it waited out its whole timeout for words
|
|
42
|
-
## screen says.
|
|
35
|
+
## wait for the end of a turn is a wait for whichever of "won", "lost" or the next turn's words
|
|
36
|
+
## comes first, and read as one glob with the bars in it, it waited out its whole timeout for words
|
|
37
|
+
## no screen says.
|
|
43
38
|
##
|
|
44
39
|
## A space in the words matches any space separator in [param said], which is how they were typed.
|
|
45
40
|
## Each node's words are spaced only once a looser match says they may be the ones: this runs for
|
|
@@ -87,13 +82,13 @@ static func spaced(text: String) -> String:
|
|
|
87
82
|
return text
|
|
88
83
|
if _spaces == null:
|
|
89
84
|
var separators: String = (
|
|
90
|
-
String.chr(
|
|
85
|
+
String.chr(0x00a0)
|
|
91
86
|
+ String.chr(0x1680)
|
|
92
87
|
+ String.chr(0x2000)
|
|
93
88
|
+ "-"
|
|
94
|
-
+ String.chr(
|
|
95
|
-
+ String.chr(
|
|
96
|
-
+ String.chr(
|
|
89
|
+
+ String.chr(0x200a)
|
|
90
|
+
+ String.chr(0x202f)
|
|
91
|
+
+ String.chr(0x205f)
|
|
97
92
|
+ String.chr(0x3000)
|
|
98
93
|
)
|
|
99
94
|
_spaces = RegEx.create_from_string("[%s]" % separators)
|
|
@@ -194,13 +189,15 @@ static func point_on_words(label: RichTextLabel, wanted: String) -> Dictionary:
|
|
|
194
189
|
start = at
|
|
195
190
|
length = words.length()
|
|
196
191
|
if start < 0:
|
|
197
|
-
return {
|
|
192
|
+
return {
|
|
193
|
+
"unplaced": "the words were matched as a pattern, which names no one place in the text",
|
|
194
|
+
}
|
|
198
195
|
if text.strip_edges().length() == length:
|
|
199
196
|
return {}
|
|
200
197
|
var line: int = label.get_character_line(start)
|
|
201
198
|
var shown: Vector2i = label.get_line_range(line)
|
|
202
199
|
if line < 0 or shown.x > start:
|
|
203
|
-
return {"unplaced": "the label could not say which line the words are on"}
|
|
200
|
+
return { "unplaced": "the label could not say which line the words are on" }
|
|
204
201
|
var on_line: int = mini(length, shown.y - start) if shown.y > start else length
|
|
205
202
|
var font: Font = label.get_theme_font("normal_font")
|
|
206
203
|
var font_size: int = label.get_theme_font_size("normal_font_size")
|
|
@@ -218,7 +215,7 @@ static func point_on_words(label: RichTextLabel, wanted: String) -> Dictionary:
|
|
|
218
215
|
var scrolled: float = label.get_v_scroll_bar().value if label.scroll_active else 0.0
|
|
219
216
|
var x: float = clampf(left + aligned + before + across * 0.5, left, left + room)
|
|
220
217
|
var top: float = frame.get_margin(SIDE_TOP) + label.get_line_offset(line) - scrolled
|
|
221
|
-
return {"point": Vector2(x, top + label.get_line_height(line) * 0.5)}
|
|
218
|
+
return { "point": Vector2(x, top + label.get_line_height(line) * 0.5) }
|
|
222
219
|
|
|
223
220
|
|
|
224
221
|
## How wide [param font] at [param font_size] draws [param words] on one line.
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
extends RefCounted
|
|
2
|
-
|
|
3
2
|
## Where a point in some viewport is on the game's own screen, for a click to be sent there and for
|
|
4
3
|
## an answer to say where something is drawn.
|
|
5
4
|
|
|
@@ -28,24 +27,28 @@ static func reach(viewport: Viewport, point: Vector2) -> Dictionary:
|
|
|
28
27
|
## viewport's space, and the embedded [Window] or the [SubViewportContainer] in it that shows the
|
|
29
28
|
## step before, null for the first. What [method reach] answers is the last of them.
|
|
30
29
|
static func steps_out(viewport: Viewport, point: Vector2) -> Array[Dictionary]:
|
|
31
|
-
var out: Array[Dictionary] = [{"viewport": viewport, "point": point, "through": null}]
|
|
30
|
+
var out: Array[Dictionary] = [{ "viewport": viewport, "point": point, "through": null }]
|
|
32
31
|
while true:
|
|
33
32
|
var window: Window = viewport as Window
|
|
34
|
-
var host: Viewport =
|
|
33
|
+
var host: Viewport = (
|
|
34
|
+
embedder_of(window) if window != null and window.is_embedded() else null
|
|
35
|
+
)
|
|
35
36
|
if host != null:
|
|
36
37
|
# A window's canvas is drawn into its embedder through its own final transform, which is
|
|
37
|
-
# where a content scale goes: one at 4 drew a button at four times the point it was
|
|
38
|
-
# at, and the click missed it.
|
|
38
|
+
# where a content scale goes: one at 4 drew a button at four times the point it was
|
|
39
|
+
# clicked at, and the click missed it.
|
|
39
40
|
point = Vector2(window.position) + window.get_final_transform() * point
|
|
40
41
|
viewport = host
|
|
41
|
-
out.append({"viewport": viewport, "point": point, "through": window})
|
|
42
|
+
out.append({ "viewport": viewport, "point": point, "through": window })
|
|
42
43
|
continue
|
|
43
44
|
var shown_by: SubViewportContainer = viewport.get_parent() as SubViewportContainer
|
|
44
45
|
if viewport is SubViewport and shown_by != null:
|
|
45
|
-
var scaled: Vector2 =
|
|
46
|
+
var scaled: Vector2 = (
|
|
47
|
+
point * float(shown_by.stretch_shrink) if shown_by.stretch else point
|
|
48
|
+
)
|
|
46
49
|
point = shown_by.get_global_transform_with_canvas() * scaled
|
|
47
50
|
viewport = shown_by.get_viewport()
|
|
48
|
-
out.append({"viewport": viewport, "point": point, "through": shown_by})
|
|
51
|
+
out.append({ "viewport": viewport, "point": point, "through": shown_by })
|
|
49
52
|
continue
|
|
50
53
|
break
|
|
51
54
|
return out
|
|
@@ -101,7 +104,9 @@ static func shown_part(control: Control) -> Variant:
|
|
|
101
104
|
while walking != null and not (walking is Viewport):
|
|
102
105
|
var holder: ScrollContainer = walking as ScrollContainer
|
|
103
106
|
if holder != null:
|
|
104
|
-
clips.append(
|
|
107
|
+
clips.append(
|
|
108
|
+
holder.get_global_transform_with_canvas() * Rect2(Vector2.ZERO, holder.size)
|
|
109
|
+
)
|
|
105
110
|
walking = walking.get_parent()
|
|
106
111
|
for clip: Rect2 in clips:
|
|
107
112
|
if not part.intersects(clip, true):
|
|
@@ -125,7 +130,9 @@ static func clipped_by(control: Control, centre: Vector2) -> ScrollContainer:
|
|
|
125
130
|
# Carried into the same space the centre is in, which is the canvas rather than the
|
|
126
131
|
# container's own: the two are only the same while nothing above it is transformed.
|
|
127
132
|
if holder != null:
|
|
128
|
-
var seen: Rect2 =
|
|
133
|
+
var seen: Rect2 = (
|
|
134
|
+
holder.get_global_transform_with_canvas() * Rect2(Vector2.ZERO, holder.size)
|
|
135
|
+
)
|
|
129
136
|
if not seen.has_point(centre):
|
|
130
137
|
return holder
|
|
131
138
|
walking = walking.get_parent()
|
|
@@ -139,8 +146,8 @@ static func clipped_by(control: Control, centre: Vector2) -> ScrollContainer:
|
|
|
139
146
|
## next layout pass rather than inside the call, and the next one out works out where the control is
|
|
140
147
|
## from where it is drawn, so asked in the same frame it scrolled to where the control had been.
|
|
141
148
|
## Moved means a scroll value changed: a container asked to show what it already shows moves
|
|
142
|
-
## nothing, and answering true for it said the view had moved when it had not. Only the containers
|
|
143
|
-
## the control's own viewport: one holding a dialog scrolls the page behind it, not the dialog.
|
|
149
|
+
## nothing, and answering true for it said the view had moved when it had not. Only the containers
|
|
150
|
+
## in the control's own viewport: one holding a dialog scrolls the page behind it, not the dialog.
|
|
144
151
|
static func scroll_into_view(control: Control, tree: SceneTree) -> bool:
|
|
145
152
|
var moved: bool = false
|
|
146
153
|
var walking: Variant = control.get_parent()
|
|
@@ -179,17 +186,17 @@ static func through_the_interface(out: Array[Dictionary], hover: GdhHover) -> Di
|
|
|
179
186
|
var window: Window = through
|
|
180
187
|
var point: Vector2 = step["point"]
|
|
181
188
|
if not Rect2(window.position, window.size).has_point(point):
|
|
182
|
-
return {"landed": false, "hovered": _path_or_null(hovered)}
|
|
189
|
+
return { "landed": false, "hovered": _path_or_null(hovered) }
|
|
183
190
|
continue
|
|
184
191
|
if through is SubViewportContainer:
|
|
185
192
|
if hovered != through:
|
|
186
|
-
return {"landed": false, "hovered": _path_or_null(hovered)}
|
|
193
|
+
return { "landed": false, "hovered": _path_or_null(hovered) }
|
|
187
194
|
continue
|
|
188
195
|
if hovered != null and not _passes_on(hovered):
|
|
189
|
-
return {"landed": false, "hovered": str(hovered.get_path())}
|
|
196
|
+
return { "landed": false, "hovered": str(hovered.get_path()) }
|
|
190
197
|
if hovered != null and first_under == null:
|
|
191
198
|
first_under = str(hovered.get_path())
|
|
192
|
-
return {"landed": true, "hovered": first_under}
|
|
199
|
+
return { "landed": true, "hovered": first_under }
|
|
193
200
|
|
|
194
201
|
|
|
195
202
|
## Whether a pointer over [param control] goes on past the interface: a control that lets it pass
|