gdharness 1.3.8 → 1.3.10

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.
Files changed (78) hide show
  1. package/build/cli.js +4668 -3229
  2. package/build/godot/addons/gdharness_editor/animation_reads.gd +115 -4
  3. package/build/godot/addons/gdharness_editor/declared_values.gd +272 -0
  4. package/build/godot/addons/gdharness_editor/file_reads.gd +115 -18
  5. package/build/godot/addons/gdharness_editor/headless_check.gd +49 -2
  6. package/build/godot/addons/gdharness_editor/property_paths.gd +89 -16
  7. package/build/godot/addons/gdharness_editor/property_values.gd +180 -121
  8. package/build/godot/addons/gdharness_editor/reading.gd +112 -6
  9. package/build/godot/addons/gdharness_editor/scene_file.gd +117 -174
  10. package/build/godot/addons/gdharness_editor/scene_uids.gd +107 -0
  11. package/build/godot/addons/gdharness_editor/scene_uids.gd.uid +1 -0
  12. package/build/godot/addons/gdharness_editor/serialisation.gd +19 -29
  13. package/build/godot/addons/gdharness_editor/setting_reads.gd +121 -0
  14. package/build/godot/addons/gdharness_editor/setting_reads.gd.uid +1 -0
  15. package/build/godot/addons/gdharness_editor/tool_executor.gd +39 -6
  16. package/build/godot/addons/gdharness_editor/tools/animation_states.gd +263 -0
  17. package/build/godot/addons/gdharness_editor/tools/animation_states.gd.uid +1 -0
  18. package/build/godot/addons/gdharness_editor/tools/animation_tools.gd +49 -414
  19. package/build/godot/addons/gdharness_editor/tools/animation_tracks.gd +464 -0
  20. package/build/godot/addons/gdharness_editor/tools/animation_tracks.gd.uid +1 -0
  21. package/build/godot/addons/gdharness_editor/tools/class_tools.gd +37 -0
  22. package/build/godot/addons/gdharness_editor/tools/navigation_bake.gd +221 -0
  23. package/build/godot/addons/gdharness_editor/tools/navigation_bake.gd.uid +1 -0
  24. package/build/godot/addons/gdharness_editor/tools/resource_tools.gd +162 -483
  25. package/build/godot/addons/gdharness_editor/tools/scene_groups.gd +16 -0
  26. package/build/godot/addons/gdharness_editor/tools/scene_kin.gd +8 -4
  27. package/build/godot/addons/gdharness_editor/tools/scene_paths.gd +2 -1
  28. package/build/godot/addons/gdharness_editor/tools/scene_placing.gd +10 -0
  29. package/build/godot/addons/gdharness_editor/tools/scene_tools.gd +151 -8
  30. package/build/godot/addons/gdharness_editor/tools/settings_tools.gd +7 -30
  31. package/build/godot/addons/gdharness_editor/tools/signal_tools.gd +190 -38
  32. package/build/godot/addons/gdharness_editor/tools/tileset_tools.gd +485 -0
  33. package/build/godot/addons/gdharness_editor/tools/tileset_tools.gd.uid +1 -0
  34. package/build/godot/addons/gdharness_editor/value_shapes.gd +128 -1
  35. package/build/godot/addons/gdharness_runtime/call_errors.gd +8 -2
  36. package/build/godot/addons/gdharness_runtime/declared_values.gd +272 -0
  37. package/build/godot/addons/gdharness_runtime/reading.gd +112 -6
  38. package/build/godot/addons/gdharness_runtime/runtime_autoload.gd +35 -3
  39. package/build/godot/addons/gdharness_runtime/runtime_capture.gd +5 -0
  40. package/build/godot/addons/gdharness_runtime/runtime_changes.gd +188 -30
  41. package/build/godot/addons/gdharness_runtime/runtime_input.gd +107 -104
  42. package/build/godot/addons/gdharness_runtime/runtime_mouse.gd +17 -0
  43. package/build/godot/addons/gdharness_runtime/runtime_pad.gd +13 -4
  44. package/build/godot/addons/gdharness_runtime/runtime_paths.gd +320 -27
  45. package/build/godot/addons/gdharness_runtime/runtime_queries.gd +165 -20
  46. package/build/godot/addons/gdharness_runtime/runtime_screen.gd +49 -3
  47. package/build/godot/addons/gdharness_runtime/runtime_targets.gd +31 -2
  48. package/build/godot/addons/gdharness_runtime/runtime_values.gd +250 -10
  49. package/build/godot/addons/gdharness_runtime/runtime_waits.gd +279 -11
  50. package/build/godot/addons/gdharness_runtime/runtime_words.gd +27 -0
  51. package/build/godot/addons/gdharness_runtime/serialisation.gd +19 -29
  52. package/build/godot/addons/gdharness_runtime/value_shapes.gd +128 -1
  53. package/build/godot/operations/animation_reads.gd +115 -4
  54. package/build/godot/operations/audio_buses.gd +93 -7
  55. package/build/godot/operations/classdb_queries.gd +119 -7
  56. package/build/godot/operations/declared_values.gd +272 -0
  57. package/build/godot/operations/dependencies.gd +154 -41
  58. package/build/godot/operations/editor_tools.gd +29 -13
  59. package/build/godot/operations/export_presets.gd +602 -4
  60. package/build/godot/operations/file_reads.gd +115 -18
  61. package/build/godot/operations/gdscript_analysis.gd +14 -0
  62. package/build/godot/operations/gdscript_authoring.gd +12 -2
  63. package/build/godot/operations/godot_operations.gd +18 -2
  64. package/build/godot/operations/import_pipeline.gd +20 -221
  65. package/build/godot/operations/input_actions.gd +127 -51
  66. package/build/godot/operations/project_config.gd +113 -68
  67. package/build/godot/operations/project_diagnostics.gd +4 -0
  68. package/build/godot/operations/project_validation.gd +266 -0
  69. package/build/godot/operations/property_paths.gd +89 -16
  70. package/build/godot/operations/property_values.gd +180 -121
  71. package/build/godot/operations/reading.gd +112 -6
  72. package/build/godot/operations/scene_connections.gd +222 -0
  73. package/build/godot/operations/serialisation.gd +19 -29
  74. package/build/godot/operations/setting_reads.gd +121 -0
  75. package/build/godot/operations/value_shapes.gd +128 -1
  76. package/build/godot/tests/listed.gd +20 -13
  77. package/build/keeper.js +1402 -707
  78. package/package.json +1 -1
@@ -6,9 +6,33 @@ extends Node
6
6
 
7
7
  const GdhRead = preload("../reading.gd")
8
8
  const GdhSceneFile = preload("../scene_file.gd")
9
+ const GdhSerialisation = preload("../serialisation.gd")
9
10
 
10
11
  ## What every emission meets when a connection cannot call its method.
11
12
  const FAILS: String = ", so every emission fails to call it."
13
+ ## The names Object.ConnectFlags gives each bit. The number alone left a reader guessing that 2 is
14
+ ## the persistence every connection in a saved scene carries.
15
+ const CONNECT_FLAG_NAMES: Dictionary[int, String] = {
16
+ Object.CONNECT_DEFERRED: "deferred",
17
+ Object.CONNECT_PERSIST: "persist",
18
+ Object.CONNECT_ONE_SHOT: "one_shot",
19
+ Object.CONNECT_REFERENCE_COUNTED: "reference_counted",
20
+ Object.CONNECT_APPEND_SOURCE_OBJECT: "append_source_object",
21
+ # The engine's CONNECT_INHERITED, which GDScript is not given: on a connection an instanced scene
22
+ # makes inside itself, as a scene holding one loads it.
23
+ 32: "inherited",
24
+ }
25
+ ## The flags a caller may give: the engine's own CONNECT_INHERITED marks a connection an instanced
26
+ ## scene makes, and is never one a scene makes for itself.
27
+ const GIVEN_FLAGS: int = (
28
+ Object.CONNECT_DEFERRED
29
+ | Object.CONNECT_PERSIST
30
+ | Object.CONNECT_ONE_SHOT
31
+ | Object.CONNECT_REFERENCE_COUNTED
32
+ | Object.CONNECT_APPEND_SOURCE_OBJECT
33
+ )
34
+
35
+ var _values: GdhSerialisation = GdhSerialisation.new()
12
36
 
13
37
 
14
38
  func connect_signal(args: Dictionary) -> Dictionary:
@@ -20,7 +44,8 @@ func connect_signal(args: Dictionary) -> Dictionary:
20
44
  var method_name: String = str(args.get("methodName", ""))
21
45
  # A connection without CONNECT_PERSIST is a runtime one, and PackedScene.pack drops those on
22
46
  # the way out: without this the scene saves unchanged and this answers success over nothing.
23
- var flags: int = GdhRead.as_int(args.get("flags", 0)) | Object.CONNECT_PERSIST
47
+ var given_flags: int = GdhRead.as_int(args.get("flags", 0))
48
+ var flags: int = given_flags | Object.CONNECT_PERSIST
24
49
 
25
50
  if (
26
51
  source_node_path.is_empty()
@@ -29,6 +54,9 @@ func connect_signal(args: Dictionary) -> Dictionary:
29
54
  or method_name.is_empty()
30
55
  ):
31
56
  return {"ok": false, "error": "Missing required signal connection arguments"}
57
+ var unasked: String = _unasked(given_flags)
58
+ if not unasked.is_empty():
59
+ return {"ok": false, "error": unasked}
32
60
 
33
61
  var scene: GdhSceneFile = GdhSceneFile.new()
34
62
  var refused: Dictionary = scene.open(scene_path, true)
@@ -43,40 +71,57 @@ func connect_signal(args: Dictionary) -> Dictionary:
43
71
 
44
72
  # Reconnected rather than left alone when the flags differ, so the connection saved is the one
45
73
  # asked for. Left alone, the answer gave the flags asked for and the file kept the old ones.
74
+ # Found by its object and method, as the engine finds it: a connection carrying binds is another
75
+ # callable, so comparing callables missed it, and the engine's is_connected then found it, took it
76
+ # out and put a bare one in its place, losing the binds without a word.
77
+ var before: Dictionary = _held(source, signal_name, target, method_name)
78
+ var binds: Array = []
79
+ var unbinds: int = 0
80
+ if not before.is_empty():
81
+ binds = before["binds"]
82
+ unbinds = before["unbinds"]
83
+ if args.has("unbinds"):
84
+ unbinds = GdhRead.as_int(args["unbinds"])
85
+ if args.has("binds"):
86
+ var given: Array = args["binds"]
87
+ binds = _bound_values(given, source, signal_name, Callable(target, method_name), unbinds, flags)
46
88
  var callable: Callable = Callable(target, method_name)
89
+ if not binds.is_empty():
90
+ callable = callable.bindv(binds)
91
+ if unbinds > 0:
92
+ callable = callable.unbind(unbinds)
47
93
  var method_found: bool = target.has_method(method_name)
48
94
  var misfit: String = _misfit(source, signal_name, callable, flags)
49
- var flags_before: int = -1
50
- for connection: Dictionary in source.get_signal_connection_list(signal_name):
51
- if connection.get("callable") == callable:
52
- flags_before = GdhRead.as_int(connection.get("flags", 0))
53
- if source.is_connected(signal_name, callable):
54
- source.disconnect(signal_name, callable)
95
+ if not before.is_empty():
96
+ var held: Callable = before["callable"]
97
+ source.disconnect(signal_name, held)
55
98
  var connect_result: Error = source.connect(signal_name, callable, flags)
56
99
  if connect_result != OK:
57
100
  return scene.refuse("Failed to connect signal: " + error_string(connect_result))
58
101
 
59
102
  var from: String = scene.path_of(source)
60
103
  var to: String = scene.path_of(target)
61
- var saved_flags: Array = [-1]
104
+ var saved: Dictionary = {}
62
105
  var written: Dictionary = scene.write(
63
- func(saved: Node) -> String:
64
- saved_flags[0] = _connection_flags(saved, from, signal_name, to, method_name)
65
- if saved_flags[0] < 0:
106
+ func(loaded: Node) -> String:
107
+ var found: Dictionary = _saved(loaded, from, signal_name, to, method_name)
108
+ saved.merge(found)
109
+ if found.is_empty():
66
110
  return "the connection would not be in the scene when it loads."
67
111
  return ""
68
112
  )
69
113
  if not written.is_empty():
70
114
  return written
71
115
 
116
+ var saved_flags: int = saved["flags"]
72
117
  var answer: Dictionary = {
73
118
  "ok": true,
74
119
  "sourceNodePath": from,
75
120
  "signalName": signal_name,
76
121
  "targetNodePath": to,
77
122
  "methodName": method_name,
78
- "flags": saved_flags[0],
79
- "flagNames": GdhSceneFile.flag_names(GdhRead.as_int(saved_flags[0])),
123
+ "flags": saved_flags,
124
+ "flagNames": flag_names(saved_flags),
80
125
  # Connected either way, as the editor's own dialog allows, since the method may be written
81
126
  # next; a caller told nothing learnt of a misspelt one only when the signal fired.
82
127
  "methodFound": method_found
@@ -89,13 +134,108 @@ func connect_signal(args: Dictionary) -> Dictionary:
89
134
  # A second connect of the same pair read exactly like the first, so a caller could not tell a
90
135
  # connection made from one that was already there, and a list showing one where two were made
91
136
  # looked like a lost write.
92
- if flags_before >= 0:
137
+ _bound_into(answer, saved)
138
+ if not before.is_empty():
93
139
  answer["alreadyConnected"] = true
94
- if flags_before != GdhRead.as_int(saved_flags[0]):
95
- answer["flagsBefore"] = GdhSceneFile.flag_names(flags_before)
140
+ var flags_before: int = before["flags"]
141
+ if flags_before != saved_flags:
142
+ answer["flagsBefore"] = flag_names(flags_before)
143
+ var binds_before: Variant = _values.serialize_value(before["binds"])
144
+ if JSON.stringify(binds_before) != JSON.stringify(answer.get("binds", [])):
145
+ answer["bindsBefore"] = binds_before
146
+ if before["unbinds"] != saved["unbinds"]:
147
+ answer["unbindsBefore"] = before["unbinds"]
96
148
  return answer
97
149
 
98
150
 
151
+ ## Why [param given_flags] cannot be connected with, or "" when it can: a bit no flag a scene
152
+ ## connects with has. The server checks the type of every argument, and nothing knows the bits.
153
+ static func _unasked(given_flags: int) -> String:
154
+ if given_flags >= 0 and not given_flags & ~GIVEN_FLAGS:
155
+ return ""
156
+ var named: Array[String] = []
157
+ for bit: int in CONNECT_FLAG_NAMES:
158
+ if bit & GIVEN_FLAGS:
159
+ named.append("%s %d" % [CONNECT_FLAG_NAMES[bit], bit])
160
+ return (
161
+ "flags %d holds bits no flag a scene connects with has: they are %s, added together."
162
+ % [given_flags, ", ".join(named)]
163
+ )
164
+
165
+
166
+ ## [param given] as the values [param called] is bound with, each number fitted to the parameter it
167
+ ## lands on. JSON has one kind of number, so 3 arrived as 3.0, the file said [3.0], and a whole
168
+ ## number for an untyped parameter is an int, as GDScript reads the literal 3.
169
+ func _bound_values(
170
+ given: Array, source: Node, signal_name: String, called: Callable, unbinds: int, flags: int
171
+ ) -> Array:
172
+ var passed: int = 0
173
+ for info: Dictionary in source.get_signal_list():
174
+ if str(info.get("name", "")) == signal_name:
175
+ var arguments: Array = info.get("args", [])
176
+ passed = arguments.size()
177
+ if flags & Object.CONNECT_APPEND_SOURCE_OBJECT:
178
+ passed += 1
179
+ passed = maxi(passed - unbinds, 0)
180
+ var parameters: Array = []
181
+ for info: Dictionary in called.get_object().get_method_list():
182
+ if str(info.get("name", "")) == str(called.get_method()):
183
+ parameters = info.get("args", [])
184
+ break
185
+ var values: Array = []
186
+ for at: int in given.size():
187
+ var value: Variant = _values.deserialize_value(given[at])
188
+ var declared: int = TYPE_NIL
189
+ if passed + at < parameters.size():
190
+ var parameter: Dictionary = parameters[passed + at]
191
+ declared = GdhRead.as_int(parameter.get("type", TYPE_NIL))
192
+ if value is float and declared != TYPE_FLOAT:
193
+ var number: float = value
194
+ if is_finite(number) and number == floorf(number) and absf(number) < 9007199254740992.0:
195
+ value = int(number)
196
+ values.append(value)
197
+ return values
198
+
199
+
200
+ ## The connection from [param signal_name] of [param source] to [param method] on [param target],
201
+ ## as {callable, flags, binds, unbinds}, or {} when there is none.
202
+ static func _held(source: Node, signal_name: String, target: Object, method: String) -> Dictionary:
203
+ for conn: Dictionary in source.get_signal_connection_list(signal_name):
204
+ var callable: Callable = conn.get("callable", Callable())
205
+ if callable.get_object() == target and str(callable.get_method()) == method:
206
+ return {
207
+ "callable": callable,
208
+ "flags": GdhRead.as_int(conn.get("flags", 0)),
209
+ "binds": callable.get_bound_arguments(),
210
+ "unbinds": callable.get_unbound_arguments_count(),
211
+ }
212
+ return {}
213
+
214
+
215
+ ## [param held]'s binds and unbinds into [param answer], each only when it has any, as the scene
216
+ ## file writes them.
217
+ func _bound_into(answer: Dictionary, held: Dictionary) -> void:
218
+ var binds: Array = held["binds"]
219
+ if not binds.is_empty():
220
+ answer["binds"] = _values.serialize_value(binds)
221
+ var unbinds: int = held["unbinds"]
222
+ if unbinds > 0:
223
+ answer["unbinds"] = unbinds
224
+
225
+
226
+ ## Each bit set in [param flags], a connection's, by name, and any bits left unnamed as one number.
227
+ static func flag_names(flags: int) -> Array[String]:
228
+ var names: Array[String] = []
229
+ var rest: int = flags
230
+ for bit: int in CONNECT_FLAG_NAMES:
231
+ if flags & bit:
232
+ names.append(CONNECT_FLAG_NAMES[bit])
233
+ rest &= ~bit
234
+ if rest != 0:
235
+ names.append(str(rest))
236
+ return names
237
+
238
+
99
239
  ## The two ends of a connection in [param scene], as [source, target], or [null, why not].
100
240
  func _connection_ends(
101
241
  scene: GdhSceneFile, source_node_path: String, signal_name: String, target_node_path: String
@@ -119,23 +259,17 @@ func _connection_ends(
119
259
  return [source, target]
120
260
 
121
261
 
122
- ## The flags of the persistent connection from [param from]'s [param signal_name] to
123
- ## [param method] on [param to] in the tree under [param root], or -1 when there is none.
124
- func _connection_flags(root: Node, from: String, signal_name: String, to: String, method: String) -> int:
262
+ ## The persistent connection from [param from]'s [param signal_name] to [param method] on [param to]
263
+ ## in the tree under [param root], as [method _held] answers it, or {} when there is none.
264
+ static func _saved(root: Node, from: String, signal_name: String, to: String, method: String) -> Dictionary:
125
265
  var source: Node = root.get_node_or_null(from)
126
266
  var target: Node = root.get_node_or_null(to)
127
267
  if source == null or target == null or not source.has_signal(signal_name):
128
- return -1
129
- for conn: Dictionary in source.get_signal_connection_list(signal_name):
130
- var flags: int = GdhRead.as_int(conn.get("flags", 0))
131
- var callable: Callable = conn.get("callable", Callable())
132
- if (
133
- flags & Object.CONNECT_PERSIST
134
- and callable.get_object() == target
135
- and str(callable.get_method()) == method
136
- ):
137
- return flags
138
- return -1
268
+ return {}
269
+ var held: Dictionary = _held(source, signal_name, target, method)
270
+ if held.is_empty() or not GdhRead.as_int(held["flags"]) & Object.CONNECT_PERSIST:
271
+ return {}
272
+ return held
139
273
 
140
274
 
141
275
  ## Why what [param signal_name] of [param source] passes cannot reach [param callable], or "" when it
@@ -264,29 +398,42 @@ func disconnect_signal(args: Dictionary) -> Dictionary:
264
398
  var to: String = scene.path_of(target)
265
399
  # A connection that was never there saved the scene unchanged and answered as removed, so a
266
400
  # typo in either name read as success.
267
- if _connection_flags(scene.root, from, signal_name, to, method_name) < 0:
401
+ var held: Dictionary = _saved(scene.root, from, signal_name, to, method_name)
402
+ if held.is_empty():
403
+ # The root's path is ".", which before ".method" read as "..method".
404
+ var said_from: String = str(source.name) if source == scene.root else from
405
+ var said_to: String = str(target.name) if target == scene.root else to
268
406
  return scene.refuse(
269
- "%s has no connection from %s.%s to %s.%s." % [scene_path, from, signal_name, to, method_name]
407
+ (
408
+ "%s has no connection from %s.%s to %s.%s."
409
+ % [scene_path, said_from, signal_name, said_to, method_name]
410
+ )
270
411
  )
271
- source.disconnect(signal_name, Callable(target, method_name))
412
+ var callable: Callable = held["callable"]
413
+ source.disconnect(signal_name, callable)
272
414
 
273
415
  var written: Dictionary = scene.write(
274
- func(saved: Node) -> String:
275
- if _connection_flags(saved, from, signal_name, to, method_name) >= 0:
416
+ func(loaded: Node) -> String:
417
+ if not _saved(loaded, from, signal_name, to, method_name).is_empty():
276
418
  return "the connection would still be in the scene when it loads."
277
419
  return ""
278
420
  )
279
421
  if not written.is_empty():
280
422
  return written
281
423
 
282
- return {
424
+ var removed_flags: int = held["flags"]
425
+ var answer: Dictionary = {
283
426
  "ok": true,
284
427
  "removed": true,
285
428
  "sourceNodePath": from,
286
429
  "signalName": signal_name,
287
430
  "targetNodePath": to,
288
- "methodName": method_name
431
+ "methodName": method_name,
432
+ "flags": removed_flags,
433
+ "flagNames": flag_names(removed_flags),
289
434
  }
435
+ _bound_into(answer, held)
436
+ return answer
290
437
 
291
438
 
292
439
  func list_connections(args: Dictionary) -> Dictionary:
@@ -338,12 +485,17 @@ func list_connections(args: Dictionary) -> Dictionary:
338
485
  "targetNodePath": target_path,
339
486
  "methodName": str(callable.get_method()),
340
487
  "flags": flags,
341
- "flagNames": GdhSceneFile.flag_names(flags),
488
+ "flagNames": flag_names(flags),
342
489
  # False for a connection an instanced scene makes inside itself, which only that
343
490
  # scene can change.
344
491
  "editable": editable,
345
492
  "methodFound": target_obj != null and target_obj.has_method(callable.get_method()),
346
493
  }
494
+ var bound: Dictionary = {
495
+ "binds": callable.get_bound_arguments(),
496
+ "unbinds": callable.get_unbound_arguments_count(),
497
+ }
498
+ _bound_into(listed, bound)
347
499
  var misfit: String = _misfit(node, signal_name, callable, flags)
348
500
  if not misfit.is_empty():
349
501
  listed["misfit"] = misfit