gdharness 0.13.2 → 0.13.3
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 +228 -93
- package/build/godot/addons/gdharness_editor/reading.gd +14 -7
- package/build/godot/addons/gdharness_editor/serialisation.gd +560 -0
- package/build/godot/addons/gdharness_editor/serialisation.gd.uid +1 -0
- package/build/godot/addons/gdharness_editor/tool_executor.gd +8 -0
- package/build/godot/addons/gdharness_editor/tools/scene_tools.gd +59 -152
- package/build/godot/addons/gdharness_editor/tools/settings_tools.gd +58 -0
- package/build/godot/addons/gdharness_editor/tools/settings_tools.gd.uid +1 -0
- package/build/godot/addons/gdharness_runtime/reading.gd +14 -7
- package/build/godot/addons/gdharness_runtime/runtime_queries.gd +16 -1
- package/build/godot/addons/gdharness_runtime/runtime_values.gd +88 -178
- package/build/godot/addons/gdharness_runtime/runtime_waits.gd +25 -2
- package/build/godot/addons/gdharness_runtime/serialisation.gd +560 -0
- package/build/godot/addons/gdharness_runtime/serialisation.gd.uid +1 -0
- package/build/godot/operations/reading.gd +6 -1
- package/build/godot/operations/serialisation.gd +448 -77
- package/build/index.js +225 -97
- package/package.json +1 -1
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
extends RefCounted
|
|
2
2
|
|
|
3
|
+
## Godot values as JSON can carry them, and JSON back into the values the engine wants.
|
|
4
|
+
##
|
|
5
|
+
## This file is the one beside the operations, and `bun run sync:gd` copies it into each addon,
|
|
6
|
+
## because an addon is installed as a directory and cannot preload out of one. The copies are
|
|
7
|
+
## byte for byte this file and a fixture says so. Three spellings of one value is what there was
|
|
8
|
+
## before: the same Vector2 came back three ways depending on which tool was asked, and a caller
|
|
9
|
+
## reading a property off the editor could not feed it back to the game.
|
|
10
|
+
|
|
3
11
|
const Read = preload("reading.gd")
|
|
4
12
|
|
|
5
13
|
# What each Godot type becomes on the wire, as one table you can read rather than an order you
|
|
@@ -10,20 +18,97 @@ const Read = preload("reading.gd")
|
|
|
10
18
|
# Method names rather than Callables: a table of Callables bound to this object is a reference
|
|
11
19
|
# cycle that nothing breaks, so the object outlives the run and the engine reports its script as
|
|
12
20
|
# a resource still in use at exit, on the same stderr the server reads failures off.
|
|
21
|
+
#
|
|
22
|
+
# Every type the engine has that JSON cannot carry is in here, and a fixture walks the engine's own
|
|
23
|
+
# type list to say so, because what this table used to be was the types somebody had thought of.
|
|
24
|
+
# JSON does not refuse what it cannot carry: it writes the value's own text and moves on, so a
|
|
25
|
+
# Polygon2D's points came back as the string "[(1.0, 2.0), (3.0, 4.0)]" and a Quaternion as
|
|
26
|
+
# "(0, 0, 0, 1)". Both read as an answer, neither can be read back, and nothing anywhere said the
|
|
27
|
+
# value had been flattened on the way out.
|
|
28
|
+
#
|
|
29
|
+
# The types JSON does carry exactly are left alone, PackedStringArray and the numeric packed arrays
|
|
30
|
+
# among them, because a plugin list reads better as ["res://addons/x/plugin.cfg"] than as a tagged
|
|
31
|
+
# wrapper around one, and type_convert restores the exact type wherever the receiver knows which
|
|
32
|
+
# type it wanted.
|
|
13
33
|
const SERIALISERS: Dictionary = {
|
|
14
34
|
TYPE_NIL: "_serialize_nil",
|
|
15
35
|
TYPE_VECTOR2: "_serialize_vector2",
|
|
16
|
-
TYPE_VECTOR3: "_serialize_vector3",
|
|
17
36
|
TYPE_VECTOR2I: "_serialize_vector2i",
|
|
37
|
+
TYPE_VECTOR3: "_serialize_vector3",
|
|
18
38
|
TYPE_VECTOR3I: "_serialize_vector3i",
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
TYPE_ARRAY: "_serialize_array",
|
|
39
|
+
TYPE_VECTOR4: "_serialize_vector4",
|
|
40
|
+
TYPE_VECTOR4I: "_serialize_vector4i",
|
|
22
41
|
TYPE_RECT2: "_serialize_rect2",
|
|
42
|
+
TYPE_RECT2I: "_serialize_rect2i",
|
|
43
|
+
TYPE_PLANE: "_serialize_plane",
|
|
44
|
+
TYPE_QUATERNION: "_serialize_quaternion",
|
|
45
|
+
TYPE_AABB: "_serialize_aabb",
|
|
46
|
+
TYPE_BASIS: "_serialize_basis",
|
|
23
47
|
TYPE_TRANSFORM2D: "_serialize_transform2d",
|
|
24
48
|
TYPE_TRANSFORM3D: "_serialize_transform3d",
|
|
25
|
-
|
|
49
|
+
TYPE_PROJECTION: "_serialize_projection",
|
|
50
|
+
TYPE_COLOR: "_serialize_color",
|
|
51
|
+
TYPE_NODE_PATH: "_serialize_node_path",
|
|
52
|
+
TYPE_RID: "_serialize_rid",
|
|
26
53
|
TYPE_OBJECT: "_serialize_object",
|
|
54
|
+
TYPE_CALLABLE: "_serialize_callable",
|
|
55
|
+
TYPE_SIGNAL: "_serialize_signal",
|
|
56
|
+
TYPE_DICTIONARY: "_serialize_dictionary",
|
|
57
|
+
TYPE_ARRAY: "_serialize_array",
|
|
58
|
+
TYPE_PACKED_BYTE_ARRAY: "_serialize_bytes",
|
|
59
|
+
TYPE_PACKED_VECTOR2_ARRAY: "_serialize_vector2s",
|
|
60
|
+
TYPE_PACKED_VECTOR3_ARRAY: "_serialize_vector3s",
|
|
61
|
+
TYPE_PACKED_VECTOR4_ARRAY: "_serialize_vector4s",
|
|
62
|
+
TYPE_PACKED_COLOR_ARRAY: "_serialize_colors",
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
# What each tag builds back into. The other half of the table above, and separate from it because
|
|
66
|
+
# they are keyed differently: one by the type of a value in hand, the other by the name in a
|
|
67
|
+
# dictionary that arrived from somewhere else.
|
|
68
|
+
const BUILDERS: Dictionary = {
|
|
69
|
+
"Vector2": "_build_vector2",
|
|
70
|
+
"Vector2i": "_build_vector2i",
|
|
71
|
+
"Vector3": "_build_vector3",
|
|
72
|
+
"Vector3i": "_build_vector3i",
|
|
73
|
+
"Vector4": "_build_vector4",
|
|
74
|
+
"Vector4i": "_build_vector4i",
|
|
75
|
+
"Rect2": "_build_rect2",
|
|
76
|
+
"Rect2i": "_build_rect2i",
|
|
77
|
+
"Plane": "_build_plane",
|
|
78
|
+
"Quaternion": "_build_quaternion",
|
|
79
|
+
"AABB": "_build_aabb",
|
|
80
|
+
"Basis": "_build_basis",
|
|
81
|
+
"Transform2D": "_build_transform2d",
|
|
82
|
+
"Transform3D": "_build_transform3d",
|
|
83
|
+
"Projection": "_build_projection",
|
|
84
|
+
"Color": "_build_color",
|
|
85
|
+
"NodePath": "_build_node_path",
|
|
86
|
+
"PackedByteArray": "_build_bytes",
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
# What each tag has to carry before it is built. A tag naming a type whose keys are not there is a
|
|
90
|
+
# tag somebody wrote by mistake or wrote for a different shape, and building one out of defaults
|
|
91
|
+
# answers with a Transform2D of zeros where the caller meant something else entirely. Unbuilt, it
|
|
92
|
+
# comes back as it arrived, and whoever asked can read it against the type they were expecting.
|
|
93
|
+
const REQUIRED: Dictionary = {
|
|
94
|
+
"Vector2": [["x", "y"]],
|
|
95
|
+
"Vector2i": [["x", "y"]],
|
|
96
|
+
"Vector3": [["x", "y", "z"]],
|
|
97
|
+
"Vector3i": [["x", "y", "z"]],
|
|
98
|
+
"Vector4": [["x", "y", "z", "w"]],
|
|
99
|
+
"Vector4i": [["x", "y", "z", "w"]],
|
|
100
|
+
"Rect2": [["position", "size"], ["x", "y", "width", "height"]],
|
|
101
|
+
"Rect2i": [["position", "size"], ["x", "y", "width", "height"]],
|
|
102
|
+
"Plane": [["normal", "d"]],
|
|
103
|
+
"Quaternion": [["x", "y", "z", "w"]],
|
|
104
|
+
"AABB": [["position", "size"]],
|
|
105
|
+
"Basis": [["x", "y", "z"]],
|
|
106
|
+
"Transform2D": [["x", "y", "origin"]],
|
|
107
|
+
"Transform3D": [["basis", "origin"]],
|
|
108
|
+
"Projection": [["x", "y", "z", "w"]],
|
|
109
|
+
"Color": [["r", "g", "b"]],
|
|
110
|
+
"NodePath": [["path"]],
|
|
111
|
+
"PackedByteArray": [["base64"]],
|
|
27
112
|
}
|
|
28
113
|
|
|
29
114
|
|
|
@@ -43,6 +128,10 @@ func serialize_value(value: Variant) -> Variant:
|
|
|
43
128
|
|
|
44
129
|
|
|
45
130
|
# Rebuilds a Godot value from the shape serialize_value gave it.
|
|
131
|
+
#
|
|
132
|
+
# A tag with no builder is left as the dictionary it arrived as. Some of them describe something
|
|
133
|
+
# that cannot be rebuilt at all, a Callable or a live object, and answering with a default-built
|
|
134
|
+
# one of those would be inventing a value rather than reporting one.
|
|
46
135
|
func deserialize_value(value: Variant) -> Variant:
|
|
47
136
|
if value == null:
|
|
48
137
|
return null
|
|
@@ -57,59 +146,34 @@ func deserialize_value(value: Variant) -> Variant:
|
|
|
57
146
|
|
|
58
147
|
var fields: Dictionary = value
|
|
59
148
|
if not fields.has("_type"):
|
|
60
|
-
var
|
|
149
|
+
var mapped: Dictionary = {}
|
|
61
150
|
for key: Variant in fields:
|
|
62
|
-
|
|
63
|
-
return
|
|
151
|
+
mapped[key] = deserialize_value(fields[key])
|
|
152
|
+
return mapped
|
|
64
153
|
|
|
65
154
|
# Each component read as the type the constructor wants before it is handed over. These come
|
|
66
155
|
# out of JSON as Variant, and a project holding `unsafe_call_argument` at error level refuses
|
|
67
156
|
# to compile a script that gives a Variant to a typed parameter: the operations are compiled
|
|
68
157
|
# under the target project's warning levels, so one such project lost every headless call.
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
return Vector3(
|
|
74
|
-
Read.as_float(fields.get("x", 0)),
|
|
75
|
-
Read.as_float(fields.get("y", 0)),
|
|
76
|
-
Read.as_float(fields.get("z", 0))
|
|
77
|
-
)
|
|
78
|
-
"Vector2i":
|
|
79
|
-
return Vector2i(Read.as_int(fields.get("x", 0)), Read.as_int(fields.get("y", 0)))
|
|
80
|
-
"Vector3i":
|
|
81
|
-
return Vector3i(
|
|
82
|
-
Read.as_int(fields.get("x", 0)),
|
|
83
|
-
Read.as_int(fields.get("y", 0)),
|
|
84
|
-
Read.as_int(fields.get("z", 0))
|
|
85
|
-
)
|
|
86
|
-
"Color":
|
|
87
|
-
return Color(
|
|
88
|
-
Read.as_float(fields.get("r", 0)),
|
|
89
|
-
Read.as_float(fields.get("g", 0)),
|
|
90
|
-
Read.as_float(fields.get("b", 0)),
|
|
91
|
-
Read.as_float(fields.get("a", 1), 1.0)
|
|
92
|
-
)
|
|
93
|
-
"Rect2":
|
|
94
|
-
return _rect_from(fields)
|
|
95
|
-
"NodePath":
|
|
96
|
-
return NodePath(str(fields.get("path", "")))
|
|
97
|
-
return fields
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
## A Rect2 out of its two tagged corners, or the fields back when they do not describe one.
|
|
101
|
-
##
|
|
102
|
-
## Its own function because the components have to be read as Vector2 before the constructor is
|
|
103
|
-
## given them, and a branch that can also answer with the fields would push the match above past
|
|
104
|
-
## the return count this project holds itself to.
|
|
105
|
-
func _rect_from(fields: Dictionary) -> Variant:
|
|
106
|
-
var position: Variant = deserialize_value(fields.get("position", {}))
|
|
107
|
-
var size: Variant = deserialize_value(fields.get("size", {}))
|
|
108
|
-
if not (position is Vector2 and size is Vector2):
|
|
158
|
+
var tag: String = str(fields["_type"])
|
|
159
|
+
var builder: String = BUILDERS.get(tag, "")
|
|
160
|
+
var needed: Array = REQUIRED.get(tag, [])
|
|
161
|
+
if builder.is_empty() or not _carries(fields, needed):
|
|
109
162
|
return fields
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
163
|
+
return call(builder, fields)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
## Whether [param fields] carries every key of any one of [param alternatives].
|
|
167
|
+
##
|
|
168
|
+
## More than one spelling because a rectangle has two: the two corners it is answered with, and the
|
|
169
|
+
## four numbers the editor's own tools answered with before the serialisers were made one, which a
|
|
170
|
+
## caller may have kept.
|
|
171
|
+
func _carries(fields: Dictionary, alternatives: Array) -> bool:
|
|
172
|
+
for alternative: Variant in alternatives:
|
|
173
|
+
var keys: Array = alternative
|
|
174
|
+
if keys.all(func(key: Variant) -> bool: return fields.has(key)):
|
|
175
|
+
return true
|
|
176
|
+
return false
|
|
113
177
|
|
|
114
178
|
|
|
115
179
|
func _serialize_nil(_value: Variant) -> Variant:
|
|
@@ -120,28 +184,24 @@ func _serialize_vector2(value: Vector2) -> Dictionary:
|
|
|
120
184
|
return {"x": value.x, "y": value.y, "_type": "Vector2"}
|
|
121
185
|
|
|
122
186
|
|
|
123
|
-
func _serialize_vector3(value: Vector3) -> Dictionary:
|
|
124
|
-
return {"x": value.x, "y": value.y, "z": value.z, "_type": "Vector3"}
|
|
125
|
-
|
|
126
|
-
|
|
127
187
|
func _serialize_vector2i(value: Vector2i) -> Dictionary:
|
|
128
188
|
return {"x": value.x, "y": value.y, "_type": "Vector2i"}
|
|
129
189
|
|
|
130
190
|
|
|
131
|
-
func
|
|
132
|
-
return {"x": value.x, "y": value.y, "z": value.z, "_type": "
|
|
191
|
+
func _serialize_vector3(value: Vector3) -> Dictionary:
|
|
192
|
+
return {"x": value.x, "y": value.y, "z": value.z, "_type": "Vector3"}
|
|
133
193
|
|
|
134
194
|
|
|
135
|
-
func
|
|
136
|
-
return {"
|
|
195
|
+
func _serialize_vector3i(value: Vector3i) -> Dictionary:
|
|
196
|
+
return {"x": value.x, "y": value.y, "z": value.z, "_type": "Vector3i"}
|
|
137
197
|
|
|
138
198
|
|
|
139
|
-
func
|
|
140
|
-
return {"
|
|
199
|
+
func _serialize_vector4(value: Vector4) -> Dictionary:
|
|
200
|
+
return {"x": value.x, "y": value.y, "z": value.z, "w": value.w, "_type": "Vector4"}
|
|
141
201
|
|
|
142
202
|
|
|
143
|
-
func
|
|
144
|
-
return value.
|
|
203
|
+
func _serialize_vector4i(value: Vector4i) -> Dictionary:
|
|
204
|
+
return {"x": value.x, "y": value.y, "z": value.z, "w": value.w, "_type": "Vector4i"}
|
|
145
205
|
|
|
146
206
|
|
|
147
207
|
func _serialize_rect2(value: Rect2) -> Dictionary:
|
|
@@ -150,6 +210,33 @@ func _serialize_rect2(value: Rect2) -> Dictionary:
|
|
|
150
210
|
}
|
|
151
211
|
|
|
152
212
|
|
|
213
|
+
func _serialize_rect2i(value: Rect2i) -> Dictionary:
|
|
214
|
+
return {
|
|
215
|
+
"position": serialize_value(value.position), "size": serialize_value(value.size), "_type": "Rect2i"
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
func _serialize_plane(value: Plane) -> Dictionary:
|
|
220
|
+
return {"normal": serialize_value(value.normal), "d": value.d, "_type": "Plane"}
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
func _serialize_quaternion(value: Quaternion) -> Dictionary:
|
|
224
|
+
return {"x": value.x, "y": value.y, "z": value.z, "w": value.w, "_type": "Quaternion"}
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
func _serialize_aabb(value: AABB) -> Dictionary:
|
|
228
|
+
return {"position": serialize_value(value.position), "size": serialize_value(value.size), "_type": "AABB"}
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
func _serialize_basis(value: Basis) -> Dictionary:
|
|
232
|
+
return {
|
|
233
|
+
"x": serialize_value(value.x),
|
|
234
|
+
"y": serialize_value(value.y),
|
|
235
|
+
"z": serialize_value(value.z),
|
|
236
|
+
"_type": "Basis"
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
|
|
153
240
|
func _serialize_transform2d(value: Transform2D) -> Dictionary:
|
|
154
241
|
return {
|
|
155
242
|
"origin": serialize_value(value.origin),
|
|
@@ -161,17 +248,52 @@ func _serialize_transform2d(value: Transform2D) -> Dictionary:
|
|
|
161
248
|
|
|
162
249
|
func _serialize_transform3d(value: Transform3D) -> Dictionary:
|
|
163
250
|
return {
|
|
164
|
-
"origin": serialize_value(value.origin),
|
|
165
|
-
"basis":
|
|
166
|
-
{
|
|
167
|
-
"x": serialize_value(value.basis.x),
|
|
168
|
-
"y": serialize_value(value.basis.y),
|
|
169
|
-
"z": serialize_value(value.basis.z)
|
|
170
|
-
},
|
|
171
|
-
"_type": "Transform3D"
|
|
251
|
+
"origin": serialize_value(value.origin), "basis": serialize_value(value.basis), "_type": "Transform3D"
|
|
172
252
|
}
|
|
173
253
|
|
|
174
254
|
|
|
255
|
+
func _serialize_projection(value: Projection) -> Dictionary:
|
|
256
|
+
return {
|
|
257
|
+
"x": serialize_value(value.x),
|
|
258
|
+
"y": serialize_value(value.y),
|
|
259
|
+
"z": serialize_value(value.z),
|
|
260
|
+
"w": serialize_value(value.w),
|
|
261
|
+
"_type": "Projection"
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
func _serialize_color(value: Color) -> Dictionary:
|
|
266
|
+
return {"r": value.r, "g": value.g, "b": value.b, "a": value.a, "_type": "Color"}
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
func _serialize_node_path(value: NodePath) -> Dictionary:
|
|
270
|
+
return {"path": str(value), "_type": "NodePath"}
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
# An id rather than nothing, because two properties holding the same RID are worth telling apart
|
|
274
|
+
# even though neither can be handed back to the engine.
|
|
275
|
+
func _serialize_rid(value: RID) -> Dictionary:
|
|
276
|
+
return {"id": value.get_id(), "_type": "RID"}
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
# A bound method, said in the terms a caller could act on: what it is on, and what it is called.
|
|
280
|
+
# It cannot be rebuilt from here, and saying so is the answer rather than a silent null.
|
|
281
|
+
func _serialize_callable(value: Callable) -> Dictionary:
|
|
282
|
+
var described: Dictionary = {"method": str(value.get_method()), "_type": "Callable"}
|
|
283
|
+
var holder: Object = value.get_object()
|
|
284
|
+
if holder != null:
|
|
285
|
+
described["object"] = _describe_object(holder)
|
|
286
|
+
return described
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
func _serialize_signal(value: Signal) -> Dictionary:
|
|
290
|
+
var described: Dictionary = {"name": str(value.get_name()), "_type": "Signal"}
|
|
291
|
+
var holder: Object = value.get_object()
|
|
292
|
+
if holder != null:
|
|
293
|
+
described["object"] = _describe_object(holder)
|
|
294
|
+
return described
|
|
295
|
+
|
|
296
|
+
|
|
175
297
|
func _serialize_dictionary(value: Dictionary) -> Dictionary:
|
|
176
298
|
var serialised: Dictionary = {}
|
|
177
299
|
for key: Variant in value:
|
|
@@ -179,11 +301,260 @@ func _serialize_dictionary(value: Dictionary) -> Dictionary:
|
|
|
179
301
|
return serialised
|
|
180
302
|
|
|
181
303
|
|
|
304
|
+
func _serialize_array(value: Array) -> Array:
|
|
305
|
+
return value.map(serialize_value)
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
# Base64 rather than a list of numbers. A byte array is a texture, a sound or a save file often
|
|
309
|
+
# enough that the list would be the whole answer, megabytes of it, and unreadable either way.
|
|
310
|
+
func _serialize_bytes(value: PackedByteArray) -> Dictionary:
|
|
311
|
+
return {"base64": Marshalls.raw_to_base64(value), "size": value.size(), "_type": "PackedByteArray"}
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
# The packed vector and colour arrays, each element serialised the way one of them on its own is.
|
|
315
|
+
# A plain list rather than a tagged wrapper, so a polygon reads as the points it is, and because
|
|
316
|
+
# the receiver of a write knows the type it wanted and converts to it. One function each rather
|
|
317
|
+
# than one taking a Variant, because a Variant handed to the element serialiser is the unsafe
|
|
318
|
+
# argument a project with every warning at error level refuses to compile.
|
|
319
|
+
func _serialize_vector2s(value: PackedVector2Array) -> Array:
|
|
320
|
+
var serialised: Array = []
|
|
321
|
+
for element: Vector2 in value:
|
|
322
|
+
serialised.append(_serialize_vector2(element))
|
|
323
|
+
return serialised
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
func _serialize_vector3s(value: PackedVector3Array) -> Array:
|
|
327
|
+
var serialised: Array = []
|
|
328
|
+
for element: Vector3 in value:
|
|
329
|
+
serialised.append(_serialize_vector3(element))
|
|
330
|
+
return serialised
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
func _serialize_vector4s(value: PackedVector4Array) -> Array:
|
|
334
|
+
var serialised: Array = []
|
|
335
|
+
for element: Vector4 in value:
|
|
336
|
+
serialised.append(_serialize_vector4(element))
|
|
337
|
+
return serialised
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
func _serialize_colors(value: PackedColorArray) -> Array:
|
|
341
|
+
var serialised: Array = []
|
|
342
|
+
for element: Color in value:
|
|
343
|
+
serialised.append(_serialize_color(element))
|
|
344
|
+
return serialised
|
|
345
|
+
|
|
346
|
+
|
|
182
347
|
# A pathless Resource says so rather than inventing an empty path for the caller to load.
|
|
183
348
|
func _serialize_object(value: Object) -> Variant:
|
|
184
|
-
if
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
return {"
|
|
189
|
-
|
|
349
|
+
if value is Resource:
|
|
350
|
+
var resource: Resource = value
|
|
351
|
+
if resource.resource_path.is_empty():
|
|
352
|
+
return {"class": resource.get_class(), "_type": "Resource"}
|
|
353
|
+
return {"path": resource.resource_path, "class": resource.get_class(), "_type": "Resource"}
|
|
354
|
+
if value is Node:
|
|
355
|
+
var node: Node = value
|
|
356
|
+
if node.is_inside_tree():
|
|
357
|
+
return {"path": str(node.get_path()), "class": node.get_class(), "_type": "Node"}
|
|
358
|
+
return _describe_object(value)
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
## A plain object, said in enough detail to tell it from the one beside it.
|
|
362
|
+
##
|
|
363
|
+
## `{"class": "RefCounted"}` was all of it, so a roster of twelve people came back as the word
|
|
364
|
+
## RefCounted twelve times: correct, useless, and indistinguishable from a list of twelve of
|
|
365
|
+
## anything else. The state worth looking at in a game is lists of these, so each one carries what
|
|
366
|
+
## it is (the `class_name` its script declares, or the script's path when it has no name), what it
|
|
367
|
+
## says about itself when the game has given it a `_to_string`, and an id that is at least
|
|
368
|
+
## different per object. None of it is invented: every field is left out when there is nothing to
|
|
369
|
+
## put in it, because a key holding "" is a worse answer than a key that is not there.
|
|
370
|
+
func _describe_object(value: Object) -> Dictionary:
|
|
371
|
+
var described: Dictionary = {"class": value.get_class(), "_type": "Object"}
|
|
372
|
+
# Asked as a Variant and narrowed, because get_script answers one and an object without a
|
|
373
|
+
# script answers null: casting that to Script is the unsafe cast the engine refuses to compile.
|
|
374
|
+
var attached: Variant = value.get_script()
|
|
375
|
+
if attached is Script:
|
|
376
|
+
var script: Script = attached
|
|
377
|
+
var declared: String = String(script.get_global_name())
|
|
378
|
+
if not declared.is_empty():
|
|
379
|
+
described["script_class"] = declared
|
|
380
|
+
elif not script.resource_path.is_empty():
|
|
381
|
+
described["script"] = script.resource_path
|
|
382
|
+
# Only when the game wrote one. The default is "<RefCounted#31098236>", which is the id again
|
|
383
|
+
# in a costume and reads as if the object had been asked and had nothing to say.
|
|
384
|
+
if value.has_method("_to_string"):
|
|
385
|
+
described["says"] = value.to_string()
|
|
386
|
+
described["id"] = value.get_instance_id()
|
|
387
|
+
return described
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
func _build_vector2(fields: Dictionary) -> Vector2:
|
|
391
|
+
return Vector2(Read.as_float(fields.get("x", 0)), Read.as_float(fields.get("y", 0)))
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
func _build_vector2i(fields: Dictionary) -> Vector2i:
|
|
395
|
+
return Vector2i(Read.as_int(fields.get("x", 0)), Read.as_int(fields.get("y", 0)))
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
func _build_vector3(fields: Dictionary) -> Vector3:
|
|
399
|
+
return Vector3(
|
|
400
|
+
Read.as_float(fields.get("x", 0)),
|
|
401
|
+
Read.as_float(fields.get("y", 0)),
|
|
402
|
+
Read.as_float(fields.get("z", 0))
|
|
403
|
+
)
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
func _build_vector3i(fields: Dictionary) -> Vector3i:
|
|
407
|
+
return Vector3i(
|
|
408
|
+
Read.as_int(fields.get("x", 0)), Read.as_int(fields.get("y", 0)), Read.as_int(fields.get("z", 0))
|
|
409
|
+
)
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
func _build_vector4(fields: Dictionary) -> Vector4:
|
|
413
|
+
return Vector4(
|
|
414
|
+
Read.as_float(fields.get("x", 0)),
|
|
415
|
+
Read.as_float(fields.get("y", 0)),
|
|
416
|
+
Read.as_float(fields.get("z", 0)),
|
|
417
|
+
Read.as_float(fields.get("w", 0))
|
|
418
|
+
)
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
func _build_vector4i(fields: Dictionary) -> Vector4i:
|
|
422
|
+
return Vector4i(
|
|
423
|
+
Read.as_int(fields.get("x", 0)),
|
|
424
|
+
Read.as_int(fields.get("y", 0)),
|
|
425
|
+
Read.as_int(fields.get("z", 0)),
|
|
426
|
+
Read.as_int(fields.get("w", 0))
|
|
427
|
+
)
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
## A rectangle from its two corners, or from the four numbers this tool answered with before the
|
|
431
|
+
## serialisers were made one, so an answer somebody kept can still be written back.
|
|
432
|
+
func _build_rect2(fields: Dictionary) -> Rect2:
|
|
433
|
+
if not fields.has("position"):
|
|
434
|
+
return Rect2(
|
|
435
|
+
Read.as_float(fields.get("x", 0)),
|
|
436
|
+
Read.as_float(fields.get("y", 0)),
|
|
437
|
+
Read.as_float(fields.get("width", 0)),
|
|
438
|
+
Read.as_float(fields.get("height", 0))
|
|
439
|
+
)
|
|
440
|
+
return Rect2(_vector2_at(fields, "position"), _vector2_at(fields, "size"))
|
|
441
|
+
|
|
442
|
+
|
|
443
|
+
func _build_rect2i(fields: Dictionary) -> Rect2i:
|
|
444
|
+
if not fields.has("position"):
|
|
445
|
+
return Rect2i(
|
|
446
|
+
Read.as_int(fields.get("x", 0)),
|
|
447
|
+
Read.as_int(fields.get("y", 0)),
|
|
448
|
+
Read.as_int(fields.get("width", 0)),
|
|
449
|
+
Read.as_int(fields.get("height", 0))
|
|
450
|
+
)
|
|
451
|
+
return Rect2i(_vector2i_at(fields, "position"), _vector2i_at(fields, "size"))
|
|
452
|
+
|
|
453
|
+
|
|
454
|
+
func _build_plane(fields: Dictionary) -> Plane:
|
|
455
|
+
return Plane(_vector3_at(fields, "normal"), Read.as_float(fields.get("d", 0)))
|
|
456
|
+
|
|
457
|
+
|
|
458
|
+
func _build_quaternion(fields: Dictionary) -> Quaternion:
|
|
459
|
+
return Quaternion(
|
|
460
|
+
Read.as_float(fields.get("x", 0)),
|
|
461
|
+
Read.as_float(fields.get("y", 0)),
|
|
462
|
+
Read.as_float(fields.get("z", 0)),
|
|
463
|
+
Read.as_float(fields.get("w", 0))
|
|
464
|
+
)
|
|
465
|
+
|
|
466
|
+
|
|
467
|
+
func _build_aabb(fields: Dictionary) -> AABB:
|
|
468
|
+
return AABB(_vector3_at(fields, "position"), _vector3_at(fields, "size"))
|
|
469
|
+
|
|
470
|
+
|
|
471
|
+
func _build_basis(fields: Dictionary) -> Basis:
|
|
472
|
+
return Basis(
|
|
473
|
+
_vector3_at(fields, "x", Vector3.RIGHT),
|
|
474
|
+
_vector3_at(fields, "y", Vector3.UP),
|
|
475
|
+
_vector3_at(fields, "z", Vector3.BACK)
|
|
476
|
+
)
|
|
477
|
+
|
|
478
|
+
|
|
479
|
+
func _build_transform2d(fields: Dictionary) -> Transform2D:
|
|
480
|
+
return Transform2D(
|
|
481
|
+
_vector2_at(fields, "x", Vector2.RIGHT),
|
|
482
|
+
_vector2_at(fields, "y", Vector2.DOWN),
|
|
483
|
+
_vector2_at(fields, "origin")
|
|
484
|
+
)
|
|
485
|
+
|
|
486
|
+
|
|
487
|
+
func _build_transform3d(fields: Dictionary) -> Transform3D:
|
|
488
|
+
var held: Variant = fields.get("basis", null)
|
|
489
|
+
if not held is Dictionary:
|
|
490
|
+
return Transform3D(Basis.IDENTITY, _vector3_at(fields, "origin"))
|
|
491
|
+
var columns: Dictionary = held
|
|
492
|
+
return Transform3D(_build_basis(columns), _vector3_at(fields, "origin"))
|
|
493
|
+
|
|
494
|
+
|
|
495
|
+
func _build_projection(fields: Dictionary) -> Projection:
|
|
496
|
+
return Projection(
|
|
497
|
+
_vector4_at(fields, "x"), _vector4_at(fields, "y"), _vector4_at(fields, "z"), _vector4_at(fields, "w")
|
|
498
|
+
)
|
|
499
|
+
|
|
500
|
+
|
|
501
|
+
func _build_color(fields: Dictionary) -> Color:
|
|
502
|
+
return Color(
|
|
503
|
+
Read.as_float(fields.get("r", 0)),
|
|
504
|
+
Read.as_float(fields.get("g", 0)),
|
|
505
|
+
Read.as_float(fields.get("b", 0)),
|
|
506
|
+
Read.as_float(fields.get("a", 1), 1.0)
|
|
507
|
+
)
|
|
508
|
+
|
|
509
|
+
|
|
510
|
+
func _build_node_path(fields: Dictionary) -> NodePath:
|
|
511
|
+
return NodePath(str(fields.get("path", "")))
|
|
512
|
+
|
|
513
|
+
|
|
514
|
+
func _build_bytes(fields: Dictionary) -> PackedByteArray:
|
|
515
|
+
return Marshalls.base64_to_raw(str(fields.get("base64", "")))
|
|
516
|
+
|
|
517
|
+
|
|
518
|
+
## One component of a compound value, as the type its constructor wants.
|
|
519
|
+
##
|
|
520
|
+
## Their own functions because a constructor refuses a Variant under `unsafe_call_argument`, and
|
|
521
|
+
## because a component that is not there at all has to answer with a default rather than take the
|
|
522
|
+
## whole build down with it.
|
|
523
|
+
##
|
|
524
|
+
## The component is built from its keys rather than deserialised, so it does not matter whether it
|
|
525
|
+
## carries a tag. A caller writing a transform by hand writes {"x": {"x": 1, "y": 0}}, and that is
|
|
526
|
+
## the same value as the tagged form this answers with.
|
|
527
|
+
##
|
|
528
|
+
## [param fallback] is what a missing component is, which is the zero vector everywhere except the
|
|
529
|
+
## columns of a basis: a matrix half given should come out the identity it was being varied from
|
|
530
|
+
## rather than the degenerate one that collapses everything drawn with it.
|
|
531
|
+
func _vector2_at(fields: Dictionary, key: String, fallback: Vector2 = Vector2.ZERO) -> Vector2:
|
|
532
|
+
var held: Variant = fields.get(key, null)
|
|
533
|
+
if not held is Dictionary:
|
|
534
|
+
return fallback
|
|
535
|
+
var component: Dictionary = held
|
|
536
|
+
return _build_vector2(component)
|
|
537
|
+
|
|
538
|
+
|
|
539
|
+
func _vector2i_at(fields: Dictionary, key: String, fallback: Vector2i = Vector2i.ZERO) -> Vector2i:
|
|
540
|
+
var held: Variant = fields.get(key, null)
|
|
541
|
+
if not held is Dictionary:
|
|
542
|
+
return fallback
|
|
543
|
+
var component: Dictionary = held
|
|
544
|
+
return _build_vector2i(component)
|
|
545
|
+
|
|
546
|
+
|
|
547
|
+
func _vector3_at(fields: Dictionary, key: String, fallback: Vector3 = Vector3.ZERO) -> Vector3:
|
|
548
|
+
var held: Variant = fields.get(key, null)
|
|
549
|
+
if not held is Dictionary:
|
|
550
|
+
return fallback
|
|
551
|
+
var component: Dictionary = held
|
|
552
|
+
return _build_vector3(component)
|
|
553
|
+
|
|
554
|
+
|
|
555
|
+
func _vector4_at(fields: Dictionary, key: String, fallback: Vector4 = Vector4.ZERO) -> Vector4:
|
|
556
|
+
var held: Variant = fields.get(key, null)
|
|
557
|
+
if not held is Dictionary:
|
|
558
|
+
return fallback
|
|
559
|
+
var component: Dictionary = held
|
|
560
|
+
return _build_vector4(component)
|