gdharness 1.3.0 → 1.3.1

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 (68) hide show
  1. package/build/cli.js +14 -13
  2. package/build/godot/addons/gdharness_editor/animation_reads.gd +7 -19
  3. package/build/godot/addons/gdharness_editor/bridge_client.gd +8 -8
  4. package/build/godot/addons/gdharness_editor/file_reads.gd +60 -25
  5. package/build/godot/addons/gdharness_editor/headless_check.gd +7 -7
  6. package/build/godot/addons/gdharness_editor/plugin.gd +6 -6
  7. package/build/godot/addons/gdharness_editor/property_values.gd +120 -53
  8. package/build/godot/addons/gdharness_editor/reading.gd +28 -0
  9. package/build/godot/addons/gdharness_editor/scene_file.gd +28 -23
  10. package/build/godot/addons/gdharness_editor/serialisation.gd +45 -94
  11. package/build/godot/addons/gdharness_editor/tool_executor.gd +24 -24
  12. package/build/godot/addons/gdharness_editor/tools/animation_tools.gd +52 -41
  13. package/build/godot/addons/gdharness_editor/tools/filesystem_tools.gd +4 -4
  14. package/build/godot/addons/gdharness_editor/tools/play_tools.gd +2 -2
  15. package/build/godot/addons/gdharness_editor/tools/profile_tools.gd +14 -14
  16. package/build/godot/addons/gdharness_editor/tools/resource_tools.gd +48 -41
  17. package/build/godot/addons/gdharness_editor/tools/scene_kin.gd +17 -17
  18. package/build/godot/addons/gdharness_editor/tools/scene_paths.gd +14 -12
  19. package/build/godot/addons/gdharness_editor/tools/scene_placing.gd +7 -7
  20. package/build/godot/addons/gdharness_editor/tools/scene_tools.gd +81 -77
  21. package/build/godot/addons/gdharness_editor/tools/settings_tools.gd +4 -4
  22. package/build/godot/addons/gdharness_editor/value_shapes.gd +111 -0
  23. package/build/godot/addons/gdharness_editor/value_shapes.gd.uid +1 -0
  24. package/build/godot/addons/gdharness_runtime/reading.gd +28 -0
  25. package/build/godot/addons/gdharness_runtime/runtime_accessibility.gd +18 -18
  26. package/build/godot/addons/gdharness_runtime/runtime_autoload.gd +29 -29
  27. package/build/godot/addons/gdharness_runtime/runtime_capture.gd +21 -18
  28. package/build/godot/addons/gdharness_runtime/runtime_changes.gd +86 -80
  29. package/build/godot/addons/gdharness_runtime/runtime_input.gd +65 -64
  30. package/build/godot/addons/gdharness_runtime/runtime_items.gd +6 -6
  31. package/build/godot/addons/gdharness_runtime/runtime_menus.gd +8 -8
  32. package/build/godot/addons/gdharness_runtime/runtime_mouse.gd +6 -6
  33. package/build/godot/addons/gdharness_runtime/runtime_pad.gd +10 -10
  34. package/build/godot/addons/gdharness_runtime/runtime_paths.gd +7 -7
  35. package/build/godot/addons/gdharness_runtime/runtime_queries.gd +41 -41
  36. package/build/godot/addons/gdharness_runtime/runtime_says.gd +3 -3
  37. package/build/godot/addons/gdharness_runtime/runtime_targets.gd +33 -33
  38. package/build/godot/addons/gdharness_runtime/runtime_typing.gd +20 -20
  39. package/build/godot/addons/gdharness_runtime/runtime_values.gd +25 -24
  40. package/build/godot/addons/gdharness_runtime/runtime_waits.gd +56 -50
  41. package/build/godot/addons/gdharness_runtime/serialisation.gd +45 -94
  42. package/build/godot/addons/gdharness_runtime/value_shapes.gd +111 -0
  43. package/build/godot/addons/gdharness_runtime/value_shapes.gd.uid +1 -0
  44. package/build/godot/operations/animation_reads.gd +7 -19
  45. package/build/godot/operations/audio_buses.gd +10 -10
  46. package/build/godot/operations/class_cache.gd +16 -16
  47. package/build/godot/operations/classdb_queries.gd +12 -12
  48. package/build/godot/operations/dependencies.gd +33 -29
  49. package/build/godot/operations/editor_tools.gd +12 -12
  50. package/build/godot/operations/export_presets.gd +10 -8
  51. package/build/godot/operations/file_reads.gd +60 -25
  52. package/build/godot/operations/gdscript_analysis.gd +20 -20
  53. package/build/godot/operations/gdscript_authoring.gd +17 -15
  54. package/build/godot/operations/godot_operations.gd +64 -63
  55. package/build/godot/operations/import_pipeline.gd +20 -20
  56. package/build/godot/operations/input_actions.gd +17 -17
  57. package/build/godot/operations/logger.gd +1 -1
  58. package/build/godot/operations/plugins.gd +8 -8
  59. package/build/godot/operations/project_config.gd +24 -17
  60. package/build/godot/operations/project_diagnostics.gd +9 -9
  61. package/build/godot/operations/property_values.gd +120 -53
  62. package/build/godot/operations/reading.gd +28 -0
  63. package/build/godot/operations/rename_checks.gd +3 -3
  64. package/build/godot/operations/resource_files.gd +5 -5
  65. package/build/godot/operations/script_check.gd +7 -7
  66. package/build/godot/operations/serialisation.gd +45 -94
  67. package/build/godot/operations/value_shapes.gd +111 -0
  68. package/package.json +1 -1
@@ -3,19 +3,19 @@ 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")
7
- const Read = preload("../reading.gd")
8
- const Kin = preload("scene_kin.gd")
9
- const Paths = preload("scene_paths.gd")
10
- const Placing = preload("scene_placing.gd")
11
- const PropertyValues = preload("../property_values.gd")
12
- const ResourceTools = preload("resource_tools.gd")
13
- const SceneFile = preload("../scene_file.gd")
14
- const Serialisation = preload("../serialisation.gd")
6
+ const GdhFileReads = preload("../file_reads.gd")
7
+ const GdhRead = preload("../reading.gd")
8
+ const GdhKin = preload("scene_kin.gd")
9
+ const GdhPaths = preload("scene_paths.gd")
10
+ const GdhPlacing = preload("scene_placing.gd")
11
+ const GdhPropertyValues = preload("../property_values.gd")
12
+ const GdhResourceTools = preload("resource_tools.gd")
13
+ const GdhSceneFile = preload("../scene_file.gd")
14
+ const GdhSerialisation = preload("../serialisation.gd")
15
15
 
16
16
  var _editor_plugin: EditorPlugin = null
17
- var _values: Serialisation = Serialisation.new()
18
- var _properties: PropertyValues = PropertyValues.new()
17
+ var _values: GdhSerialisation = GdhSerialisation.new()
18
+ var _properties: GdhPropertyValues = GdhPropertyValues.new()
19
19
 
20
20
 
21
21
  func set_editor_plugin(plugin: EditorPlugin) -> void:
@@ -47,7 +47,7 @@ func _to_scene_res_path(project_path: String, scene_path: String) -> String:
47
47
 
48
48
  ## [param scene_path] opened for a tool, as [scene, refusal]: the refusal is {} when it opened.
49
49
  func _open(scene_path: String, for_writing: bool) -> Array:
50
- var scene: SceneFile = SceneFile.new()
50
+ var scene: GdhSceneFile = GdhSceneFile.new()
51
51
  return [scene, scene.open(scene_path, for_writing)]
52
52
 
53
53
 
@@ -76,7 +76,7 @@ func _missing_on(node: Node, missing: String) -> String:
76
76
 
77
77
  ## Whether each of [param names] reads the same off [param saved] as off [param node], answering
78
78
  ## the first that does not, or "".
79
- func _kept(scene: SceneFile, saved: Node, node: Node, names: Array) -> String:
79
+ func _kept(scene: GdhSceneFile, saved: Node, node: Node, names: Array) -> String:
80
80
  var at: String = scene.path_of(node)
81
81
  var copy: Node = saved.get_node_or_null(at)
82
82
  if copy == null:
@@ -106,7 +106,7 @@ func _properties_of(node: Node, include_defaults: bool, set_here: Dictionary) ->
106
106
  scripted[str(declared.get("name", ""))] = true
107
107
  var props: Dictionary = {}
108
108
  for p: Dictionary in node.get_property_list():
109
- if not (Read.as_int(p.get("usage", 0)) & PROPERTY_USAGE_STORAGE):
109
+ if not (GdhRead.as_int(p.get("usage", 0)) & PROPERTY_USAGE_STORAGE):
110
110
  continue
111
111
  var property: String = str(p.get("name", ""))
112
112
  if property.is_empty():
@@ -118,7 +118,7 @@ func _properties_of(node: Node, include_defaults: bool, set_here: Dictionary) ->
118
118
  if scripted.has(property)
119
119
  else ClassDB.class_get_property_default_value(node.get_class(), property)
120
120
  )
121
- if PropertyValues.same(value, default):
121
+ if GdhPropertyValues.same(value, default):
122
122
  continue
123
123
  props[property] = _readable(node, value)
124
124
  return props
@@ -141,7 +141,7 @@ func _parse_properties_arg(raw_properties: Variant) -> Dictionary:
141
141
  var text: String = str(raw_properties)
142
142
  if text.strip_edges().is_empty():
143
143
  return {}
144
- var parsed: Variant = Read.json_or_null(text)
144
+ var parsed: Variant = GdhRead.json_or_null(text)
145
145
  if typeof(parsed) == TYPE_DICTIONARY:
146
146
  return parsed
147
147
  return {}
@@ -157,7 +157,7 @@ func _collect_nodes_recursive(node: Node, path: String, out_nodes: Array) -> voi
157
157
  func _inherited(args: Dictionary) -> Dictionary:
158
158
  var project_path: String = str(args.get("projectPath", ""))
159
159
  var script_path: String = str(args.get("script", ""))
160
- return Kin.inherited(
160
+ return GdhKin.inherited(
161
161
  args,
162
162
  _new_scene_path(args),
163
163
  _to_scene_res_path(project_path, str(args.get("inherits", ""))),
@@ -191,7 +191,7 @@ func _create_of_a_class(args: Dictionary) -> Dictionary:
191
191
  if scene_path.is_empty():
192
192
  return {"ok": false, "error": "Missing scenePath"}
193
193
  var asked_type: String = root_node_type
194
- var made: Dictionary = SceneFile.made_as(
194
+ var made: Dictionary = GdhSceneFile.made_as(
195
195
  root_node_type,
196
196
  "" if script_path.is_empty() else _to_scene_res_path(project_path, script_path),
197
197
  "Node",
@@ -209,15 +209,15 @@ func _create_of_a_class(args: Dictionary) -> Dictionary:
209
209
  }
210
210
  if not ClassDB.can_instantiate(root_node_type):
211
211
  return {"ok": false, "error": "%s cannot be instantiated." % root_node_type}
212
- var unfit: String = SceneFile.unfit_name(root_node_name)
212
+ var unfit: String = GdhSceneFile.unfit_name(root_node_name)
213
213
  if not unfit.is_empty():
214
214
  return {"ok": false, "error": unfit}
215
215
  if FileAccess.file_exists(scene_path):
216
- return Kin.taken(scene_path)
216
+ return GdhKin.taken(scene_path)
217
217
 
218
218
  var root: Node = ClassDB.instantiate(root_node_type)
219
219
  root.name = root_node_name
220
- var scene: SceneFile = SceneFile.new()
220
+ var scene: GdhSceneFile = GdhSceneFile.new()
221
221
  scene.root = root
222
222
  scene.path = scene_path
223
223
  scene.asked(root, ["script"])
@@ -228,7 +228,7 @@ func _create_of_a_class(args: Dictionary) -> Dictionary:
228
228
  if script == null:
229
229
  root.free()
230
230
  return {"ok": false, "error": "No script loads from " + full_script_path}
231
- var uncompiled: String = SceneFile.uncompiled(script, full_script_path)
231
+ var uncompiled: String = GdhSceneFile.uncompiled(script, full_script_path)
232
232
  if not uncompiled.is_empty():
233
233
  root.free()
234
234
  return {"ok": false, "error": uncompiled}
@@ -245,7 +245,7 @@ func _create_of_a_class(args: Dictionary) -> Dictionary:
245
245
  # name is no property the write compares.
246
246
  root.name = root_node_name
247
247
 
248
- var folder: String = SceneFile.make_folder_for(scene_path)
248
+ var folder: String = GdhSceneFile.make_folder_for(scene_path)
249
249
  if not folder.is_empty():
250
250
  root.free()
251
251
  return {"ok": false, "error": folder}
@@ -259,14 +259,14 @@ func _create_of_a_class(args: Dictionary) -> Dictionary:
259
259
  func list_scene_nodes(args: Dictionary) -> Dictionary:
260
260
  var project_path: String = str(args.get("projectPath", ""))
261
261
  var scene_path: String = _to_scene_res_path(project_path, str(args.get("scenePath", "")))
262
- var depth: int = Read.as_int(args.get("depth", -1), -1)
263
- var include_properties: bool = Read.as_bool(args.get("includeProperties", false))
262
+ var depth: int = GdhRead.as_int(args.get("depth", -1), -1)
263
+ var include_properties: bool = GdhRead.as_bool(args.get("includeProperties", false))
264
264
 
265
265
  # Read as a headless engine reads it with no editor connected, so the answer does not depend on
266
266
  # whether one is.
267
- var read: Dictionary = FileReads.scene_tree(scene_path, depth, include_properties)
267
+ var read: Dictionary = GdhFileReads.scene_tree(scene_path, depth, include_properties)
268
268
  if read.get("ok", false):
269
- read["unsavedInEditor"] = SceneFile.unsaved(scene_path)
269
+ read["unsavedInEditor"] = GdhSceneFile.unsaved(scene_path)
270
270
  return read
271
271
 
272
272
 
@@ -284,20 +284,20 @@ func add_node(args: Dictionary) -> Dictionary:
284
284
  var asked_type: String = node_type
285
285
  var class_script: Script = null
286
286
  if not node_type.is_empty() and not ClassDB.class_exists(node_type):
287
- var declared: Dictionary = SceneFile.script_class(node_type, "Node")
287
+ var declared: Dictionary = GdhSceneFile.script_class(node_type, "Node")
288
288
  if declared.has("error"):
289
289
  return {"ok": false, "error": declared["error"]}
290
290
  var held: Variant = declared.get("script")
291
291
  if held is Script:
292
292
  class_script = held
293
293
  node_type = str(declared["base"])
294
- var making: Array = Placing.what_to_add(node_type, instance_path, scene_path)
294
+ var making: Array = GdhPlacing.what_to_add(node_type, instance_path, scene_path)
295
295
  var packed: PackedScene = making[0]
296
296
  if not str(making[1]).is_empty():
297
297
  return {"ok": false, "error": making[1]}
298
298
 
299
299
  var opened: Array = _open(scene_path, true)
300
- var scene: SceneFile = opened[0]
300
+ var scene: GdhSceneFile = opened[0]
301
301
  var refused: Dictionary = opened[1]
302
302
  if not refused.is_empty():
303
303
  return refused
@@ -313,19 +313,19 @@ func add_node(args: Dictionary) -> Dictionary:
313
313
  if not unusable.is_empty():
314
314
  return scene.refuse(unusable)
315
315
  var place: Variant = args.get("index")
316
- var unplaceable: String = Placing.unplaceable(parent, null, place)
316
+ var unplaceable: String = GdhPlacing.unplaceable(parent, null, place)
317
317
  if not unplaceable.is_empty():
318
318
  return scene.refuse(unplaceable)
319
319
 
320
320
  # In the tree before its properties, so a property holding a node can find it by path.
321
- var new_node: Node = Placing.made(packed, node_type)
321
+ var new_node: Node = GdhPlacing.made(packed, node_type)
322
322
  if class_script != null:
323
323
  scene.asked(new_node, ["script"])
324
324
  new_node.set_script(class_script)
325
325
  new_node.name = node_name
326
326
  parent.add_child(new_node)
327
- Placing.place(new_node, place)
328
- Placing.own(new_node, root)
327
+ GdhPlacing.place(new_node, place)
328
+ GdhPlacing.own(new_node, root)
329
329
  var unsettled: String = scene.settle()
330
330
  if not unsettled.is_empty():
331
331
  return scene.refuse("Nothing was written: " + unsettled)
@@ -343,6 +343,7 @@ func add_node(args: Dictionary) -> Dictionary:
343
343
  "nodePath": new_path,
344
344
  "index": new_node.get_index(),
345
345
  }
346
+ _properties.note_also_changed(answer, new_node)
346
347
  var written: Dictionary = scene.write(
347
348
  func(saved: Node) -> String:
348
349
  return _kept(
@@ -373,7 +374,7 @@ func delete_node(args: Dictionary) -> Dictionary:
373
374
  }
374
375
 
375
376
  var opened: Array = _open(scene_path, true)
376
- var scene: SceneFile = opened[0]
377
+ var scene: GdhSceneFile = opened[0]
377
378
  var refused: Dictionary = opened[1]
378
379
  if not refused.is_empty():
379
380
  return refused
@@ -388,7 +389,7 @@ func delete_node(args: Dictionary) -> Dictionary:
388
389
  var at: String = scene.path_of(node)
389
390
  node.get_parent().remove_child(node)
390
391
  node.free()
391
- var carried: Paths = Paths.new()
392
+ var carried: GdhPaths = GdhPaths.new()
392
393
  var _rewritten: int = carried.carry(scene, {at: ""})
393
394
 
394
395
  var written: Dictionary = scene.write(
@@ -401,7 +402,7 @@ func delete_node(args: Dictionary) -> Dictionary:
401
402
  return written
402
403
 
403
404
  var answer: Dictionary = {"ok": true, "deletedNodePath": at}
404
- answer.merge(Kin.follow(scene_path, at, "", carried))
405
+ answer.merge(GdhKin.follow(scene_path, at, "", carried))
405
406
  return answer
406
407
 
407
408
 
@@ -416,7 +417,7 @@ func duplicate_node(args: Dictionary) -> Dictionary:
416
417
  return {"ok": false, "error": "Missing nodePath or newName"}
417
418
 
418
419
  var opened: Array = _open(scene_path, true)
419
- var scene: SceneFile = opened[0]
420
+ var scene: GdhSceneFile = opened[0]
420
421
  var refused: Dictionary = opened[1]
421
422
  if not refused.is_empty():
422
423
  return refused
@@ -444,14 +445,14 @@ func duplicate_node(args: Dictionary) -> Dictionary:
444
445
  return scene.refuse(unusable)
445
446
 
446
447
  var copies: Dictionary = {}
447
- var duplicated_node: Node = Placing.copied(source, root, copies)
448
+ var duplicated_node: Node = GdhPlacing.copied(source, root, copies)
448
449
  if not duplicated_node:
449
450
  return scene.refuse("Failed to duplicate node: " + node_path)
450
451
 
451
452
  duplicated_node.name = new_name
452
453
  target_parent.add_child(duplicated_node)
453
- Placing.own(duplicated_node, root)
454
- Placing.reconnect(copies, source)
454
+ GdhPlacing.own(duplicated_node, root)
455
+ GdhPlacing.reconnect(copies, source)
455
456
  scene.copied(copies)
456
457
 
457
458
  var new_path: String = scene.path_of(duplicated_node)
@@ -487,7 +488,7 @@ func reparent_node(args: Dictionary) -> Dictionary:
487
488
  return {"ok": false, "error": "Missing newParentPath"}
488
489
 
489
490
  var opened: Array = _open(scene_path, true)
490
- var scene: SceneFile = opened[0]
491
+ var scene: GdhSceneFile = opened[0]
491
492
  var refused: Dictionary = opened[1]
492
493
  if not refused.is_empty():
493
494
  return refused
@@ -515,7 +516,7 @@ func reparent_node(args: Dictionary) -> Dictionary:
515
516
  if not unusable.is_empty():
516
517
  return scene.refuse(unusable)
517
518
  var place: Variant = args.get("index")
518
- var unplaceable: String = Placing.unplaceable(new_parent, node, place)
519
+ var unplaceable: String = GdhPlacing.unplaceable(new_parent, node, place)
519
520
  if not unplaceable.is_empty():
520
521
  return scene.refuse(unplaceable)
521
522
 
@@ -525,12 +526,12 @@ func reparent_node(args: Dictionary) -> Dictionary:
525
526
  node.owner = null
526
527
  node.get_parent().remove_child(node)
527
528
  new_parent.add_child(node)
528
- Placing.place(node, place)
529
- Placing.own(node, root)
529
+ GdhPlacing.place(node, place)
530
+ GdhPlacing.own(node, root)
530
531
 
531
532
  var new_path: String = scene.path_of(node)
532
533
  var index: int = node.get_index()
533
- var carried: Paths = Paths.new()
534
+ var carried: GdhPaths = GdhPaths.new()
534
535
  var _rewritten: int = carried.carry(scene, {old_path: new_path} if new_path != old_path else {})
535
536
  var written: Dictionary = scene.write(
536
537
  func(saved: Node) -> String:
@@ -555,7 +556,7 @@ func reparent_node(args: Dictionary) -> Dictionary:
555
556
  "index": index,
556
557
  }
557
558
  if new_path != old_path:
558
- answer.merge(Kin.follow(scene_path, old_path, new_path, carried))
559
+ answer.merge(GdhKin.follow(scene_path, old_path, new_path, carried))
559
560
  return answer
560
561
 
561
562
 
@@ -569,7 +570,7 @@ func set_node_properties(args: Dictionary) -> Dictionary:
569
570
  return {"ok": false, "error": "set takes properties, editableChildren, or both."}
570
571
 
571
572
  var opened: Array = _open(scene_path, true)
572
- var scene: SceneFile = opened[0]
573
+ var scene: GdhSceneFile = opened[0]
573
574
  var refused: Dictionary = opened[1]
574
575
  if not refused.is_empty():
575
576
  return refused
@@ -590,10 +591,10 @@ func set_node_properties(args: Dictionary) -> Dictionary:
590
591
 
591
592
  var dropped: Dictionary = {}
592
593
  if editable != null:
593
- var not_an_instance: String = Kin.set_editable(scene, node, Read.as_bool(editable))
594
+ var not_an_instance: String = GdhKin.set_editable(scene, node, GdhRead.as_bool(editable))
594
595
  if not not_an_instance.is_empty():
595
596
  return scene.refuse(not_an_instance)
596
- if not Read.as_bool(editable):
597
+ if not GdhRead.as_bool(editable):
597
598
  dropped = scene.held_under(scene.path_of(node))
598
599
 
599
600
  var old_path: String = scene.path_of(node)
@@ -606,28 +607,31 @@ func set_node_properties(args: Dictionary) -> Dictionary:
606
607
  for key: Variant in properties:
607
608
  held[str(key)] = _readable(node, _properties.read_at(node, str(key)))
608
609
  var new_path: String = scene.path_of(node)
609
- var carried: Paths = Paths.new()
610
+ var answer: Dictionary = {"ok": true, "nodePath": new_path, "properties": held}
611
+ _properties.note_also_changed(answer, node)
612
+ var carried: GdhPaths = GdhPaths.new()
610
613
  var _rewritten: int = carried.carry(scene, {old_path: new_path} if new_path != old_path else {})
611
614
  var written: Dictionary = scene.write(
612
615
  func(saved: Node) -> String:
613
616
  var unkept: String = _kept(scene, saved, node, properties.keys())
614
617
  if unkept.is_empty() and editable != null:
615
- if saved.is_editable_instance(saved.get_node(new_path)) != Read.as_bool(editable):
616
- return "%s would load with editableChildren %s." % [new_path, not Read.as_bool(editable)]
618
+ if saved.is_editable_instance(saved.get_node(new_path)) != GdhRead.as_bool(editable):
619
+ return (
620
+ "%s would load with editableChildren %s." % [new_path, not GdhRead.as_bool(editable)]
621
+ )
617
622
  return unkept
618
623
  )
619
624
  if not written.is_empty():
620
625
  carried.put_back(scene_path)
621
626
  return written
622
627
 
623
- var answer: Dictionary = {"ok": true, "nodePath": new_path, "properties": held}
624
628
  if editable != null:
625
- answer["editableChildren"] = Read.as_bool(editable)
629
+ answer["editableChildren"] = GdhRead.as_bool(editable)
626
630
  if not dropped.is_empty():
627
631
  answer["droppedValues"] = dropped["values"]
628
632
  answer["droppedNodes"] = dropped["nodes"]
629
633
  if new_path != old_path:
630
- answer.merge(Kin.follow(scene_path, old_path, new_path, carried))
634
+ answer.merge(GdhKin.follow(scene_path, old_path, new_path, carried))
631
635
  return answer
632
636
 
633
637
 
@@ -635,10 +639,10 @@ func get_node_properties(args: Dictionary) -> Dictionary:
635
639
  var project_path: String = str(args.get("projectPath", ""))
636
640
  var scene_path: String = _to_scene_res_path(project_path, str(args.get("scenePath", "")))
637
641
  var node_path: String = str(args.get("nodePath", "."))
638
- var include_defaults: bool = Read.as_bool(args.get("includeDefaults", false))
642
+ var include_defaults: bool = GdhRead.as_bool(args.get("includeDefaults", false))
639
643
 
640
644
  var opened: Array = _open(scene_path, false)
641
- var scene: SceneFile = opened[0]
645
+ var scene: GdhSceneFile = opened[0]
642
646
  var refused: Dictionary = opened[1]
643
647
  if not refused.is_empty():
644
648
  return refused
@@ -652,7 +656,7 @@ func get_node_properties(args: Dictionary) -> Dictionary:
652
656
  # variable read as changed; and a value this scene sets back to the default was hidden, so the
653
657
  # names this scene's file sets are always answered.
654
658
  var props: Dictionary = _properties_of(node, include_defaults, scene.set_here(node))
655
- var cells: Dictionary = ResourceTools.cells_of(node)
659
+ var cells: Dictionary = GdhResourceTools.cells_of(node)
656
660
  var answer: Dictionary = {"ok": true, "nodePath": node_path, "properties": props}
657
661
  if node != scene.root and node.owner != null and not node.scene_file_path.is_empty():
658
662
  answer["instanceOf"] = node.scene_file_path
@@ -660,7 +664,7 @@ func get_node_properties(args: Dictionary) -> Dictionary:
660
664
  scene.close()
661
665
  # Only an editor holds anything unsaved; a headless engine answering false would say it looked.
662
666
  if Engine.is_editor_hint():
663
- answer["unsavedInEditor"] = SceneFile.unsaved(scene_path)
667
+ answer["unsavedInEditor"] = GdhSceneFile.unsaved(scene_path)
664
668
  answer.merge(cells)
665
669
  # An empty answer read as a node with nothing on it, rather than one with nothing changed.
666
670
  if props.is_empty() and not include_defaults:
@@ -680,7 +684,7 @@ func save_scene(args: Dictionary) -> Dictionary:
680
684
  target_path = _to_scene_res_path(project_path, new_path_raw)
681
685
 
682
686
  if target_path != scene_path:
683
- var unsaved: String = SceneFile.unsaved_in_editor(target_path)
687
+ var unsaved: String = GdhSceneFile.unsaved_in_editor(target_path)
684
688
  if not unsaved.is_empty():
685
689
  return {"ok": false, "error": unsaved}
686
690
 
@@ -703,9 +707,9 @@ func save_scene(args: Dictionary) -> Dictionary:
703
707
  var packing: Error = packed.pack(open_root)
704
708
  if packing != OK:
705
709
  return {"ok": false, "error": "Failed to pack %s: %s" % [scene_path, error_string(packing)]}
706
- written = SceneFile.save_packed(packed, target_path)
710
+ written = GdhSceneFile.save_packed(packed, target_path)
707
711
  else:
708
- var scene: SceneFile = SceneFile.new()
712
+ var scene: GdhSceneFile = GdhSceneFile.new()
709
713
  var refused: Dictionary = scene.open(scene_path, true)
710
714
  if not refused.is_empty():
711
715
  return refused
@@ -743,7 +747,7 @@ func _save_open_scene(scene_path: String) -> Dictionary:
743
747
  "scenePath": scene_path,
744
748
  "savedPath": scene_path,
745
749
  "from": "editor",
746
- "unsavedInEditor": SceneFile.unsaved(scene_path)
750
+ "unsavedInEditor": GdhSceneFile.unsaved(scene_path)
747
751
  }
748
752
 
749
753
 
@@ -756,7 +760,7 @@ func connect_signal(args: Dictionary) -> Dictionary:
756
760
  var method_name: String = str(args.get("methodName", ""))
757
761
  # A connection without CONNECT_PERSIST is a runtime one, and PackedScene.pack drops those on
758
762
  # the way out: without this the scene saves unchanged and this answers success over nothing.
759
- var flags: int = Read.as_int(args.get("flags", 0)) | Object.CONNECT_PERSIST
763
+ var flags: int = GdhRead.as_int(args.get("flags", 0)) | Object.CONNECT_PERSIST
760
764
 
761
765
  if (
762
766
  source_node_path.is_empty()
@@ -767,7 +771,7 @@ func connect_signal(args: Dictionary) -> Dictionary:
767
771
  return {"ok": false, "error": "Missing required signal connection arguments"}
768
772
 
769
773
  var opened: Array = _open(scene_path, true)
770
- var scene: SceneFile = opened[0]
774
+ var scene: GdhSceneFile = opened[0]
771
775
  var refused: Dictionary = opened[1]
772
776
  if not refused.is_empty():
773
777
  return refused
@@ -785,7 +789,7 @@ func connect_signal(args: Dictionary) -> Dictionary:
785
789
  var flags_before: int = -1
786
790
  for connection: Dictionary in source.get_signal_connection_list(signal_name):
787
791
  if connection.get("callable") == callable:
788
- flags_before = Read.as_int(connection.get("flags", 0))
792
+ flags_before = GdhRead.as_int(connection.get("flags", 0))
789
793
  if source.is_connected(signal_name, callable):
790
794
  source.disconnect(signal_name, callable)
791
795
  var connect_result: Error = source.connect(signal_name, callable, flags)
@@ -812,7 +816,7 @@ func connect_signal(args: Dictionary) -> Dictionary:
812
816
  "targetNodePath": to,
813
817
  "methodName": method_name,
814
818
  "flags": saved_flags[0],
815
- "flagNames": SceneFile.flag_names(Read.as_int(saved_flags[0])),
819
+ "flagNames": GdhSceneFile.flag_names(GdhRead.as_int(saved_flags[0])),
816
820
  # Connected either way, as the editor's own dialog allows, since the method may be written
817
821
  # next; a caller told nothing learnt of a misspelt one only when the signal fired.
818
822
  "methodFound": method_found
@@ -822,14 +826,14 @@ func connect_signal(args: Dictionary) -> Dictionary:
822
826
  # looked like a lost write.
823
827
  if flags_before >= 0:
824
828
  answer["alreadyConnected"] = true
825
- if flags_before != Read.as_int(saved_flags[0]):
826
- answer["flagsBefore"] = SceneFile.flag_names(flags_before)
829
+ if flags_before != GdhRead.as_int(saved_flags[0]):
830
+ answer["flagsBefore"] = GdhSceneFile.flag_names(flags_before)
827
831
  return answer
828
832
 
829
833
 
830
834
  ## The two ends of a connection in [param scene], as [source, target], or [null, why not].
831
835
  func _connection_ends(
832
- scene: SceneFile, source_node_path: String, signal_name: String, target_node_path: String
836
+ scene: GdhSceneFile, source_node_path: String, signal_name: String, target_node_path: String
833
837
  ) -> Array:
834
838
  var source: Node = _find_node(scene.root, source_node_path)
835
839
  var target: Node = _find_node(scene.root, target_node_path)
@@ -856,7 +860,7 @@ func _connection_flags(root: Node, from: String, signal_name: String, to: String
856
860
  if source == null or target == null or not source.has_signal(signal_name):
857
861
  return -1
858
862
  for conn: Dictionary in source.get_signal_connection_list(signal_name):
859
- var flags: int = Read.as_int(conn.get("flags", 0))
863
+ var flags: int = GdhRead.as_int(conn.get("flags", 0))
860
864
  var callable: Callable = conn.get("callable", Callable())
861
865
  if (
862
866
  flags & Object.CONNECT_PERSIST
@@ -884,7 +888,7 @@ func disconnect_signal(args: Dictionary) -> Dictionary:
884
888
  return {"ok": false, "error": "Missing required signal disconnection arguments"}
885
889
 
886
890
  var opened: Array = _open(scene_path, true)
887
- var scene: SceneFile = opened[0]
891
+ var scene: GdhSceneFile = opened[0]
888
892
  var refused: Dictionary = opened[1]
889
893
  if not refused.is_empty():
890
894
  return refused
@@ -929,7 +933,7 @@ func list_connections(args: Dictionary) -> Dictionary:
929
933
  var filter_path: String = str(args.get("nodePath", ""))
930
934
 
931
935
  var opened: Array = _open(scene_path, false)
932
- var scene: SceneFile = opened[0]
936
+ var scene: GdhSceneFile = opened[0]
933
937
  var refused: Dictionary = opened[1]
934
938
  if not refused.is_empty():
935
939
  return refused
@@ -955,7 +959,7 @@ func list_connections(args: Dictionary) -> Dictionary:
955
959
  if signal_name.is_empty():
956
960
  continue
957
961
  for conn: Dictionary in node.get_signal_connection_list(signal_name):
958
- var flags: int = Read.as_int(conn.get("flags", 0))
962
+ var flags: int = GdhRead.as_int(conn.get("flags", 0))
959
963
  if not flags & Object.CONNECT_PERSIST:
960
964
  continue
961
965
  var callable: Callable = conn.get("callable", Callable())
@@ -976,7 +980,7 @@ func list_connections(args: Dictionary) -> Dictionary:
976
980
  "targetNodePath": target_path,
977
981
  "methodName": str(callable.get_method()),
978
982
  "flags": flags,
979
- "flagNames": SceneFile.flag_names(flags),
983
+ "flagNames": GdhSceneFile.flag_names(flags),
980
984
  # False for a connection an instanced scene makes inside itself, which only that
981
985
  # scene can change.
982
986
  "editable": editable
@@ -987,5 +991,5 @@ func list_connections(args: Dictionary) -> Dictionary:
987
991
  scene.close()
988
992
  var listed: Dictionary = {"ok": true, "connections": connections}
989
993
  if Engine.is_editor_hint():
990
- listed["unsavedInEditor"] = SceneFile.unsaved(scene_path)
994
+ listed["unsavedInEditor"] = GdhSceneFile.unsaved(scene_path)
991
995
  return listed
@@ -14,10 +14,10 @@ extends Node
14
14
  ## question asked of two places: a value spelled differently here would read as the editor and the
15
15
  ## file disagreeing about something they agree about.
16
16
 
17
- const Read = preload("../reading.gd")
18
- const Serialisation = preload("../serialisation.gd")
17
+ const GdhRead = preload("../reading.gd")
18
+ const GdhSerialisation = preload("../serialisation.gd")
19
19
 
20
- var _values: Serialisation = Serialisation.new()
20
+ var _values: GdhSerialisation = GdhSerialisation.new()
21
21
 
22
22
 
23
23
  func get_project_setting(args: Dictionary) -> Dictionary:
@@ -47,7 +47,7 @@ func _settings_under(prefix: String) -> Dictionary:
47
47
  var setting: String = str(property.get("name", ""))
48
48
  if not setting.begins_with(prefix):
49
49
  continue
50
- var kind: int = Read.as_int(property.get("type", TYPE_NIL), TYPE_NIL)
50
+ var kind: int = GdhRead.as_int(property.get("type", TYPE_NIL), TYPE_NIL)
51
51
  var described: Dictionary = {
52
52
  "setting": setting,
53
53
  "type": type_string(kind),
@@ -0,0 +1,111 @@
1
+ extends RefCounted
2
+
3
+ ## How each engine type built from fields is written: the fields it needs, the ones it reads when
4
+ ## they are there, and what a refusal says about a value that is not one.
5
+ ##
6
+ ## This file is the one beside the operations, and `bun run sync:gd` copies it into each addon.
7
+
8
+ # What each tag has to carry before it is built. A tag naming a type whose keys are not there is a
9
+ # tag somebody wrote by mistake or wrote for a different shape, and building one out of defaults
10
+ # answers with a Transform2D of zeros where the caller meant something else entirely. Unbuilt, it
11
+ # comes back as it arrived, and whoever asked can read it against the type they were expecting.
12
+ const REQUIRED: Dictionary = {
13
+ "Vector2": [["x", "y"]],
14
+ "Vector2i": [["x", "y"]],
15
+ "Vector3": [["x", "y", "z"]],
16
+ "Vector3i": [["x", "y", "z"]],
17
+ "Vector4": [["x", "y", "z", "w"]],
18
+ "Vector4i": [["x", "y", "z", "w"]],
19
+ "Rect2": [["position", "size"], ["x", "y", "width", "height"]],
20
+ "Rect2i": [["position", "size"], ["x", "y", "width", "height"]],
21
+ "Plane": [["normal", "d"]],
22
+ "Quaternion": [["x", "y", "z", "w"]],
23
+ "AABB": [["position", "size"]],
24
+ "Basis": [["x", "y", "z"]],
25
+ "Transform2D": [["x", "y", "origin"]],
26
+ "Transform3D": [["basis", "origin"]],
27
+ "Projection": [["x", "y", "z", "w"]],
28
+ "Color": [["r", "g", "b"]],
29
+ "NodePath": [["path"]],
30
+ "PackedByteArray": [["base64"]],
31
+ "float": [["value"]],
32
+ "int": [["value"]],
33
+ "RID": [["id"]],
34
+ "Dictionary": [["entries"]],
35
+ }
36
+ ## The fields a builder reads when they are there and does without when not.
37
+ const OPTIONAL: Dictionary = {
38
+ "Color": ["a"],
39
+ "PackedByteArray": ["size"],
40
+ }
41
+
42
+
43
+ ## How a value of [param type] is written to be read as one, for a refusal to name, or empty for a
44
+ ## type that is not built from fields. A refusal naming only the two types leaves a caller to find
45
+ ## the shape in the description of another op.
46
+ static func written_as(type: int) -> String:
47
+ var tag: String = type_string(type)
48
+ var alternatives: Array = REQUIRED.get(tag, [])
49
+ if alternatives.is_empty() or tag in ["float", "int", "Dictionary"]:
50
+ return ""
51
+ var keys: Array = alternatives[0]
52
+ var optional: Array = OPTIONAL.get(tag, [])
53
+ var parts: Array[String] = []
54
+ for key: Variant in keys + optional:
55
+ parts.append('"%s": ...' % key)
56
+ var shape: String = "{%s}" % ", ".join(parts)
57
+ if type == TYPE_COLOR:
58
+ return shape + ' or as its html code, "#ff8000"'
59
+ return shape
60
+
61
+
62
+ ## The keys of [param fields] that a [param tag] built from them would not read, one list for each
63
+ ## spelling of its fields carried whole. A rectangle has two: its corners, and four numbers.
64
+ ##
65
+ ## A builder drops a key it does not know without a word, so {"x": 1, "y": 2, "z": 3} for a Vector2
66
+ ## would be written as (1, 2) and answered as written: a value is built only when a list is empty.
67
+ static func spare(fields: Dictionary, tag: String) -> Array:
68
+ var optional: Array = OPTIONAL.get(tag, [])
69
+ var lists: Array = []
70
+ var alternatives: Array = REQUIRED.get(tag, [])
71
+ for alternative: Variant in alternatives:
72
+ var keys: Array = alternative
73
+ if keys.all(func(key: Variant) -> bool: return fields.has(key)):
74
+ var over: Callable = func(key: Variant) -> bool:
75
+ return str(key) != "_type" and not keys.has(key) and not optional.has(key)
76
+ lists.append(fields.keys().filter(over))
77
+ return lists
78
+
79
+
80
+ ## The sentences a refusal of [param value] where a [param type] goes ends with: which of its keys
81
+ ## kept it from being read as one, and how one is written. Empty for a type not built from fields.
82
+ ##
83
+ ## The refusal names only the two types, and the key is what the caller has to take out.
84
+ static func remedy(value: Variant, type: int) -> String:
85
+ var shape: String = written_as(type)
86
+ if shape.is_empty():
87
+ return ""
88
+ return _unread(value, type) + " A %s is sent as %s." % [type_string(type), shape]
89
+
90
+
91
+ static func _unread(value: Variant, type: int) -> String:
92
+ if not value is Dictionary:
93
+ return ""
94
+ var fields: Dictionary = value
95
+ var tag: String = type_string(type)
96
+ var lists: Array = spare(fields, tag)
97
+ var built: bool = lists.any(func(over: Array) -> bool: return over.is_empty())
98
+ if lists.is_empty() or built or str(fields.get("_type", tag)) != tag:
99
+ return ""
100
+ lists.sort_custom(func(a: Array, b: Array) -> bool: return a.size() < b.size())
101
+ var left: Array = lists[0]
102
+ var named: PackedStringArray = []
103
+ for key: Variant in left:
104
+ var _named: bool = named.append('"%s"' % key)
105
+ var fields_called: String = "field called" if named.size() == 1 else "fields called"
106
+ var note: String = (
107
+ " A %s has no %s %s, so the value is not read as one" % [tag, fields_called, " or ".join(named)]
108
+ )
109
+ if left.has("type") and not fields.has("_type"):
110
+ return note + ': a value names its type under "_type".'
111
+ return note + "."
@@ -0,0 +1 @@
1
+ uid://c3dfdk66o90zz