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,13 +1,11 @@
|
|
|
1
1
|
extends RefCounted
|
|
2
|
-
|
|
3
|
-
##
|
|
4
|
-
##
|
|
5
|
-
## stores, which runs no script of the scene's, so a read answers the same either way.
|
|
2
|
+
## Animations and state machines read off their nodes: by the editor's tools from the scene it
|
|
3
|
+
## holds, and by a headless engine with none connected from a bare node of the class given what the
|
|
4
|
+
## file stores, which runs no script of the scene's, so a read answers the same either way.
|
|
6
5
|
|
|
7
6
|
const GdhFileReads = preload("file_reads.gd")
|
|
8
7
|
const GdhRead = preload("reading.gd")
|
|
9
8
|
const GdhSerialisation = preload("serialisation.gd")
|
|
10
|
-
|
|
11
9
|
const LOOP_MODE_NAMES: Dictionary[int, String] = {
|
|
12
10
|
Animation.LOOP_NONE: "none",
|
|
13
11
|
Animation.LOOP_LINEAR: "linear",
|
|
@@ -37,7 +35,9 @@ const ADVANCE_MODE_NAMES: Dictionary[int, String] = {
|
|
|
37
35
|
|
|
38
36
|
|
|
39
37
|
## [method animations] of the AnimationPlayer at [param player_path] in the file [param scene_path].
|
|
40
|
-
static func scene_animations(
|
|
38
|
+
static func scene_animations(
|
|
39
|
+
scene_path: String, player_path: String, animation_name: String
|
|
40
|
+
) -> Dictionary:
|
|
41
41
|
var made: Array = _bare(scene_path, player_path, "AnimationPlayer")
|
|
42
42
|
if made[0] == null:
|
|
43
43
|
return made[1]
|
|
@@ -48,7 +48,9 @@ static func scene_animations(scene_path: String, player_path: String, animation_
|
|
|
48
48
|
|
|
49
49
|
|
|
50
50
|
## [method states] of the AnimationTree at [param tree_path] in the file [param scene_path].
|
|
51
|
-
static func scene_states(
|
|
51
|
+
static func scene_states(
|
|
52
|
+
scene_path: String, tree_path: String, state_machine_path: String
|
|
53
|
+
) -> Dictionary:
|
|
52
54
|
var made: Array = _bare(scene_path, tree_path, "AnimationTree")
|
|
53
55
|
if made[0] == null:
|
|
54
56
|
return made[1]
|
|
@@ -64,7 +66,13 @@ static func scene_states(scene_path: String, tree_path: String, state_machine_pa
|
|
|
64
66
|
player_path = "."
|
|
65
67
|
var found: Array = _bare(scene_path, player_path, "AnimationPlayer")
|
|
66
68
|
player = found[0]
|
|
67
|
-
var
|
|
69
|
+
var reached: Node = null
|
|
70
|
+
if player == null and not player_path.is_empty():
|
|
71
|
+
reached = _bare(scene_path, player_path, "Node")[0]
|
|
72
|
+
var unplayed: String = no_player(str(tree.anim_player), reached)
|
|
73
|
+
if reached != null:
|
|
74
|
+
reached.free()
|
|
75
|
+
var answer: Dictionary = states(tree, state_machine_path, player, player_path, unplayed)
|
|
68
76
|
tree.free()
|
|
69
77
|
if player != null:
|
|
70
78
|
player.free()
|
|
@@ -98,9 +106,9 @@ static func animations(player: AnimationPlayer, animation_name: String) -> Dicti
|
|
|
98
106
|
summary["animationName"] = called
|
|
99
107
|
summary["tracks"] = listed_one.get_track_count()
|
|
100
108
|
listed.append(summary)
|
|
101
|
-
return {"ok": true, "animations": listed}
|
|
109
|
+
return { "ok": true, "animations": listed }
|
|
102
110
|
if not player.has_animation(StringName(animation_name)):
|
|
103
|
-
return {"ok": false, "error": no_animation(player, str(player.name), animation_name)}
|
|
111
|
+
return { "ok": false, "error": no_animation(player, str(player.name), animation_name) }
|
|
104
112
|
var anim: Animation = player.get_animation(StringName(animation_name))
|
|
105
113
|
var answer: Dictionary = _summary(anim)
|
|
106
114
|
answer["ok"] = true
|
|
@@ -152,7 +160,8 @@ static func _track(anim: Animation, track: int, values: GdhSerialisation) -> Dic
|
|
|
152
160
|
var kind: int = anim.track_get_type(track)
|
|
153
161
|
var path: NodePath = anim.track_get_path(track)
|
|
154
162
|
var shaped: Dictionary = {
|
|
155
|
-
"type": TRACK_TYPE_NAMES.get(kind, str(kind)),
|
|
163
|
+
"type": TRACK_TYPE_NAMES.get(kind, str(kind)),
|
|
164
|
+
"nodePath": str(path.get_concatenated_names()),
|
|
156
165
|
}
|
|
157
166
|
var property: String = str(path.get_concatenated_subnames())
|
|
158
167
|
if not property.is_empty():
|
|
@@ -160,7 +169,7 @@ static func _track(anim: Animation, track: int, values: GdhSerialisation) -> Dic
|
|
|
160
169
|
var keyframes: Array[Dictionary] = []
|
|
161
170
|
var methods: Dictionary = {}
|
|
162
171
|
for key: int in anim.track_get_key_count(track):
|
|
163
|
-
var keyframe: Dictionary = {"time": held_seconds(anim.track_get_key_time(track, key))}
|
|
172
|
+
var keyframe: Dictionary = { "time": held_seconds(anim.track_get_key_time(track, key)) }
|
|
164
173
|
if kind == Animation.TYPE_METHOD:
|
|
165
174
|
var method: String = str(anim.method_track_get_name(track, key))
|
|
166
175
|
methods[method] = true
|
|
@@ -182,7 +191,9 @@ static func _track(anim: Animation, track: int, values: GdhSerialisation) -> Dic
|
|
|
182
191
|
## The state machine at a slash-separated path of nested state machine names in [param tree], or
|
|
183
192
|
## null when any step of the path is not one. Every node in a tree of state machines is a state
|
|
184
193
|
## machine itself, so the walk asks each step for the child by name and refuses anything else.
|
|
185
|
-
static func state_machine(
|
|
194
|
+
static func state_machine(
|
|
195
|
+
tree: AnimationTree, state_machine_path: String
|
|
196
|
+
) -> AnimationNodeStateMachine:
|
|
186
197
|
if tree == null:
|
|
187
198
|
return null
|
|
188
199
|
var current: AnimationNodeStateMachine = tree.tree_root as AnimationNodeStateMachine
|
|
@@ -201,8 +212,8 @@ static func no_machine(tree: AnimationTree, state_machine_path: String) -> Strin
|
|
|
201
212
|
if tree == null or tree.tree_root == null:
|
|
202
213
|
return (
|
|
203
214
|
"The AnimationTree has no tree_root. Give it one with scene_node set: tree_root "
|
|
204
|
-
+ '{"_type": "AnimationNodeStateMachine"} builds one into the scene, and the res://
|
|
205
|
-
+ "of one made with resource_edit create keeps it in a file of its own."
|
|
215
|
+
+ '{"_type": "AnimationNodeStateMachine"} builds one into the scene, and the res:// '
|
|
216
|
+
+ "path of one made with resource_edit create keeps it in a file of its own."
|
|
206
217
|
)
|
|
207
218
|
var current: AnimationNodeStateMachine = tree.tree_root as AnimationNodeStateMachine
|
|
208
219
|
if current == null:
|
|
@@ -236,42 +247,74 @@ static func no_machine(tree: AnimationTree, state_machine_path: String) -> Strin
|
|
|
236
247
|
return "No state machine at " + state_machine_path
|
|
237
248
|
|
|
238
249
|
|
|
250
|
+
## Why no state finds its animation when [param anim_player], an AnimationTree's path to its player,
|
|
251
|
+
## reaches no AnimationPlayer: [param reached] is the node it reaches instead, null for none.
|
|
252
|
+
static func no_player(anim_player: String, reached: Node) -> String:
|
|
253
|
+
var until: String = "so no state plays anything until scene_node set points anim_player at one."
|
|
254
|
+
if anim_player.is_empty():
|
|
255
|
+
return "The AnimationTree has no anim_player, " + until
|
|
256
|
+
if reached == null:
|
|
257
|
+
return "The AnimationTree's anim_player %s reaches no node, %s" % [anim_player, until]
|
|
258
|
+
return (
|
|
259
|
+
"The AnimationTree's anim_player %s reaches %s, a %s rather than an AnimationPlayer, %s"
|
|
260
|
+
% [anim_player, reached.name, reached.get_class(), until]
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
|
|
239
264
|
## Why a state playing [param animation_name] from [param player], at [param player_path] from the
|
|
240
265
|
## scene root as playerNodePath takes it, plays nothing yet. anim_player is relative to the tree
|
|
241
266
|
## ("../Anim"), which no argument takes.
|
|
242
|
-
static func missing_animation(
|
|
267
|
+
static func missing_animation(
|
|
268
|
+
player: AnimationPlayer, player_path: String, animation_name: String
|
|
269
|
+
) -> String:
|
|
243
270
|
var said: String = (
|
|
244
|
-
|
|
271
|
+
(
|
|
272
|
+
"The AnimationPlayer at %s has no animation %s yet, so the state plays nothing until "
|
|
273
|
+
+ "it has one."
|
|
274
|
+
)
|
|
245
275
|
% [player_path, animation_name]
|
|
246
276
|
)
|
|
247
277
|
var close: PackedStringArray = GdhRead.nearest(animation_name, player.get_animation_list())
|
|
248
278
|
if close.is_empty():
|
|
249
279
|
return said
|
|
250
280
|
return (
|
|
251
|
-
said
|
|
281
|
+
said
|
|
282
|
+
+ (
|
|
283
|
+
" The nearest %s %s."
|
|
284
|
+
% ["it has is" if close.size() == 1 else "it has are", ", ".join(close)]
|
|
285
|
+
)
|
|
252
286
|
)
|
|
253
287
|
|
|
254
288
|
|
|
255
289
|
## The states and transitions of the state machine at [param state_machine_path] in [param tree].
|
|
256
290
|
## A state playing an animation says whether [param player], at [param player_path], has it, as
|
|
257
|
-
## add_state answers: a state naming one it lacks plays nothing, and says so only in a log.
|
|
291
|
+
## add_state answers: a state naming one it lacks plays nothing, and says so only in a log. With no
|
|
292
|
+
## player, each says [param unplayed], what [method no_player] made of the tree's path.
|
|
258
293
|
static func states(
|
|
259
|
-
tree: AnimationTree,
|
|
294
|
+
tree: AnimationTree,
|
|
295
|
+
state_machine_path: String,
|
|
296
|
+
player: AnimationPlayer,
|
|
297
|
+
player_path: String,
|
|
298
|
+
unplayed: String
|
|
260
299
|
) -> Dictionary:
|
|
261
300
|
var sm: AnimationNodeStateMachine = state_machine(tree, state_machine_path)
|
|
262
301
|
if sm == null:
|
|
263
|
-
return {"ok": false, "error": no_machine(tree, state_machine_path)}
|
|
302
|
+
return { "ok": false, "error": no_machine(tree, state_machine_path) }
|
|
264
303
|
var listed: Array[Dictionary] = []
|
|
265
304
|
for state: StringName in sm.get_node_list():
|
|
266
305
|
var node: AnimationNode = sm.get_node(state)
|
|
267
|
-
var entry: Dictionary = {"stateName": str(state), "type": node.get_class()}
|
|
306
|
+
var entry: Dictionary = { "stateName": str(state), "type": node.get_class() }
|
|
268
307
|
if node is AnimationNodeAnimation:
|
|
269
308
|
var plays: AnimationNodeAnimation = node
|
|
270
309
|
entry["animationName"] = str(plays.animation)
|
|
271
310
|
var found: bool = player != null and player.has_animation(plays.animation)
|
|
272
311
|
entry["animationFound"] = found
|
|
273
|
-
if player
|
|
274
|
-
entry["animationMissing"] =
|
|
312
|
+
if player == null:
|
|
313
|
+
entry["animationMissing"] = unplayed
|
|
314
|
+
elif not found:
|
|
315
|
+
entry["animationMissing"] = missing_animation(
|
|
316
|
+
player, player_path, str(plays.animation)
|
|
317
|
+
)
|
|
275
318
|
listed.append(entry)
|
|
276
319
|
var transitions: Array[Dictionary] = []
|
|
277
320
|
for index: int in sm.get_transition_count():
|
|
@@ -279,8 +322,10 @@ static func states(
|
|
|
279
322
|
var shaped: Dictionary = {
|
|
280
323
|
"from": str(sm.get_transition_from(index)),
|
|
281
324
|
"to": str(sm.get_transition_to(index)),
|
|
282
|
-
"transitionType":
|
|
283
|
-
|
|
325
|
+
"transitionType":
|
|
326
|
+
SWITCH_MODE_NAMES.get(transition.switch_mode, str(transition.switch_mode)),
|
|
327
|
+
"advanceMode":
|
|
328
|
+
ADVANCE_MODE_NAMES.get(transition.advance_mode, str(transition.advance_mode)),
|
|
284
329
|
}
|
|
285
330
|
var condition: String = str(transition.advance_condition)
|
|
286
331
|
if not condition.is_empty():
|
|
@@ -290,9 +335,9 @@ static func states(
|
|
|
290
335
|
if not condition.is_empty() and transition.advance_mode != transition.ADVANCE_MODE_AUTO:
|
|
291
336
|
shaped["conditionNote"] = (
|
|
292
337
|
(
|
|
293
|
-
"advanceMode is %s, so %s never advances it: the engine checks a condition
|
|
294
|
-
+ " auto transition%s. disconnect_states and connect_states again
|
|
295
|
-
+ " write it as auto"
|
|
338
|
+
"advanceMode is %s, so %s never advances it: the engine checks a condition "
|
|
339
|
+
+ "only on an auto transition%s. disconnect_states and connect_states again "
|
|
340
|
+
+ "with the condition write it as auto"
|
|
296
341
|
)
|
|
297
342
|
% [
|
|
298
343
|
shaped["advanceMode"],
|
|
@@ -301,16 +346,16 @@ static func states(
|
|
|
301
346
|
", and travel() still takes this one"
|
|
302
347
|
if transition.advance_mode == transition.ADVANCE_MODE_ENABLED
|
|
303
348
|
else ""
|
|
304
|
-
)
|
|
349
|
+
),
|
|
305
350
|
]
|
|
306
351
|
)
|
|
307
352
|
transitions.append(shaped)
|
|
308
|
-
return {"ok": true, "states": listed, "transitions": transitions}
|
|
353
|
+
return { "ok": true, "states": listed, "transitions": transitions }
|
|
309
354
|
|
|
310
355
|
|
|
311
356
|
## The states of the AnimationTrees under [param root] that play [param animation_name] from
|
|
312
|
-
## [param player], as [[node, {animTreePath, state}]], a state named by the path of the nodes
|
|
313
|
-
## to it.
|
|
357
|
+
## [param player], as [[node, {animTreePath, state}]], a state named by the path of the nodes
|
|
358
|
+
## leading to it.
|
|
314
359
|
static func states_playing(root: Node, player: AnimationPlayer, animation_name: String) -> Array:
|
|
315
360
|
var found: Array = []
|
|
316
361
|
var trees: Array[Node] = root.find_children("*", "AnimationTree", true, false)
|
|
@@ -331,7 +376,9 @@ static func _collect_playing(
|
|
|
331
376
|
if node is AnimationNodeAnimation:
|
|
332
377
|
var plays: AnimationNodeAnimation = node
|
|
333
378
|
if plays.animation == StringName(animation_name):
|
|
334
|
-
found.append(
|
|
379
|
+
found.append(
|
|
380
|
+
[plays, { "animTreePath": tree_path, "state": "." if at.is_empty() else at }]
|
|
381
|
+
)
|
|
335
382
|
return
|
|
336
383
|
var children: Dictionary = {}
|
|
337
384
|
if node is AnimationNodeStateMachine:
|
|
@@ -352,4 +399,6 @@ static func _collect_playing(
|
|
|
352
399
|
children[str(point)] = plane.get_blend_point_node(point)
|
|
353
400
|
for step: String in children:
|
|
354
401
|
var child: AnimationNode = children[step]
|
|
355
|
-
_collect_playing(
|
|
402
|
+
_collect_playing(
|
|
403
|
+
child, step if at.is_empty() else at + "/" + step, tree_path, animation_name, found
|
|
404
|
+
)
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
@tool
|
|
2
2
|
extends Node
|
|
3
|
-
|
|
4
3
|
## The editor's end of the bridge: one WebSocket to the gdharness server, reconnected on its
|
|
5
4
|
## own when it drops, carrying tool requests in and their results out.
|
|
6
5
|
|
|
@@ -9,7 +8,6 @@ signal disconnected
|
|
|
9
8
|
signal tool_requested(request_id: String, tool_name: String, args: Dictionary)
|
|
10
9
|
|
|
11
10
|
const GdhRead = preload("reading.gd")
|
|
12
|
-
|
|
13
11
|
const DEFAULT_URL: String = "ws://127.0.0.1:6505/godot"
|
|
14
12
|
## Written beside the addon by the install, so it names the version this copy came from.
|
|
15
13
|
const VERSION_MARKER: String = "res://addons/gdharness_editor/.gdharness-version"
|
|
@@ -24,7 +22,6 @@ const EDITORS_RECORDED: String = "res://.godot/gdharness-editors"
|
|
|
24
22
|
const EDITORS_RECORDED_PROTOCOL: int = 1
|
|
25
23
|
const RECONNECT_DELAY: float = 3.0
|
|
26
24
|
const MAX_RECONNECT_DELAY: float = 30.0
|
|
27
|
-
|
|
28
25
|
## What a server closes with when the editor that said hello belongs to another project. Kept in
|
|
29
26
|
## step with `OTHER_PROJECT_CLOSE_CODE` in src/godot-bridge.ts.
|
|
30
27
|
const ELSEWHERE_CLOSE_CODE: int = 4001
|
|
@@ -32,7 +29,6 @@ const ELSEWHERE_CLOSE_CODE: int = 4001
|
|
|
32
29
|
## rather than an editor: dialling in again changes nothing for the rest of the run. Kept in step
|
|
33
30
|
## with `ONE_SHOT_CLOSE_CODE` in src/godot-bridge.ts.
|
|
34
31
|
const ONE_SHOT_CLOSE_CODE: int = 4002
|
|
35
|
-
|
|
36
32
|
## Where Godot keeps the three ports an editor serves. None of them is per editor: the settings
|
|
37
33
|
## file is one for every editor of this engine version on the machine, so two open at once want the
|
|
38
34
|
## same three and the second one binds nothing. Read here and never written, for the reason
|
|
@@ -58,7 +54,6 @@ const OPENED_BY_A_SERVER: String = "GDHARNESS_OPENED_BY_A_SERVER"
|
|
|
58
54
|
## tells the two apart when both are announced at once. Kept in step with `EDITOR_PID_VARIABLE`
|
|
59
55
|
## in the runtime autoload.
|
|
60
56
|
const EDITOR_PID_VARIABLE: String = "GDHARNESS_EDITOR_PID"
|
|
61
|
-
|
|
62
57
|
## How long one attempt is given before the address is called a bad one.
|
|
63
58
|
##
|
|
64
59
|
## A socket pointed at a port nothing holds gives up by itself, in thirty seconds on Windows. One
|
|
@@ -67,7 +62,6 @@ const EDITOR_PID_VARIABLE: String = "GDHARNESS_EDITOR_PID"
|
|
|
67
62
|
## timeout of its own in 4.7. A leftover announcement can be either, and a port that has been
|
|
68
63
|
## reused by some other program is the second.
|
|
69
64
|
const CONNECT_TIMEOUT: float = 10.0
|
|
70
|
-
|
|
71
65
|
## How often the announcement is read again while connected, so a newer server is moved to
|
|
72
66
|
## rather than waited for. A harness reconnect leaves the server it replaced running and holding
|
|
73
67
|
## the old port, and an editor with no reason to look elsewhere stayed on it for the session.
|
|
@@ -75,13 +69,11 @@ const FOLLOW_INTERVAL: float = 5.0
|
|
|
75
69
|
|
|
76
70
|
var socket: WebSocketPeer = WebSocketPeer.new()
|
|
77
71
|
var server_url: String = DEFAULT_URL
|
|
78
|
-
|
|
79
72
|
## What this copy was when it loaded, which is what the editor is running until it is restarted.
|
|
80
73
|
## See [method _loaded_version] for why it is held rather than read when it is wanted.
|
|
81
74
|
var version_at_load: String = ""
|
|
82
75
|
## The digest of the editor addon code this copy loaded, held for the same reason.
|
|
83
76
|
var digest_at_load: String = ""
|
|
84
|
-
|
|
85
77
|
var _is_connected: bool = false
|
|
86
78
|
var _reconnect_timer: Timer
|
|
87
79
|
var _current_reconnect_delay: float = RECONNECT_DELAY
|
|
@@ -89,27 +81,23 @@ var _current_reconnect_delay: float = RECONNECT_DELAY
|
|
|
89
81
|
var _should_reconnect: bool = false
|
|
90
82
|
var _project_path: String
|
|
91
83
|
var _initialized: bool = false
|
|
92
|
-
|
|
93
84
|
## Whether the address came from a caller rather than from the project, in which case it is not
|
|
94
85
|
## this node's to change.
|
|
95
86
|
var _named_by_caller: bool = false
|
|
96
87
|
var _since_looked: float = 0.0
|
|
97
|
-
|
|
98
88
|
## The announced address that did not answer, so the fallback gets a turn. Cleared the moment
|
|
99
89
|
## anything connects or the project names a different one.
|
|
100
90
|
var _refused_url: String = ""
|
|
101
91
|
var _tried_announced: bool = false
|
|
102
|
-
|
|
103
92
|
## The address this editor has already been told is another project's, so a reconnect loop says it
|
|
104
93
|
## once rather than every few seconds for the rest of the session.
|
|
105
94
|
var _told_about_url: String = ""
|
|
106
95
|
var _connecting_for: float = 0.0
|
|
107
|
-
|
|
108
96
|
## Whether this editor was started by another editor, which is Godot's own restart and the project
|
|
109
97
|
## manager opening a project: the new editor is the old one's child and has its environment, with
|
|
110
98
|
## the variable this addon sets as it loads still naming the old one. Its arguments are gone, since
|
|
111
|
-
## the engine consumes `--lsp-port`, `--dap-port` and `--log-file` and hands none of them back, so
|
|
112
|
-
## ports and the marker a server put in that environment describe the editor before this one.
|
|
99
|
+
## the engine consumes `--lsp-port`, `--dap-port` and `--log-file` and hands none of them back, so
|
|
100
|
+
## the ports and the marker a server put in that environment describe the editor before this one.
|
|
113
101
|
var _started_by_an_editor: bool = false
|
|
114
102
|
|
|
115
103
|
|
|
@@ -140,45 +128,6 @@ func _exit_tree() -> void:
|
|
|
140
128
|
var _removed: Error = DirAccess.remove_absolute(record)
|
|
141
129
|
|
|
142
130
|
|
|
143
|
-
## The file saying which ports this editor serves, named by its process id, so `gdharness lsp` can
|
|
144
|
-
## find its language server when a server moved it off the default. One for each process rather
|
|
145
|
-
## than one for the project, because a gate's `--import` loads this addon too, and its plugin is
|
|
146
|
-
## shown none of the options that would say so; the reader tells the two apart by command line.
|
|
147
|
-
## Kept in step with `editorRecordsIn` in src/lsp-bridge.ts.
|
|
148
|
-
static func _ports_record() -> String:
|
|
149
|
-
return ProjectSettings.globalize_path(EDITORS_RECORDED).path_join("%d.json" % OS.get_process_id())
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
func _record_ports() -> void:
|
|
153
|
-
if not Engine.is_editor_hint():
|
|
154
|
-
return
|
|
155
|
-
var made: Error = DirAccess.make_dir_recursive_absolute(ProjectSettings.globalize_path(EDITORS_RECORDED))
|
|
156
|
-
if made != OK and made != ERR_ALREADY_EXISTS:
|
|
157
|
-
return
|
|
158
|
-
var file: FileAccess = FileAccess.open(_ports_record(), FileAccess.WRITE)
|
|
159
|
-
if file == null:
|
|
160
|
-
return
|
|
161
|
-
var written: bool = (
|
|
162
|
-
file
|
|
163
|
-
. store_string(
|
|
164
|
-
(
|
|
165
|
-
JSON
|
|
166
|
-
. stringify(
|
|
167
|
-
{
|
|
168
|
-
"protocol": EDITORS_RECORDED_PROTOCOL,
|
|
169
|
-
"pid": OS.get_process_id(),
|
|
170
|
-
"lsp_port": _serves(LSP_ASKED, LSP_SETTING),
|
|
171
|
-
"dap_port": _serves(DAP_ASKED, DAP_SETTING),
|
|
172
|
-
"written_at_ms": int(Time.get_unix_time_from_system() * 1000.0),
|
|
173
|
-
}
|
|
174
|
-
)
|
|
175
|
-
)
|
|
176
|
-
)
|
|
177
|
-
)
|
|
178
|
-
if not written:
|
|
179
|
-
push_warning("gdharness: could not write " + _ports_record())
|
|
180
|
-
|
|
181
|
-
|
|
182
131
|
func _process(_delta: float) -> void:
|
|
183
132
|
if not _initialized:
|
|
184
133
|
return
|
|
@@ -220,6 +169,49 @@ func _process(_delta: float) -> void:
|
|
|
220
169
|
_handle_disconnect()
|
|
221
170
|
|
|
222
171
|
|
|
172
|
+
## The file saying which ports this editor serves, named by its process id, so `gdharness lsp` can
|
|
173
|
+
## find its language server when a server moved it off the default. One for each process rather
|
|
174
|
+
## than one for the project, because a gate's `--import` loads this addon too, and its plugin is
|
|
175
|
+
## shown none of the options that would say so; the reader tells the two apart by command line.
|
|
176
|
+
## Kept in step with `editorRecordsIn` in src/lsp-bridge.ts.
|
|
177
|
+
static func _ports_record() -> String:
|
|
178
|
+
return ProjectSettings.globalize_path(EDITORS_RECORDED).path_join(
|
|
179
|
+
"%d.json" % OS.get_process_id()
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
func _record_ports() -> void:
|
|
184
|
+
if not Engine.is_editor_hint():
|
|
185
|
+
return
|
|
186
|
+
var made: Error = DirAccess.make_dir_recursive_absolute(
|
|
187
|
+
ProjectSettings.globalize_path(EDITORS_RECORDED)
|
|
188
|
+
)
|
|
189
|
+
if made != OK and made != ERR_ALREADY_EXISTS:
|
|
190
|
+
return
|
|
191
|
+
var file: FileAccess = FileAccess.open(_ports_record(), FileAccess.WRITE)
|
|
192
|
+
if file == null:
|
|
193
|
+
return
|
|
194
|
+
var written: bool = (
|
|
195
|
+
file
|
|
196
|
+
. store_string(
|
|
197
|
+
(
|
|
198
|
+
JSON
|
|
199
|
+
. stringify(
|
|
200
|
+
{
|
|
201
|
+
"protocol": EDITORS_RECORDED_PROTOCOL,
|
|
202
|
+
"pid": OS.get_process_id(),
|
|
203
|
+
"lsp_port": _serves(LSP_ASKED, LSP_SETTING),
|
|
204
|
+
"dap_port": _serves(DAP_ASKED, DAP_SETTING),
|
|
205
|
+
"written_at_ms": int(Time.get_unix_time_from_system() * 1000.0),
|
|
206
|
+
}
|
|
207
|
+
)
|
|
208
|
+
)
|
|
209
|
+
)
|
|
210
|
+
)
|
|
211
|
+
if not written:
|
|
212
|
+
push_warning("gdharness: could not write " + _ports_record())
|
|
213
|
+
|
|
214
|
+
|
|
223
215
|
## Moves to the server the project now names, when that is not the one this is talking to.
|
|
224
216
|
##
|
|
225
217
|
## A harness reconnect leaves the server it replaced running, still holding the port it bound and
|
|
@@ -371,7 +363,7 @@ func _handle_connect() -> void:
|
|
|
371
363
|
# as itself: the engine hands back none of the arguments it was given, so the arguments
|
|
372
364
|
# have to come from whoever wrote them. See `restart_editor` in play_tools.gd.
|
|
373
365
|
"opened_by_a_server": _opened_by_a_server() and not _started_by_an_editor,
|
|
374
|
-
"started_by_an_editor": _started_by_an_editor
|
|
366
|
+
"started_by_an_editor": _started_by_an_editor,
|
|
375
367
|
}
|
|
376
368
|
)
|
|
377
369
|
|
|
@@ -556,7 +548,7 @@ func _handle_message(json_string: String) -> void:
|
|
|
556
548
|
|
|
557
549
|
match message.get("type", ""):
|
|
558
550
|
"ping":
|
|
559
|
-
_send_message({"type": "pong"})
|
|
551
|
+
_send_message({ "type": "pong" })
|
|
560
552
|
|
|
561
553
|
"tool_invoke":
|
|
562
554
|
var request_id: String = message.get("id", "")
|
|
@@ -577,13 +569,16 @@ func send_busy(request_id: String, tool_name: String) -> void:
|
|
|
577
569
|
"id": request_id,
|
|
578
570
|
"success": false,
|
|
579
571
|
"busy": true,
|
|
580
|
-
"error":
|
|
572
|
+
"error":
|
|
573
|
+
"The editor is scanning or importing its files, so %s was not run." % tool_name,
|
|
581
574
|
}
|
|
582
575
|
)
|
|
583
576
|
|
|
584
577
|
|
|
585
|
-
func send_tool_result(
|
|
586
|
-
|
|
578
|
+
func send_tool_result(
|
|
579
|
+
request_id: String, success: bool, result: Variant = null, error: String = ""
|
|
580
|
+
) -> void:
|
|
581
|
+
var response: Dictionary = { "type": "tool_result", "id": request_id, "success": success }
|
|
587
582
|
|
|
588
583
|
if success:
|
|
589
584
|
response["result"] = result
|