gdharness 1.2.29 → 1.2.31
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 +80 -24
- package/build/godot/addons/gdharness_editor/property_values.gd +80 -54
- package/build/godot/addons/gdharness_editor/serialisation.gd +46 -0
- package/build/godot/addons/gdharness_editor/tools/resource_tools.gd +78 -3
- package/build/godot/addons/gdharness_editor/tools/scene_tools.gd +19 -4
- package/build/godot/addons/gdharness_runtime/runtime_changes.gd +139 -91
- package/build/godot/addons/gdharness_runtime/runtime_paths.gd +5 -4
- package/build/godot/addons/gdharness_runtime/runtime_values.gd +23 -47
- package/build/godot/addons/gdharness_runtime/runtime_waits.gd +1 -13
- 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/project_config.gd +16 -6
- package/build/godot/operations/serialisation.gd +46 -0
- package/build/index.js +80 -24
- package/package.json +1 -1
|
@@ -5,6 +5,7 @@ extends Node
|
|
|
5
5
|
|
|
6
6
|
const Read = preload("../reading.gd")
|
|
7
7
|
const PropertyValues = preload("../property_values.gd")
|
|
8
|
+
const ResourceTools = preload("resource_tools.gd")
|
|
8
9
|
const SceneFile = preload("../scene_file.gd")
|
|
9
10
|
const Serialisation = preload("../serialisation.gd")
|
|
10
11
|
|
|
@@ -101,8 +102,8 @@ func _kept(scene: SceneFile, saved: Node, node: Node, names: Array) -> String:
|
|
|
101
102
|
return "%s is not in the scene as it would be saved." % at
|
|
102
103
|
for key: Variant in names:
|
|
103
104
|
var property: String = str(key)
|
|
104
|
-
var loaded: Variant =
|
|
105
|
-
if not scene.kept(saved, loaded,
|
|
105
|
+
var loaded: Variant = _properties.read_at(copy, property)
|
|
106
|
+
if not scene.kept(saved, loaded, _properties.read_at(node, property)):
|
|
106
107
|
return (
|
|
107
108
|
(
|
|
108
109
|
"%s.%s would load from the file as %s: the scene does not keep it, because the property "
|
|
@@ -601,7 +602,7 @@ func set_node_properties(args: Dictionary) -> Dictionary:
|
|
|
601
602
|
|
|
602
603
|
var held: Dictionary = {}
|
|
603
604
|
for key: Variant in properties:
|
|
604
|
-
held[str(key)] = _readable(node,
|
|
605
|
+
held[str(key)] = _readable(node, _properties.read_at(node, str(key)))
|
|
605
606
|
var new_path: String = scene.path_of(node)
|
|
606
607
|
var written: Dictionary = scene.write(
|
|
607
608
|
func(saved: Node) -> String: return _kept(scene, saved, node, properties.keys())
|
|
@@ -633,6 +634,7 @@ func get_node_properties(args: Dictionary) -> Dictionary:
|
|
|
633
634
|
# variable read as changed; and a value this scene sets back to the default was hidden, so the
|
|
634
635
|
# names this scene's file sets are always answered.
|
|
635
636
|
var props: Dictionary = _properties_of(node, include_defaults, scene.set_here(node))
|
|
637
|
+
var cells: Dictionary = ResourceTools.cells_of(node)
|
|
636
638
|
scene.close()
|
|
637
639
|
var answer: Dictionary = {
|
|
638
640
|
"ok": true,
|
|
@@ -640,6 +642,7 @@ func get_node_properties(args: Dictionary) -> Dictionary:
|
|
|
640
642
|
"properties": props,
|
|
641
643
|
"unsavedInEditor": SceneFile.unsaved(scene_path)
|
|
642
644
|
}
|
|
645
|
+
answer.merge(cells)
|
|
643
646
|
# An empty answer read as a node with nothing on it, rather than one with nothing changed.
|
|
644
647
|
if props.is_empty() and not include_defaults:
|
|
645
648
|
answer["note"] = (
|
|
@@ -757,6 +760,10 @@ func connect_signal(args: Dictionary) -> Dictionary:
|
|
|
757
760
|
# asked for. Left alone, the answer gave the flags asked for and the file kept the old ones.
|
|
758
761
|
var callable: Callable = Callable(target, method_name)
|
|
759
762
|
var method_found: bool = target.has_method(method_name)
|
|
763
|
+
var flags_before: int = -1
|
|
764
|
+
for connection: Dictionary in source.get_signal_connection_list(signal_name):
|
|
765
|
+
if connection.get("callable") == callable:
|
|
766
|
+
flags_before = Read.as_int(connection.get("flags", 0))
|
|
760
767
|
if source.is_connected(signal_name, callable):
|
|
761
768
|
source.disconnect(signal_name, callable)
|
|
762
769
|
var connect_result: Error = source.connect(signal_name, callable, flags)
|
|
@@ -776,7 +783,7 @@ func connect_signal(args: Dictionary) -> Dictionary:
|
|
|
776
783
|
if not written.is_empty():
|
|
777
784
|
return written
|
|
778
785
|
|
|
779
|
-
|
|
786
|
+
var answer: Dictionary = {
|
|
780
787
|
"ok": true,
|
|
781
788
|
"sourceNodePath": from,
|
|
782
789
|
"signalName": signal_name,
|
|
@@ -788,6 +795,14 @@ func connect_signal(args: Dictionary) -> Dictionary:
|
|
|
788
795
|
# next; a caller told nothing learnt of a misspelt one only when the signal fired.
|
|
789
796
|
"methodFound": method_found
|
|
790
797
|
}
|
|
798
|
+
# A second connect of the same pair read exactly like the first, so a caller could not tell a
|
|
799
|
+
# connection made from one that was already there, and a list showing one where two were made
|
|
800
|
+
# looked like a lost write.
|
|
801
|
+
if flags_before >= 0:
|
|
802
|
+
answer["alreadyConnected"] = true
|
|
803
|
+
if flags_before != Read.as_int(saved_flags[0]):
|
|
804
|
+
answer["flagsBefore"] = _flag_names(flags_before)
|
|
805
|
+
return answer
|
|
791
806
|
|
|
792
807
|
|
|
793
808
|
## The two ends of a connection in [param scene], as [source, target], or [null, why not].
|
|
@@ -61,65 +61,13 @@ func set_property(params: Dictionary) -> Dictionary:
|
|
|
61
61
|
if not Paths.can_read(holder, named):
|
|
62
62
|
return {"type": "error", "message": Paths.nothing_there(holder, named, str(reached["called"]))}
|
|
63
63
|
var old_value: Variant = Paths.read_under(holder, named)
|
|
64
|
-
# The same rule the call path holds: a value that cannot become what the property holds is
|
|
65
|
-
# refused rather than written. Writing it means the engine picks something, and what it picks
|
|
66
|
-
# for a word where a number goes is zero, which the answer then reports as the new value.
|
|
67
|
-
#
|
|
68
|
-
# A slot declaring nothing, an untyped or Variant property or an element of an untyped list, takes
|
|
69
|
-
# anything, and the engine stores what it is given there. What it holds now still says how a value
|
|
70
|
-
# of the same kind is meant, 5 into an int staying an int, and whether a path names an object, but
|
|
71
|
-
# a value of another kind is stored as sent: a word into one holding a number is the word, and a
|
|
72
|
-
# number into one holding a word is the number, not its text. The text null empties it when it
|
|
73
|
-
# holds an object or nothing, as a wait reads it on the same slot.
|
|
74
64
|
var slot: Dictionary = Values.slot_declared(holder, named)
|
|
75
65
|
var declared: int = slot["type"]
|
|
76
66
|
var declared_script: Script = slot["script"]
|
|
77
|
-
var
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
var given: Variant
|
|
82
|
-
if holds_objects and names_no_object(value):
|
|
83
|
-
given = null
|
|
84
|
-
elif wanted == TYPE_OBJECT and (value is String or value is Dictionary):
|
|
85
|
-
var named_object: Dictionary = _object_named(
|
|
86
|
-
node, node_path, value, str(slot["class"]), declared_script
|
|
87
|
-
)
|
|
88
|
-
if named_object.has("message"):
|
|
89
|
-
return {
|
|
90
|
-
"type": "error",
|
|
91
|
-
"message": "%s.%s holds an object: %s" % [reached["called"], named, named_object["message"]]
|
|
92
|
-
}
|
|
93
|
-
given = named_object["object"]
|
|
94
|
-
else:
|
|
95
|
-
var as_sent: Variant = _values.fitted(value, TYPE_NIL)
|
|
96
|
-
if declared == TYPE_NIL and not Values.acceptable(as_sent, wanted):
|
|
97
|
-
given = as_sent
|
|
98
|
-
else:
|
|
99
|
-
given = _values.fitted(value, wanted)
|
|
100
|
-
if not Values.acceptable(given, wanted):
|
|
101
|
-
return {
|
|
102
|
-
"type": "error",
|
|
103
|
-
"message":
|
|
104
|
-
(
|
|
105
|
-
"%s.%s holds %s and the value given is %s, which cannot become one.%s"
|
|
106
|
-
% [
|
|
107
|
-
reached["called"],
|
|
108
|
-
named,
|
|
109
|
-
type_string(wanted),
|
|
110
|
-
type_string(typeof(given)),
|
|
111
|
-
_written_as(wanted)
|
|
112
|
-
]
|
|
113
|
-
)
|
|
114
|
-
}
|
|
115
|
-
var typed: Dictionary = _values.typed_like(given, old_value, _element_object.bind(node, node_path))
|
|
116
|
-
if typed.has("message"):
|
|
117
|
-
return {
|
|
118
|
-
"type": "error",
|
|
119
|
-
"message":
|
|
120
|
-
"%s.%s is a typed %s: %s." % [reached["called"], named, _sort_of(old_value), typed["message"]]
|
|
121
|
-
}
|
|
122
|
-
given = typed["value"]
|
|
67
|
+
var fitting: Dictionary = _value_for(value, slot, old_value, reached, node, node_path)
|
|
68
|
+
if fitting.has("message"):
|
|
69
|
+
return fitting
|
|
70
|
+
var given: Variant = fitting["value"]
|
|
123
71
|
# A component of a vector or a transform is written into a copy, which is put back where it was
|
|
124
72
|
# read from, setter and all; the read back is then taken off the game again, since the copy here
|
|
125
73
|
# says only what was written into it.
|
|
@@ -201,6 +149,131 @@ func set_property(params: Dictionary) -> Dictionary:
|
|
|
201
149
|
return answer
|
|
202
150
|
|
|
203
151
|
|
|
152
|
+
## What [param sent] becomes as argument [param index] of the method [param named] on the holder
|
|
153
|
+
## [param reached] ends at, as [method Paths.walk_to] answered it, as `{"value": ...}`, or the refusal
|
|
154
|
+
## saying why it cannot become one: a number the parameter would have to cut, a value that cannot be
|
|
155
|
+
## converted, or a list or map that cannot be built as the typed one the parameter takes.
|
|
156
|
+
func _argument_for(
|
|
157
|
+
sent: Variant, index: int, reached: Dictionary, named: String, node: Node, node_path: String
|
|
158
|
+
) -> Dictionary:
|
|
159
|
+
var holder: Object = reached["holder"]
|
|
160
|
+
var called: String = reached["called"]
|
|
161
|
+
var wants: int = _values.parameter_type(holder, named, index)
|
|
162
|
+
if Values.cuts(sent, wants):
|
|
163
|
+
return {
|
|
164
|
+
"type": "error",
|
|
165
|
+
"message":
|
|
166
|
+
(
|
|
167
|
+
"%s.%s takes %s as argument %d and was given %s, which it would have to be cut to fit"
|
|
168
|
+
% [called, named, type_string(wants), index + 1, JSON.stringify(sent)]
|
|
169
|
+
)
|
|
170
|
+
}
|
|
171
|
+
var given: Variant = _values.fitted(sent, wants)
|
|
172
|
+
if not Values.acceptable(given, wants):
|
|
173
|
+
return {
|
|
174
|
+
"type": "error",
|
|
175
|
+
"message":
|
|
176
|
+
(
|
|
177
|
+
"%s.%s takes %s as argument %d and was given %s, which cannot be converted.%s"
|
|
178
|
+
% [
|
|
179
|
+
called,
|
|
180
|
+
named,
|
|
181
|
+
type_string(wants),
|
|
182
|
+
index + 1,
|
|
183
|
+
type_string(typeof(given)),
|
|
184
|
+
_written_as(wants)
|
|
185
|
+
]
|
|
186
|
+
)
|
|
187
|
+
}
|
|
188
|
+
# A typed list or map is built as one, since a plain one handed to such a parameter raises
|
|
189
|
+
# inside the game, which is what every check here exists to stop.
|
|
190
|
+
var container: Variant = _values.parameter_container(holder, named, index)
|
|
191
|
+
if container == null:
|
|
192
|
+
return {"value": given}
|
|
193
|
+
var typed: Dictionary = _values.typed_like(given, container, _element_object.bind(node, node_path))
|
|
194
|
+
if typed.has("message"):
|
|
195
|
+
return {
|
|
196
|
+
"type": "error",
|
|
197
|
+
"message":
|
|
198
|
+
(
|
|
199
|
+
"%s.%s takes a typed %s as argument %d: %s."
|
|
200
|
+
% [called, named, _sort_of(container), index + 1, typed["message"]]
|
|
201
|
+
)
|
|
202
|
+
}
|
|
203
|
+
return {"value": typed["value"]}
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
## What [param value] becomes written into the slot [param reached] ends on, as
|
|
207
|
+
## [method Paths.walk_to] answered it, as `{"value": ...}`, or the refusal saying why it cannot become
|
|
208
|
+
## one.
|
|
209
|
+
##
|
|
210
|
+
## The same rule the call path holds: a value that cannot become what the property holds is
|
|
211
|
+
## refused rather than written. Writing it means the engine picks something, and what it picks
|
|
212
|
+
## for a word where a number goes is zero, which the answer then reports as the new value. So is a
|
|
213
|
+
## number a whole number or a truth would have to cut, since converting one truncates.
|
|
214
|
+
##
|
|
215
|
+
## A slot declaring nothing, an untyped or Variant property or an element of an untyped list, takes
|
|
216
|
+
## anything, and the engine stores what it is given there. What it holds now still says how a value
|
|
217
|
+
## of the same kind is meant, 5 into an int staying an int, and whether a path names an object, but
|
|
218
|
+
## a value of another kind is stored as sent: a word into one holding a number is the word, a
|
|
219
|
+
## fraction into one holding a whole number is the fraction, and a number into one holding a word is
|
|
220
|
+
## the number, not its text. The text null empties it when it holds an object or nothing, as a wait
|
|
221
|
+
## reads it on the same slot.
|
|
222
|
+
func _value_for(
|
|
223
|
+
value: Variant, slot: Dictionary, old_value: Variant, reached: Dictionary, node: Node, node_path: String
|
|
224
|
+
) -> Dictionary:
|
|
225
|
+
var named: String = reached["name"]
|
|
226
|
+
var called: String = reached["called"]
|
|
227
|
+
var declared: int = slot["type"]
|
|
228
|
+
var declared_script: Script = slot["script"]
|
|
229
|
+
var wanted: int = declared if declared != TYPE_NIL else typeof(old_value)
|
|
230
|
+
var holds_objects: bool = (
|
|
231
|
+
declared == TYPE_OBJECT or (declared == TYPE_NIL and (old_value == null or old_value is Object))
|
|
232
|
+
)
|
|
233
|
+
if holds_objects and names_no_object(value):
|
|
234
|
+
return {"value": null}
|
|
235
|
+
if wanted == TYPE_OBJECT and (value is String or value is Dictionary):
|
|
236
|
+
var named_object: Dictionary = _object_named(
|
|
237
|
+
node, node_path, value, str(slot["class"]), declared_script
|
|
238
|
+
)
|
|
239
|
+
if named_object.has("message"):
|
|
240
|
+
return {
|
|
241
|
+
"type": "error",
|
|
242
|
+
"message": "%s.%s holds an object: %s" % [called, named, named_object["message"]]
|
|
243
|
+
}
|
|
244
|
+
return {"value": named_object["object"]}
|
|
245
|
+
var as_sent: Variant = _values.fitted(value, TYPE_NIL)
|
|
246
|
+
var cut: bool = Values.cuts(value, wanted)
|
|
247
|
+
var given: Variant = as_sent
|
|
248
|
+
if declared != TYPE_NIL and cut:
|
|
249
|
+
return {
|
|
250
|
+
"type": "error",
|
|
251
|
+
"message":
|
|
252
|
+
(
|
|
253
|
+
"%s.%s holds %s and the value given is %s, which it would have to be cut to fit"
|
|
254
|
+
% [called, named, type_string(wanted), JSON.stringify(value)]
|
|
255
|
+
)
|
|
256
|
+
}
|
|
257
|
+
if declared != TYPE_NIL or (not cut and Values.acceptable(as_sent, wanted)):
|
|
258
|
+
given = _values.fitted(value, wanted)
|
|
259
|
+
if not Values.acceptable(given, wanted):
|
|
260
|
+
return {
|
|
261
|
+
"type": "error",
|
|
262
|
+
"message":
|
|
263
|
+
(
|
|
264
|
+
"%s.%s holds %s and the value given is %s, which cannot become one.%s"
|
|
265
|
+
% [called, named, type_string(wanted), type_string(typeof(given)), _written_as(wanted)]
|
|
266
|
+
)
|
|
267
|
+
}
|
|
268
|
+
var typed: Dictionary = _values.typed_like(given, old_value, _element_object.bind(node, node_path))
|
|
269
|
+
if typed.has("message"):
|
|
270
|
+
return {
|
|
271
|
+
"type": "error",
|
|
272
|
+
"message": "%s.%s is a typed %s: %s." % [called, named, _sort_of(old_value), typed["message"]]
|
|
273
|
+
}
|
|
274
|
+
return {"value": typed["value"]}
|
|
275
|
+
|
|
276
|
+
|
|
204
277
|
## Whether [param given] is the value [param held] already is, at the precision the property keeps.
|
|
205
278
|
##
|
|
206
279
|
## A float arrives as 64 bits and most engine properties keep 32: Camera3D.h_offset given -1.1
|
|
@@ -254,6 +327,12 @@ func call_method(params: Dictionary) -> Dictionary:
|
|
|
254
327
|
var spelled: String = str(reached["name"])
|
|
255
328
|
if Paths.method_of(spelled).is_empty():
|
|
256
329
|
spelled += "()"
|
|
330
|
+
# A vector or a transform has no element to call a method on: it is read by its components.
|
|
331
|
+
if Paths.composite(reached["holder"]):
|
|
332
|
+
return {
|
|
333
|
+
"type": "error",
|
|
334
|
+
"message": Paths.nothing_there(reached["holder"], spelled, str(reached["called"]))
|
|
335
|
+
}
|
|
257
336
|
if args.is_empty() and Paths.can_read(reached["holder"], spelled):
|
|
258
337
|
var read_from: int = Time.get_ticks_usec()
|
|
259
338
|
var answered: Variant = Paths.read_under(reached["holder"], spelled)
|
|
@@ -320,41 +399,10 @@ func call_method(params: Dictionary) -> Dictionary:
|
|
|
320
399
|
by_engine_class[index + 1] = [declared, Values.class_of(resolved)]
|
|
321
400
|
deserialized_args.append(named_object["object"])
|
|
322
401
|
continue
|
|
323
|
-
var
|
|
324
|
-
if
|
|
325
|
-
return
|
|
326
|
-
|
|
327
|
-
"message":
|
|
328
|
-
(
|
|
329
|
-
"%s.%s takes %s as argument %d and was given %s, which cannot be converted.%s"
|
|
330
|
-
% [
|
|
331
|
-
reached["called"],
|
|
332
|
-
named,
|
|
333
|
-
type_string(wants),
|
|
334
|
-
index + 1,
|
|
335
|
-
type_string(typeof(given)),
|
|
336
|
-
_written_as(wants)
|
|
337
|
-
]
|
|
338
|
-
)
|
|
339
|
-
}
|
|
340
|
-
# A typed list or map is built as one, since a plain one handed to such a parameter raises
|
|
341
|
-
# inside the game, which is what every check here exists to stop.
|
|
342
|
-
var container: Variant = _values.parameter_container(holder, named, index)
|
|
343
|
-
if container != null:
|
|
344
|
-
var typed: Dictionary = _values.typed_like(
|
|
345
|
-
given, container, _element_object.bind(node, node_path)
|
|
346
|
-
)
|
|
347
|
-
if typed.has("message"):
|
|
348
|
-
return {
|
|
349
|
-
"type": "error",
|
|
350
|
-
"message":
|
|
351
|
-
(
|
|
352
|
-
"%s.%s takes a typed %s as argument %d: %s."
|
|
353
|
-
% [reached["called"], named, _sort_of(container), index + 1, typed["message"]]
|
|
354
|
-
)
|
|
355
|
-
}
|
|
356
|
-
given = typed["value"]
|
|
357
|
-
deserialized_args.append(given)
|
|
402
|
+
var argument: Dictionary = _argument_for(args[index], index, reached, named, node, node_path)
|
|
403
|
+
if argument.has("message"):
|
|
404
|
+
return argument
|
|
405
|
+
deserialized_args.append(argument["value"])
|
|
358
406
|
|
|
359
407
|
# Timed around the call alone, since the bridge's round trip is a second or more and swamps what
|
|
360
408
|
# one click's work costs; the frame metrics read whichever frame is last, which is not this one.
|
|
@@ -4,6 +4,7 @@ extends RefCounted
|
|
|
4
4
|
## run off the game the node keeps. Every op that reads, writes, calls or waits on a game's state
|
|
5
5
|
## walks the same way, so the walk and what it says when a step is not there live in one place.
|
|
6
6
|
|
|
7
|
+
const Serialisation = preload("serialisation.gd")
|
|
7
8
|
const Values = preload("runtime_values.gd")
|
|
8
9
|
|
|
9
10
|
## The calls a path makes on a list or a map: those that take no arguments and change nothing.
|
|
@@ -95,7 +96,7 @@ const READS_ONLY: String = ", and a path, which a wait reads every frame, only c
|
|
|
95
96
|
##
|
|
96
97
|
## A step into a vector, a colour, a rectangle or a transform reads one of its components,
|
|
97
98
|
## "size:x" or "global_transform:origin:y", by the names GDScript gives them; see
|
|
98
|
-
## [method
|
|
99
|
+
## [method Serialisation.components_of].
|
|
99
100
|
##
|
|
100
101
|
## Answers with a `message` instead when a step along the way is not there or holds something that
|
|
101
102
|
## is not an object, naming the step rather than the whole path: "/root/Main:_game has no property
|
|
@@ -184,7 +185,7 @@ static func can_hold(value: Variant) -> bool:
|
|
|
184
185
|
## Whether [param value] is read by its components, a vector or a colour or a transform, and is a
|
|
185
186
|
## copy wherever it is read from.
|
|
186
187
|
static func composite(value: Variant) -> bool:
|
|
187
|
-
return not
|
|
188
|
+
return not Serialisation.components_of(value).is_empty()
|
|
188
189
|
|
|
189
190
|
|
|
190
191
|
## Whether [param value] is a packed list. Stepped into the way a list is, by index, and a write into
|
|
@@ -234,7 +235,7 @@ static func can_read(holder: Variant, named: String) -> bool:
|
|
|
234
235
|
return args.is_empty() and method in MAP_CALLS
|
|
235
236
|
return not _key_in(map, named).is_empty()
|
|
236
237
|
if composite(holder):
|
|
237
|
-
return method.is_empty() and named in
|
|
238
|
+
return method.is_empty() and named in Serialisation.components_of(holder)
|
|
238
239
|
if holder is Object:
|
|
239
240
|
var object: Object = holder
|
|
240
241
|
if not method.is_empty():
|
|
@@ -586,7 +587,7 @@ static func nothing_there(holder: Variant, named: String, called_as: String) ->
|
|
|
586
587
|
if composite(holder):
|
|
587
588
|
var kind: String = type_string(typeof(holder))
|
|
588
589
|
var sort: String = ("an " if kind.left(1) in ["A", "E", "I", "O", "U"] else "a ") + kind
|
|
589
|
-
var has: String = ", ".join(
|
|
590
|
+
var has: String = ", ".join(Serialisation.components_of(holder))
|
|
590
591
|
if not container_call.is_empty():
|
|
591
592
|
return (
|
|
592
593
|
"%s is %s, which a path reads by its components, %s, and makes no call on"
|
|
@@ -134,6 +134,28 @@ static func acceptable(value: Variant, type: int) -> bool:
|
|
|
134
134
|
return Serialisation.acceptable(value, type)
|
|
135
135
|
|
|
136
136
|
|
|
137
|
+
## Whether [param given] would have to be cut to become a [param type]: a fraction made a whole
|
|
138
|
+
## number, or a number other than 0 or 1 made a truth.
|
|
139
|
+
##
|
|
140
|
+
## A number arrives from JSON as a float, so one is converted to what the slot holds, and the
|
|
141
|
+
## conversion truncates: 2.5 written into an int was stored as 2 and answered as written, and 0.2 on
|
|
142
|
+
## a bool became true. A number written as text counts as the number.
|
|
143
|
+
static func cuts(given: Variant, type: int) -> bool:
|
|
144
|
+
if type != TYPE_INT and type != TYPE_BOOL:
|
|
145
|
+
return false
|
|
146
|
+
var number: Variant = given
|
|
147
|
+
if given is String:
|
|
148
|
+
var text: String = given
|
|
149
|
+
number = Read.json_or_null(text)
|
|
150
|
+
if typeof(number) != TYPE_FLOAT and typeof(number) != TYPE_INT:
|
|
151
|
+
return false
|
|
152
|
+
var value: float = Read.as_float(number)
|
|
153
|
+
var whole: bool = value == floorf(value)
|
|
154
|
+
if type == TYPE_INT:
|
|
155
|
+
return not whole
|
|
156
|
+
return not (whole and (value == 0.0 or value == 1.0))
|
|
157
|
+
|
|
158
|
+
|
|
137
159
|
## How a value of [param type] is written to be read as one, or empty when it is not built from fields.
|
|
138
160
|
static func written_as(type: int) -> String:
|
|
139
161
|
return Serialisation.written_as(type)
|
|
@@ -167,52 +189,6 @@ func parameter_class(object: Object, method: String, index: int) -> String:
|
|
|
167
189
|
return ""
|
|
168
190
|
|
|
169
191
|
|
|
170
|
-
## The components a path reads off [param value], by the names GDScript gives them, or none for a
|
|
171
|
-
## value that has none: a vector's axes, a colour's channels, a rectangle's corners and size, a
|
|
172
|
-
## transform's basis and origin. A value of one of these is a copy wherever it is read, so a write
|
|
173
|
-
## into a component is a write into the copy until it is put back where it was read from.
|
|
174
|
-
static func components_of(value: Variant) -> PackedStringArray:
|
|
175
|
-
match typeof(value):
|
|
176
|
-
TYPE_VECTOR2, TYPE_VECTOR2I:
|
|
177
|
-
return PackedStringArray(["x", "y"])
|
|
178
|
-
TYPE_VECTOR3, TYPE_VECTOR3I:
|
|
179
|
-
return PackedStringArray(["x", "y", "z"])
|
|
180
|
-
TYPE_VECTOR4, TYPE_VECTOR4I, TYPE_QUATERNION:
|
|
181
|
-
return PackedStringArray(["x", "y", "z", "w"])
|
|
182
|
-
TYPE_COLOR:
|
|
183
|
-
return PackedStringArray(
|
|
184
|
-
[
|
|
185
|
-
"r",
|
|
186
|
-
"g",
|
|
187
|
-
"b",
|
|
188
|
-
"a",
|
|
189
|
-
"r8",
|
|
190
|
-
"g8",
|
|
191
|
-
"b8",
|
|
192
|
-
"a8",
|
|
193
|
-
"h",
|
|
194
|
-
"s",
|
|
195
|
-
"v",
|
|
196
|
-
"ok_hsl_h",
|
|
197
|
-
"ok_hsl_s",
|
|
198
|
-
"ok_hsl_l"
|
|
199
|
-
]
|
|
200
|
-
)
|
|
201
|
-
TYPE_RECT2, TYPE_RECT2I, TYPE_AABB:
|
|
202
|
-
return PackedStringArray(["position", "size", "end"])
|
|
203
|
-
TYPE_PLANE:
|
|
204
|
-
return PackedStringArray(["normal", "x", "y", "z", "d"])
|
|
205
|
-
TYPE_BASIS:
|
|
206
|
-
return PackedStringArray(["x", "y", "z"])
|
|
207
|
-
TYPE_TRANSFORM2D:
|
|
208
|
-
return PackedStringArray(["x", "y", "origin"])
|
|
209
|
-
TYPE_TRANSFORM3D:
|
|
210
|
-
return PackedStringArray(["basis", "origin"])
|
|
211
|
-
TYPE_PROJECTION:
|
|
212
|
-
return PackedStringArray(["x", "y", "z", "w"])
|
|
213
|
-
return PackedStringArray()
|
|
214
|
-
|
|
215
|
-
|
|
216
192
|
## What the slot [param named] on [param holder] declares it holds, as `{"type": int, "class":
|
|
217
193
|
## String, "script": Script or null}`: a property's declaration, or the element type of a typed list
|
|
218
194
|
## or the value type of a typed map. TYPE_NIL when nothing is declared, which is also what a
|
|
@@ -228,7 +204,7 @@ static func components_of(value: Variant) -> PackedStringArray:
|
|
|
228
204
|
static func slot_declared(holder: Variant, named: String) -> Dictionary:
|
|
229
205
|
# A component holds one kind of value and no other: a vector's axis is a number, its integer
|
|
230
206
|
# form's an integer, a rectangle's size a vector of the same form.
|
|
231
|
-
if named in components_of(holder):
|
|
207
|
+
if named in Serialisation.components_of(holder):
|
|
232
208
|
var component: Variant = holder[named]
|
|
233
209
|
return {"type": typeof(component), "class": "", "script": null}
|
|
234
210
|
if holder is Array:
|
|
@@ -312,19 +312,7 @@ static func _in_order(compare: String, limit: float, current: Variant) -> bool:
|
|
|
312
312
|
## conversion truncates: 3.5 waited on an int became 3 and 0.2 on a bool became true, and a property
|
|
313
313
|
## already holding that answered met at once, about a state nobody asked for.
|
|
314
314
|
static func _cut_to_fit(given: Variant, type: int, node_path: String, property: String) -> String:
|
|
315
|
-
if
|
|
316
|
-
return ""
|
|
317
|
-
var number: Variant = given
|
|
318
|
-
if given is String:
|
|
319
|
-
var text: String = given
|
|
320
|
-
number = Read.json_or_null(text)
|
|
321
|
-
if typeof(number) != TYPE_FLOAT and typeof(number) != TYPE_INT:
|
|
322
|
-
return ""
|
|
323
|
-
var value: float = Read.as_float(number)
|
|
324
|
-
var whole: bool = value == floorf(value)
|
|
325
|
-
if type == TYPE_INT and whole:
|
|
326
|
-
return ""
|
|
327
|
-
if type == TYPE_BOOL and whole and (value == 0.0 or value == 1.0):
|
|
315
|
+
if not Values.cuts(given, type):
|
|
328
316
|
return ""
|
|
329
317
|
return (
|
|
330
318
|
(
|
|
@@ -246,6 +246,52 @@ func deserialize_value(value: Variant) -> Variant:
|
|
|
246
246
|
return call(builder, fields)
|
|
247
247
|
|
|
248
248
|
|
|
249
|
+
## The components a path reads off [param value], by the names GDScript gives them, or none for a
|
|
250
|
+
## value that has none: a vector's axes, a colour's channels, a rectangle's corners and size, a
|
|
251
|
+
## transform's basis and origin. A value of one of these is a copy wherever it is read, so a write
|
|
252
|
+
## into a component is a write into the copy until it is put back where it was read from.
|
|
253
|
+
static func components_of(value: Variant) -> PackedStringArray:
|
|
254
|
+
match typeof(value):
|
|
255
|
+
TYPE_VECTOR2, TYPE_VECTOR2I:
|
|
256
|
+
return PackedStringArray(["x", "y"])
|
|
257
|
+
TYPE_VECTOR3, TYPE_VECTOR3I:
|
|
258
|
+
return PackedStringArray(["x", "y", "z"])
|
|
259
|
+
TYPE_VECTOR4, TYPE_VECTOR4I, TYPE_QUATERNION:
|
|
260
|
+
return PackedStringArray(["x", "y", "z", "w"])
|
|
261
|
+
TYPE_COLOR:
|
|
262
|
+
return PackedStringArray(
|
|
263
|
+
[
|
|
264
|
+
"r",
|
|
265
|
+
"g",
|
|
266
|
+
"b",
|
|
267
|
+
"a",
|
|
268
|
+
"r8",
|
|
269
|
+
"g8",
|
|
270
|
+
"b8",
|
|
271
|
+
"a8",
|
|
272
|
+
"h",
|
|
273
|
+
"s",
|
|
274
|
+
"v",
|
|
275
|
+
"ok_hsl_h",
|
|
276
|
+
"ok_hsl_s",
|
|
277
|
+
"ok_hsl_l"
|
|
278
|
+
]
|
|
279
|
+
)
|
|
280
|
+
TYPE_RECT2, TYPE_RECT2I, TYPE_AABB:
|
|
281
|
+
return PackedStringArray(["position", "size", "end"])
|
|
282
|
+
TYPE_PLANE:
|
|
283
|
+
return PackedStringArray(["normal", "x", "y", "z", "d"])
|
|
284
|
+
TYPE_BASIS:
|
|
285
|
+
return PackedStringArray(["x", "y", "z"])
|
|
286
|
+
TYPE_TRANSFORM2D:
|
|
287
|
+
return PackedStringArray(["x", "y", "origin"])
|
|
288
|
+
TYPE_TRANSFORM3D:
|
|
289
|
+
return PackedStringArray(["basis", "origin"])
|
|
290
|
+
TYPE_PROJECTION:
|
|
291
|
+
return PackedStringArray(["x", "y", "z", "w"])
|
|
292
|
+
return PackedStringArray()
|
|
293
|
+
|
|
294
|
+
|
|
249
295
|
## Whether [param entries] is a list of [key, value] pairs, as a Dictionary tag carries them.
|
|
250
296
|
static func pairs(entries: Variant) -> bool:
|
|
251
297
|
if not entries is Array:
|
|
@@ -83,7 +83,20 @@ func get_audio_buses(_params: Dictionary) -> Dictionary:
|
|
|
83
83
|
var buses: Array[Dictionary] = []
|
|
84
84
|
for i: int in range(AudioServer.bus_count):
|
|
85
85
|
buses.append(_bus(i))
|
|
86
|
-
|
|
86
|
+
var layout: String = _layout_path()
|
|
87
|
+
var answer: Dictionary = {"bus_count": AudioServer.bus_count, "buses": buses, "layout": layout}
|
|
88
|
+
# The setting names a path whether or not anything is there, and a project that never changed a
|
|
89
|
+
# bus has no file: the path alone read as a layout the project keeps.
|
|
90
|
+
if not FileAccess.file_exists(layout):
|
|
91
|
+
answer["layout_on_disk"] = false
|
|
92
|
+
answer["note"] = (
|
|
93
|
+
(
|
|
94
|
+
"Nothing is at %s, so these are the engine's default buses; the first change to a bus "
|
|
95
|
+
+ "writes the layout there."
|
|
96
|
+
)
|
|
97
|
+
% layout
|
|
98
|
+
)
|
|
99
|
+
return answer
|
|
87
100
|
|
|
88
101
|
|
|
89
102
|
func set_audio_bus_effect(params: Dictionary) -> Dictionary:
|