gdharness 1.2.30 → 1.2.32
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 +7197 -6159
- package/build/godot/addons/gdharness_editor/bridge_client.gd +14 -0
- package/build/godot/addons/gdharness_editor/file_reads.gd +237 -0
- package/build/godot/addons/gdharness_editor/file_reads.gd.uid +1 -0
- package/build/godot/addons/gdharness_editor/plugin.gd +27 -0
- package/build/godot/addons/gdharness_editor/property_values.gd +80 -54
- package/build/godot/addons/gdharness_editor/scene_file.gd +38 -1
- package/build/godot/addons/gdharness_editor/serialisation.gd +46 -0
- package/build/godot/addons/gdharness_editor/tool_executor.gd +1 -0
- package/build/godot/addons/gdharness_editor/tools/class_tools.gd +22 -0
- package/build/godot/addons/gdharness_editor/tools/filesystem_tools.gd +64 -1
- package/build/godot/addons/gdharness_editor/tools/resource_tools.gd +82 -44
- package/build/godot/addons/gdharness_editor/tools/scene_placing.gd +178 -0
- package/build/godot/addons/gdharness_editor/tools/scene_placing.gd.uid +1 -0
- package/build/godot/addons/gdharness_editor/tools/scene_tools.gd +73 -124
- package/build/godot/addons/gdharness_runtime/runtime_autoload.gd +39 -0
- package/build/godot/addons/gdharness_runtime/runtime_hover.gd +285 -0
- package/build/godot/addons/gdharness_runtime/runtime_hover.gd.uid +1 -0
- package/build/godot/addons/gdharness_runtime/runtime_input.gd +151 -106
- package/build/godot/addons/gdharness_runtime/runtime_mouse.gd +112 -0
- package/build/godot/addons/gdharness_runtime/runtime_mouse.gd.uid +1 -0
- package/build/godot/addons/gdharness_runtime/runtime_paths.gd +5 -4
- package/build/godot/addons/gdharness_runtime/runtime_values.gd +1 -47
- package/build/godot/addons/gdharness_runtime/serialisation.gd +46 -0
- package/build/godot/operations/audio_buses.gd +14 -1
- package/build/godot/operations/class_cache.gd +146 -35
- package/build/godot/operations/classdb_queries.gd +19 -12
- package/build/godot/operations/file_reads.gd +237 -0
- package/build/godot/operations/gdscript_authoring.gd +136 -19
- package/build/godot/operations/godot_operations.gd +24 -0
- package/build/godot/operations/project_config.gd +16 -6
- package/build/godot/operations/property_values.gd +767 -0
- package/build/godot/operations/rename_checks.gd +5 -2
- package/build/godot/operations/serialisation.gd +46 -0
- package/build/index.js +4896 -3872
- package/build/keeper.js +184 -71
- package/package.json +1 -1
|
@@ -3,8 +3,11 @@ extends Node
|
|
|
3
3
|
|
|
4
4
|
## Scenes and their nodes, edited in the open editor and read back from what it holds.
|
|
5
5
|
|
|
6
|
+
const FileReads = preload("../file_reads.gd")
|
|
6
7
|
const Read = preload("../reading.gd")
|
|
8
|
+
const Placing = preload("scene_placing.gd")
|
|
7
9
|
const PropertyValues = preload("../property_values.gd")
|
|
10
|
+
const ResourceTools = preload("resource_tools.gd")
|
|
8
11
|
const SceneFile = preload("../scene_file.gd")
|
|
9
12
|
const Serialisation = preload("../serialisation.gd")
|
|
10
13
|
|
|
@@ -101,8 +104,8 @@ func _kept(scene: SceneFile, saved: Node, node: Node, names: Array) -> String:
|
|
|
101
104
|
return "%s is not in the scene as it would be saved." % at
|
|
102
105
|
for key: Variant in names:
|
|
103
106
|
var property: String = str(key)
|
|
104
|
-
var loaded: Variant =
|
|
105
|
-
if not scene.kept(saved, loaded,
|
|
107
|
+
var loaded: Variant = _properties.read_at(copy, property)
|
|
108
|
+
if not scene.kept(saved, loaded, _properties.read_at(node, property)):
|
|
106
109
|
return (
|
|
107
110
|
(
|
|
108
111
|
"%s.%s would load from the file as %s: the scene does not keep it, because the property "
|
|
@@ -113,81 +116,6 @@ func _kept(scene: SceneFile, saved: Node, node: Node, names: Array) -> String:
|
|
|
113
116
|
return ""
|
|
114
117
|
|
|
115
118
|
|
|
116
|
-
## Owns [param node] and the nodes under it to [param root], except those an instanced scene
|
|
117
|
-
## inside it owns: giving those to the root made the pack write the instance's own nodes into this
|
|
118
|
-
## scene as overrides.
|
|
119
|
-
func _copy(source: Node, root: Node, copies: Dictionary) -> Node:
|
|
120
|
-
var copy: Node = null
|
|
121
|
-
if not source.scene_file_path.is_empty():
|
|
122
|
-
# An instanced scene made again from its file, so the copy is an instance too. Node.duplicate
|
|
123
|
-
# makes a plain node of the instance's class, and the pack saved it with every property of the
|
|
124
|
-
# instance's root frozen into this scene.
|
|
125
|
-
var packed: PackedScene = ResourceLoader.load(source.scene_file_path, "PackedScene") as PackedScene
|
|
126
|
-
if packed == null:
|
|
127
|
-
return null
|
|
128
|
-
copy = packed.instantiate(PackedScene.GEN_EDIT_STATE_INSTANCE)
|
|
129
|
-
for declared: Dictionary in source.get_property_list():
|
|
130
|
-
if not (Read.as_int(declared.get("usage", 0)) & PROPERTY_USAGE_STORAGE):
|
|
131
|
-
continue
|
|
132
|
-
var property: String = str(declared.get("name", ""))
|
|
133
|
-
var value: Variant = source.get(property)
|
|
134
|
-
if not PropertyValues.same(copy.get(property), value):
|
|
135
|
-
copy.set(property, value)
|
|
136
|
-
for group: StringName in source.get_groups():
|
|
137
|
-
if not copy.is_in_group(group):
|
|
138
|
-
copy.add_to_group(group, true)
|
|
139
|
-
else:
|
|
140
|
-
copy = source.duplicate(Node.DUPLICATE_GROUPS | Node.DUPLICATE_SCRIPTS)
|
|
141
|
-
for child: Node in copy.get_children():
|
|
142
|
-
copy.remove_child(child)
|
|
143
|
-
child.free()
|
|
144
|
-
copy.name = source.name
|
|
145
|
-
copies[source] = copy
|
|
146
|
-
# Only what this scene owns: an instance's own nodes come with the instance.
|
|
147
|
-
for child: Node in source.get_children():
|
|
148
|
-
if child.owner == root:
|
|
149
|
-
var copied: Node = _copy(child, root, copies)
|
|
150
|
-
if copied == null:
|
|
151
|
-
return null
|
|
152
|
-
copy.add_child(copied)
|
|
153
|
-
return copy
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
## Gives each copy in [param copies] the persistent connections its source has, to the copy of the
|
|
157
|
-
## target when the target was copied too. A connection an instance makes inside itself is skipped:
|
|
158
|
-
## the instance made again from its file already has it.
|
|
159
|
-
func _reconnect(copies: Dictionary, top: Node) -> void:
|
|
160
|
-
for key: Variant in copies:
|
|
161
|
-
var source: Node = key
|
|
162
|
-
var copy: Node = copies[key]
|
|
163
|
-
for info: Dictionary in source.get_signal_list():
|
|
164
|
-
var signal_name: String = str(info.get("name", ""))
|
|
165
|
-
for conn: Dictionary in source.get_signal_connection_list(signal_name):
|
|
166
|
-
var flags: int = Read.as_int(conn.get("flags", 0))
|
|
167
|
-
var callable: Callable = conn.get("callable", Callable())
|
|
168
|
-
var target: Object = callable.get_object()
|
|
169
|
-
if not flags & Object.CONNECT_PERSIST or target == null:
|
|
170
|
-
continue
|
|
171
|
-
var to: Object = copies.get(target, target)
|
|
172
|
-
if to == target and target is Node and (target == top or top.is_ancestor_of(target as Node)):
|
|
173
|
-
continue
|
|
174
|
-
var copied: Callable = Callable(to, callable.get_method())
|
|
175
|
-
if (
|
|
176
|
-
not copy.is_connected(signal_name, copied)
|
|
177
|
-
and copy.connect(signal_name, copied, flags) != OK
|
|
178
|
-
):
|
|
179
|
-
push_error(
|
|
180
|
-
"gdharness: could not copy the connection %s of %s" % [signal_name, source.name]
|
|
181
|
-
)
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
func _own(node: Node, root: Node) -> void:
|
|
185
|
-
node.owner = root
|
|
186
|
-
for child: Node in node.get_children():
|
|
187
|
-
if child.owner == null or child.owner == root:
|
|
188
|
-
_own(child, root)
|
|
189
|
-
|
|
190
|
-
|
|
191
119
|
## [param node]'s stored properties as a read answers them. A property holding a node is answered
|
|
192
120
|
## as the path to it: the copy read here is never in the tree, and an object description named a
|
|
193
121
|
## node that was freed when the call ended.
|
|
@@ -240,27 +168,6 @@ func _parse_properties_arg(raw_properties: Variant) -> Dictionary:
|
|
|
240
168
|
return {}
|
|
241
169
|
|
|
242
170
|
|
|
243
|
-
func _build_node_tree(
|
|
244
|
-
node: Node, include_properties: bool, depth: int, current_depth: int, node_path: String
|
|
245
|
-
) -> Dictionary:
|
|
246
|
-
var children: Array[Dictionary] = []
|
|
247
|
-
var data: Dictionary = {
|
|
248
|
-
"name": str(node.name), "type": node.get_class(), "path": node_path, "children": children
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
if include_properties:
|
|
252
|
-
data["properties"] = _properties_of(node, true, {})
|
|
253
|
-
|
|
254
|
-
if depth >= 0 and current_depth >= depth:
|
|
255
|
-
return data
|
|
256
|
-
|
|
257
|
-
for child: Node in node.get_children():
|
|
258
|
-
var child_path: String = str(child.name) if node_path == "." else node_path + "/" + str(child.name)
|
|
259
|
-
children.append(_build_node_tree(child, include_properties, depth, current_depth + 1, child_path))
|
|
260
|
-
|
|
261
|
-
return data
|
|
262
|
-
|
|
263
|
-
|
|
264
171
|
func _collect_nodes_recursive(node: Node, path: String, out_nodes: Array) -> void:
|
|
265
172
|
out_nodes.append({"path": path, "node": node})
|
|
266
173
|
for child: Node in node.get_children():
|
|
@@ -349,31 +256,29 @@ func list_scene_nodes(args: Dictionary) -> Dictionary:
|
|
|
349
256
|
var depth: int = Read.as_int(args.get("depth", -1), -1)
|
|
350
257
|
var include_properties: bool = Read.as_bool(args.get("includeProperties", false))
|
|
351
258
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
var
|
|
355
|
-
if
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
var tree: Dictionary = _build_node_tree(scene.root, include_properties, depth, 0, ".")
|
|
359
|
-
scene.close()
|
|
360
|
-
return {"ok": true, "tree": tree, "unsavedInEditor": SceneFile.unsaved(scene_path)}
|
|
259
|
+
# Read as a headless engine reads it with no editor connected, so the answer does not depend on
|
|
260
|
+
# whether one is.
|
|
261
|
+
var read: Dictionary = FileReads.scene_tree(scene_path, depth, include_properties)
|
|
262
|
+
if read.get("ok", false):
|
|
263
|
+
read["unsavedInEditor"] = SceneFile.unsaved(scene_path)
|
|
264
|
+
return read
|
|
361
265
|
|
|
362
266
|
|
|
363
267
|
func add_node(args: Dictionary) -> Dictionary:
|
|
364
268
|
var project_path: String = str(args.get("projectPath", ""))
|
|
365
269
|
var scene_path: String = _to_scene_res_path(project_path, str(args.get("scenePath", "")))
|
|
366
270
|
var node_type: String = str(args.get("nodeType", ""))
|
|
271
|
+
var instance_path: String = str(args.get("instancePath", ""))
|
|
367
272
|
var node_name: String = str(args.get("nodeName", ""))
|
|
368
273
|
var parent_node_path: String = str(args.get("parentNodePath", "."))
|
|
369
274
|
var properties: Dictionary = _parse_properties_arg(args.get("properties", {}))
|
|
370
275
|
|
|
371
|
-
if
|
|
372
|
-
return {"ok": false, "error": "Missing
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
if not
|
|
376
|
-
return {"ok": false, "error":
|
|
276
|
+
if node_name.is_empty():
|
|
277
|
+
return {"ok": false, "error": "Missing nodeName"}
|
|
278
|
+
var making: Array = Placing.what_to_add(node_type, instance_path, scene_path)
|
|
279
|
+
var packed: PackedScene = making[0]
|
|
280
|
+
if not str(making[1]).is_empty():
|
|
281
|
+
return {"ok": false, "error": making[1]}
|
|
377
282
|
|
|
378
283
|
var opened: Array = _open(scene_path, true)
|
|
379
284
|
var scene: SceneFile = opened[0]
|
|
@@ -391,24 +296,39 @@ func add_node(args: Dictionary) -> Dictionary:
|
|
|
391
296
|
var unusable: String = scene.unusable_name(parent, node_name)
|
|
392
297
|
if not unusable.is_empty():
|
|
393
298
|
return scene.refuse(unusable)
|
|
299
|
+
var place: Variant = args.get("index")
|
|
300
|
+
var unplaceable: String = Placing.unplaceable(parent, null, place)
|
|
301
|
+
if not unplaceable.is_empty():
|
|
302
|
+
return scene.refuse(unplaceable)
|
|
394
303
|
|
|
395
304
|
# In the tree before its properties, so a property holding a node can find it by path.
|
|
396
|
-
var new_node: Node =
|
|
305
|
+
var new_node: Node = Placing.made(packed, node_type)
|
|
397
306
|
new_node.name = node_name
|
|
398
307
|
parent.add_child(new_node)
|
|
399
|
-
|
|
308
|
+
Placing.place(new_node, place)
|
|
309
|
+
Placing.own(new_node, root)
|
|
400
310
|
var refused_property: String = _properties.write_all(new_node, properties)
|
|
401
311
|
if not refused_property.is_empty():
|
|
402
312
|
return scene.refuse(refused_property)
|
|
403
313
|
|
|
404
314
|
var new_path: String = scene.path_of(new_node)
|
|
315
|
+
# Read before the write, which frees the scene it was added to.
|
|
316
|
+
var answer: Dictionary = {
|
|
317
|
+
"ok": true,
|
|
318
|
+
"nodeName": node_name,
|
|
319
|
+
"nodeType": new_node.get_class(),
|
|
320
|
+
"nodePath": new_path,
|
|
321
|
+
"index": new_node.get_index(),
|
|
322
|
+
}
|
|
405
323
|
var written: Dictionary = scene.write(
|
|
406
324
|
func(saved: Node) -> String: return _kept(scene, saved, new_node, properties.keys())
|
|
407
325
|
)
|
|
408
326
|
if not written.is_empty():
|
|
409
327
|
return written
|
|
410
328
|
|
|
411
|
-
|
|
329
|
+
if packed != null:
|
|
330
|
+
answer["instanceOf"] = instance_path
|
|
331
|
+
return answer
|
|
412
332
|
|
|
413
333
|
|
|
414
334
|
func delete_node(args: Dictionary) -> Dictionary:
|
|
@@ -487,14 +407,14 @@ func duplicate_node(args: Dictionary) -> Dictionary:
|
|
|
487
407
|
return scene.refuse(unusable)
|
|
488
408
|
|
|
489
409
|
var copies: Dictionary = {}
|
|
490
|
-
var duplicated_node: Node =
|
|
410
|
+
var duplicated_node: Node = Placing.copied(source, root, copies)
|
|
491
411
|
if not duplicated_node:
|
|
492
412
|
return scene.refuse("Failed to duplicate node: " + node_path)
|
|
493
413
|
|
|
494
414
|
duplicated_node.name = new_name
|
|
495
415
|
target_parent.add_child(duplicated_node)
|
|
496
|
-
|
|
497
|
-
|
|
416
|
+
Placing.own(duplicated_node, root)
|
|
417
|
+
Placing.reconnect(copies, source)
|
|
498
418
|
|
|
499
419
|
var new_path: String = scene.path_of(duplicated_node)
|
|
500
420
|
var written: Dictionary = scene.write(
|
|
@@ -548,25 +468,40 @@ func reparent_node(args: Dictionary) -> Dictionary:
|
|
|
548
468
|
var unusable: String = scene.unusable_name(new_parent, str(node.name), node)
|
|
549
469
|
if not unusable.is_empty():
|
|
550
470
|
return scene.refuse(unusable)
|
|
471
|
+
var place: Variant = args.get("index")
|
|
472
|
+
var unplaceable: String = Placing.unplaceable(new_parent, node, place)
|
|
473
|
+
if not unplaceable.is_empty():
|
|
474
|
+
return scene.refuse(unplaceable)
|
|
551
475
|
|
|
552
476
|
var old_path: String = scene.path_of(node)
|
|
553
477
|
node.get_parent().remove_child(node)
|
|
554
478
|
new_parent.add_child(node)
|
|
555
|
-
|
|
479
|
+
Placing.place(node, place)
|
|
480
|
+
Placing.own(node, root)
|
|
556
481
|
|
|
557
482
|
var new_path: String = scene.path_of(node)
|
|
483
|
+
var index: int = node.get_index()
|
|
558
484
|
var written: Dictionary = scene.write(
|
|
559
485
|
func(saved: Node) -> String:
|
|
560
|
-
|
|
486
|
+
var copy: Node = saved.get_node_or_null(new_path)
|
|
487
|
+
if copy == null:
|
|
561
488
|
return "%s would not be in the scene when it loads." % new_path
|
|
562
489
|
if old_path != new_path and saved.get_node_or_null(old_path) != null:
|
|
563
490
|
return "%s would still be in the scene as well when it loads." % old_path
|
|
491
|
+
if copy.get_index() != index:
|
|
492
|
+
return "%s would load at index %d, not %d." % [new_path, copy.get_index(), index]
|
|
564
493
|
return ""
|
|
565
494
|
)
|
|
566
495
|
if not written.is_empty():
|
|
567
496
|
return written
|
|
568
497
|
|
|
569
|
-
return {
|
|
498
|
+
return {
|
|
499
|
+
"ok": true,
|
|
500
|
+
"nodePath": old_path,
|
|
501
|
+
"newParentPath": new_parent_path,
|
|
502
|
+
"newNodePath": new_path,
|
|
503
|
+
"index": index,
|
|
504
|
+
}
|
|
570
505
|
|
|
571
506
|
|
|
572
507
|
func set_node_properties(args: Dictionary) -> Dictionary:
|
|
@@ -601,7 +536,7 @@ func set_node_properties(args: Dictionary) -> Dictionary:
|
|
|
601
536
|
|
|
602
537
|
var held: Dictionary = {}
|
|
603
538
|
for key: Variant in properties:
|
|
604
|
-
held[str(key)] = _readable(node,
|
|
539
|
+
held[str(key)] = _readable(node, _properties.read_at(node, str(key)))
|
|
605
540
|
var new_path: String = scene.path_of(node)
|
|
606
541
|
var written: Dictionary = scene.write(
|
|
607
542
|
func(saved: Node) -> String: return _kept(scene, saved, node, properties.keys())
|
|
@@ -633,6 +568,7 @@ func get_node_properties(args: Dictionary) -> Dictionary:
|
|
|
633
568
|
# variable read as changed; and a value this scene sets back to the default was hidden, so the
|
|
634
569
|
# names this scene's file sets are always answered.
|
|
635
570
|
var props: Dictionary = _properties_of(node, include_defaults, scene.set_here(node))
|
|
571
|
+
var cells: Dictionary = ResourceTools.cells_of(node)
|
|
636
572
|
scene.close()
|
|
637
573
|
var answer: Dictionary = {
|
|
638
574
|
"ok": true,
|
|
@@ -640,6 +576,7 @@ func get_node_properties(args: Dictionary) -> Dictionary:
|
|
|
640
576
|
"properties": props,
|
|
641
577
|
"unsavedInEditor": SceneFile.unsaved(scene_path)
|
|
642
578
|
}
|
|
579
|
+
answer.merge(cells)
|
|
643
580
|
# An empty answer read as a node with nothing on it, rather than one with nothing changed.
|
|
644
581
|
if props.is_empty() and not include_defaults:
|
|
645
582
|
answer["note"] = (
|
|
@@ -757,6 +694,10 @@ func connect_signal(args: Dictionary) -> Dictionary:
|
|
|
757
694
|
# asked for. Left alone, the answer gave the flags asked for and the file kept the old ones.
|
|
758
695
|
var callable: Callable = Callable(target, method_name)
|
|
759
696
|
var method_found: bool = target.has_method(method_name)
|
|
697
|
+
var flags_before: int = -1
|
|
698
|
+
for connection: Dictionary in source.get_signal_connection_list(signal_name):
|
|
699
|
+
if connection.get("callable") == callable:
|
|
700
|
+
flags_before = Read.as_int(connection.get("flags", 0))
|
|
760
701
|
if source.is_connected(signal_name, callable):
|
|
761
702
|
source.disconnect(signal_name, callable)
|
|
762
703
|
var connect_result: Error = source.connect(signal_name, callable, flags)
|
|
@@ -776,7 +717,7 @@ func connect_signal(args: Dictionary) -> Dictionary:
|
|
|
776
717
|
if not written.is_empty():
|
|
777
718
|
return written
|
|
778
719
|
|
|
779
|
-
|
|
720
|
+
var answer: Dictionary = {
|
|
780
721
|
"ok": true,
|
|
781
722
|
"sourceNodePath": from,
|
|
782
723
|
"signalName": signal_name,
|
|
@@ -788,6 +729,14 @@ func connect_signal(args: Dictionary) -> Dictionary:
|
|
|
788
729
|
# next; a caller told nothing learnt of a misspelt one only when the signal fired.
|
|
789
730
|
"methodFound": method_found
|
|
790
731
|
}
|
|
732
|
+
# A second connect of the same pair read exactly like the first, so a caller could not tell a
|
|
733
|
+
# connection made from one that was already there, and a list showing one where two were made
|
|
734
|
+
# looked like a lost write.
|
|
735
|
+
if flags_before >= 0:
|
|
736
|
+
answer["alreadyConnected"] = true
|
|
737
|
+
if flags_before != Read.as_int(saved_flags[0]):
|
|
738
|
+
answer["flagsBefore"] = _flag_names(flags_before)
|
|
739
|
+
return answer
|
|
791
740
|
|
|
792
741
|
|
|
793
742
|
## The two ends of a connection in [param scene], as [source, target], or [null, why not].
|
|
@@ -49,6 +49,10 @@ const PROFILER_PROBE_CALLS: int = 10000
|
|
|
49
49
|
## on every 2048th call and only counts the others.
|
|
50
50
|
const POLLS_PER_READ: int = 2048
|
|
51
51
|
|
|
52
|
+
## The most frames a close waits for its answer to leave: a client that stopped reading would
|
|
53
|
+
## otherwise hold the close for ever, and the server is past caring about the answer by then.
|
|
54
|
+
const CLOSE_AFTER_FRAMES: int = 30
|
|
55
|
+
|
|
52
56
|
## Set in a game's environment by whatever starts it to be profiled from its start: the server for
|
|
53
57
|
## a run it spawns, the editor for a play. Kept in step with `PROFILE_FROM_START` in the server.
|
|
54
58
|
const PROFILE_FROM_START: String = "GDHARNESS_PROFILE_FROM_START"
|
|
@@ -118,6 +122,8 @@ var _commands: Dictionary = {}
|
|
|
118
122
|
var _loaded_version: String = _read_marker(VERSION_MARKER)
|
|
119
123
|
var _loaded_digest: String = _read_marker(DIGEST_MARKER)
|
|
120
124
|
var _profile_received: bool = false
|
|
125
|
+
var _close_asked: bool = false
|
|
126
|
+
var _close_frames: int = 0
|
|
121
127
|
var _profiler_calibrated: bool = false
|
|
122
128
|
|
|
123
129
|
|
|
@@ -140,6 +146,7 @@ func _init() -> void:
|
|
|
140
146
|
"inject_text": _typing.inject_text,
|
|
141
147
|
"inject_mouse_click": _input.inject_mouse_click,
|
|
142
148
|
"inject_mouse_motion": _input.inject_mouse_motion,
|
|
149
|
+
"inject_mouse_drag": _input.inject_mouse_drag,
|
|
143
150
|
"inject_joypad_button": _pad.inject_joypad_button,
|
|
144
151
|
"inject_joypad_motion": _pad.inject_joypad_motion,
|
|
145
152
|
"click": _input.click,
|
|
@@ -148,6 +155,7 @@ func _init() -> void:
|
|
|
148
155
|
"wait_signal": _waits.wait_signal,
|
|
149
156
|
"wait_until": _waits.wait_until,
|
|
150
157
|
"send_profile": _send_profile_now,
|
|
158
|
+
"close_game": _close_game,
|
|
151
159
|
}
|
|
152
160
|
_wait_for_the_profiler()
|
|
153
161
|
|
|
@@ -341,6 +349,14 @@ func _process(_delta: float) -> void:
|
|
|
341
349
|
var _had_pending: bool = _pending.erase(client)
|
|
342
350
|
var _had_outgoing: bool = _outgoing.erase(client)
|
|
343
351
|
|
|
352
|
+
# After the answer to the close has gone out: the close request reaches this node too, which then
|
|
353
|
+
# drops its clients and whatever they were still owed.
|
|
354
|
+
if _close_asked:
|
|
355
|
+
_close_frames += 1
|
|
356
|
+
if _close_frames >= CLOSE_AFTER_FRAMES or _outgoing.values().all(_is_empty):
|
|
357
|
+
_close_asked = false
|
|
358
|
+
_deliver_close()
|
|
359
|
+
|
|
344
360
|
|
|
345
361
|
## Hands the socket as much of what it is owed as it will take, and says whether it is still worth
|
|
346
362
|
## talking to. Reading comes first in the frame, so a client that asked and left is noticed here
|
|
@@ -578,6 +594,29 @@ func _ping(_params: Dictionary) -> Dictionary:
|
|
|
578
594
|
return {"type": "pong", "timestamp": Time.get_unix_time_from_system()}
|
|
579
595
|
|
|
580
596
|
|
|
597
|
+
## The game asked to close, as a player's close asks it: answered at once and delivered once the
|
|
598
|
+
## answer has gone out. `acceptsQuit` says whether the tree quits on the request by itself; a game
|
|
599
|
+
## that turned that off decides for itself, and may ask the player first.
|
|
600
|
+
func _close_game(_params: Dictionary) -> Dictionary:
|
|
601
|
+
_close_asked = true
|
|
602
|
+
_close_frames = 0
|
|
603
|
+
return {"type": "closing", "acceptsQuit": get_tree().auto_accept_quit}
|
|
604
|
+
|
|
605
|
+
|
|
606
|
+
## What a player's close does: the close request reaches every node, and then the root window says
|
|
607
|
+
## it was asked to close, which the tree answers by quitting when it accepts quits. So the game's own
|
|
608
|
+
## close runs, a crash guard's included, where a stop that ended the process ran none of it.
|
|
609
|
+
func _deliver_close() -> void:
|
|
610
|
+
var root: Window = get_tree().root
|
|
611
|
+
root.propagate_notification(NOTIFICATION_WM_CLOSE_REQUEST)
|
|
612
|
+
root.close_requested.emit()
|
|
613
|
+
|
|
614
|
+
|
|
615
|
+
static func _is_empty(owed: Variant) -> bool:
|
|
616
|
+
var bytes: PackedByteArray = owed
|
|
617
|
+
return bytes.is_empty()
|
|
618
|
+
|
|
619
|
+
|
|
581
620
|
## One JSON object and a newline, queued for [method _drain] rather than written here.
|
|
582
621
|
##
|
|
583
622
|
## Raw bytes rather than put_utf8_string, which would prefix them with a length the other side is
|