gdharness 0.13.1 → 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 +236 -97
- 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 +233 -101
- package/package.json +1 -1
|
@@ -1,15 +1,22 @@
|
|
|
1
1
|
extends RefCounted
|
|
2
2
|
|
|
3
|
-
# A value out of a
|
|
3
|
+
# A value out of a dictionary that came from JSON, as the type the caller 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 runtime addon's
|
|
7
|
+
# copy ships inside exported games and the editor addon's does not, which is the other reason
|
|
8
|
+
# neither of them reaches into the other.
|
|
4
9
|
#
|
|
5
10
|
# `int(value)`, `float(value)` and `bool(value)` take a Variant, and a project holding
|
|
6
|
-
# `unsafe_call_argument` at error level will not compile a script that hands one over.
|
|
7
|
-
#
|
|
8
|
-
# the
|
|
11
|
+
# `unsafe_call_argument` at error level will not compile a script that hands one over. These
|
|
12
|
+
# scripts are compiled under the target project's warning levels rather than under this package's,
|
|
13
|
+
# because `--path` makes that project the loaded one and the file's living in the npm cache
|
|
14
|
+
# changes nothing: one project turning that warning on lost every headless operation at once.
|
|
9
15
|
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
16
|
+
# The conversion itself is the same. What changes is that the type is established first, on a
|
|
17
|
+
# typed local, and the conversion happens from that. Here rather than at each of the thirty-one
|
|
18
|
+
# places that read a parameter, because thirty-one is how many chances there are to write the old
|
|
19
|
+
# form again, and the error does not appear until somebody sets the warning.
|
|
13
20
|
#
|
|
14
21
|
# `fallback` answers a value that is there and is null, which is a caller sending JSON null where a
|
|
15
22
|
# number was expected. Absent keys never reach here: the callers pass their own default to
|
|
@@ -0,0 +1,560 @@
|
|
|
1
|
+
extends RefCounted
|
|
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
|
+
|
|
11
|
+
const Read = preload("reading.gd")
|
|
12
|
+
|
|
13
|
+
# What each Godot type becomes on the wire, as one table you can read rather than an order you
|
|
14
|
+
# have to trust. Keyed on typeof() rather than written as a chain of `is` tests, because the
|
|
15
|
+
# order mattered: Resource had to be tested before Object or every resource came back as a bare
|
|
16
|
+
# class name with its path dropped, and nothing but a comment said so.
|
|
17
|
+
#
|
|
18
|
+
# Method names rather than Callables: a table of Callables bound to this object is a reference
|
|
19
|
+
# cycle that nothing breaks, so the object outlives the run and the engine reports its script as
|
|
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.
|
|
33
|
+
const SERIALISERS: Dictionary = {
|
|
34
|
+
TYPE_NIL: "_serialize_nil",
|
|
35
|
+
TYPE_VECTOR2: "_serialize_vector2",
|
|
36
|
+
TYPE_VECTOR2I: "_serialize_vector2i",
|
|
37
|
+
TYPE_VECTOR3: "_serialize_vector3",
|
|
38
|
+
TYPE_VECTOR3I: "_serialize_vector3i",
|
|
39
|
+
TYPE_VECTOR4: "_serialize_vector4",
|
|
40
|
+
TYPE_VECTOR4I: "_serialize_vector4i",
|
|
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",
|
|
47
|
+
TYPE_TRANSFORM2D: "_serialize_transform2d",
|
|
48
|
+
TYPE_TRANSFORM3D: "_serialize_transform3d",
|
|
49
|
+
TYPE_PROJECTION: "_serialize_projection",
|
|
50
|
+
TYPE_COLOR: "_serialize_color",
|
|
51
|
+
TYPE_NODE_PATH: "_serialize_node_path",
|
|
52
|
+
TYPE_RID: "_serialize_rid",
|
|
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"]],
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
# Converts a Godot value into something JSON can carry. A type with no entry in the table
|
|
116
|
+
# passes through as itself, which is what the JSON-native ones want.
|
|
117
|
+
#
|
|
118
|
+
# A property that holds no object is Object-typed and null rather than nil, which is most of what
|
|
119
|
+
# a node's property list is, and an object that has been freed is the same shape again. Both are
|
|
120
|
+
# refused here rather than inside _serialize_object, because a freed one never reaches it: the
|
|
121
|
+
# call itself fails on the argument, with "previously freed is not a subclass of the expected
|
|
122
|
+
# argument class", and a guard behind that is a guard that never runs.
|
|
123
|
+
func serialize_value(value: Variant) -> Variant:
|
|
124
|
+
if typeof(value) == TYPE_OBJECT and not is_instance_valid(value):
|
|
125
|
+
return null
|
|
126
|
+
var serialiser: String = SERIALISERS.get(typeof(value), "")
|
|
127
|
+
return call(serialiser, value) if not serialiser.is_empty() else value
|
|
128
|
+
|
|
129
|
+
|
|
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.
|
|
135
|
+
func deserialize_value(value: Variant) -> Variant:
|
|
136
|
+
if value == null:
|
|
137
|
+
return null
|
|
138
|
+
if value is Array:
|
|
139
|
+
var items: Array = value
|
|
140
|
+
var rebuilt: Array = []
|
|
141
|
+
for item: Variant in items:
|
|
142
|
+
rebuilt.append(deserialize_value(item))
|
|
143
|
+
return rebuilt
|
|
144
|
+
if not value is Dictionary:
|
|
145
|
+
return value
|
|
146
|
+
|
|
147
|
+
var fields: Dictionary = value
|
|
148
|
+
if not fields.has("_type"):
|
|
149
|
+
var mapped: Dictionary = {}
|
|
150
|
+
for key: Variant in fields:
|
|
151
|
+
mapped[key] = deserialize_value(fields[key])
|
|
152
|
+
return mapped
|
|
153
|
+
|
|
154
|
+
# Each component read as the type the constructor wants before it is handed over. These come
|
|
155
|
+
# out of JSON as Variant, and a project holding `unsafe_call_argument` at error level refuses
|
|
156
|
+
# to compile a script that gives a Variant to a typed parameter: the operations are compiled
|
|
157
|
+
# under the target project's warning levels, so one such project lost every headless call.
|
|
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):
|
|
162
|
+
return fields
|
|
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
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
func _serialize_nil(_value: Variant) -> Variant:
|
|
180
|
+
return null
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
func _serialize_vector2(value: Vector2) -> Dictionary:
|
|
184
|
+
return {"x": value.x, "y": value.y, "_type": "Vector2"}
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
func _serialize_vector2i(value: Vector2i) -> Dictionary:
|
|
188
|
+
return {"x": value.x, "y": value.y, "_type": "Vector2i"}
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
func _serialize_vector3(value: Vector3) -> Dictionary:
|
|
192
|
+
return {"x": value.x, "y": value.y, "z": value.z, "_type": "Vector3"}
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
func _serialize_vector3i(value: Vector3i) -> Dictionary:
|
|
196
|
+
return {"x": value.x, "y": value.y, "z": value.z, "_type": "Vector3i"}
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
func _serialize_vector4(value: Vector4) -> Dictionary:
|
|
200
|
+
return {"x": value.x, "y": value.y, "z": value.z, "w": value.w, "_type": "Vector4"}
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
func _serialize_vector4i(value: Vector4i) -> Dictionary:
|
|
204
|
+
return {"x": value.x, "y": value.y, "z": value.z, "w": value.w, "_type": "Vector4i"}
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
func _serialize_rect2(value: Rect2) -> Dictionary:
|
|
208
|
+
return {
|
|
209
|
+
"position": serialize_value(value.position), "size": serialize_value(value.size), "_type": "Rect2"
|
|
210
|
+
}
|
|
211
|
+
|
|
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
|
+
|
|
240
|
+
func _serialize_transform2d(value: Transform2D) -> Dictionary:
|
|
241
|
+
return {
|
|
242
|
+
"origin": serialize_value(value.origin),
|
|
243
|
+
"x": serialize_value(value.x),
|
|
244
|
+
"y": serialize_value(value.y),
|
|
245
|
+
"_type": "Transform2D"
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
func _serialize_transform3d(value: Transform3D) -> Dictionary:
|
|
250
|
+
return {
|
|
251
|
+
"origin": serialize_value(value.origin), "basis": serialize_value(value.basis), "_type": "Transform3D"
|
|
252
|
+
}
|
|
253
|
+
|
|
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
|
+
|
|
297
|
+
func _serialize_dictionary(value: Dictionary) -> Dictionary:
|
|
298
|
+
var serialised: Dictionary = {}
|
|
299
|
+
for key: Variant in value:
|
|
300
|
+
serialised[str(key)] = serialize_value(value[key])
|
|
301
|
+
return serialised
|
|
302
|
+
|
|
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
|
+
|
|
347
|
+
# A pathless Resource says so rather than inventing an empty path for the caller to load.
|
|
348
|
+
func _serialize_object(value: Object) -> Variant:
|
|
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)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
uid://b7nqx3mvd8kfa
|
|
@@ -10,6 +10,7 @@ const ResourceTools = preload("tools/resource_tools.gd")
|
|
|
10
10
|
const AnimationTools = preload("tools/animation_tools.gd")
|
|
11
11
|
const PlayTools = preload("tools/play_tools.gd")
|
|
12
12
|
const ClassTools = preload("tools/class_tools.gd")
|
|
13
|
+
const SettingsTools = preload("tools/settings_tools.gd")
|
|
13
14
|
|
|
14
15
|
var _editor_plugin: EditorPlugin = null
|
|
15
16
|
|
|
@@ -18,6 +19,7 @@ var _resource_tools: ResourceTools = null
|
|
|
18
19
|
var _animation_tools: AnimationTools = null
|
|
19
20
|
var _play_tools: PlayTools = null
|
|
20
21
|
var _class_tools: ClassTools = null
|
|
22
|
+
var _settings_tools: SettingsTools = null
|
|
21
23
|
|
|
22
24
|
var _tool_map: Dictionary = {}
|
|
23
25
|
var _initialized: bool = false
|
|
@@ -58,6 +60,10 @@ func _init_tools() -> void:
|
|
|
58
60
|
_class_tools.name = "ClassTools"
|
|
59
61
|
add_child(_class_tools)
|
|
60
62
|
|
|
63
|
+
_settings_tools = SettingsTools.new()
|
|
64
|
+
_settings_tools.name = "SettingsTools"
|
|
65
|
+
add_child(_settings_tools)
|
|
66
|
+
|
|
61
67
|
_tool_map = {
|
|
62
68
|
# Scene tools
|
|
63
69
|
"create_scene": [_scene_tools, "create_scene"],
|
|
@@ -74,6 +80,8 @@ func _init_tools() -> void:
|
|
|
74
80
|
"list_connections": [_scene_tools, "list_connections"],
|
|
75
81
|
"rescan_filesystem": [_scene_tools, "rescan_filesystem"],
|
|
76
82
|
"global_classes": [_class_tools, "global_classes"],
|
|
83
|
+
# Settings, for a caller that asked the editor rather than the file on disk
|
|
84
|
+
"get_project_setting": [_settings_tools, "get_project_setting"],
|
|
77
85
|
# Resource tools
|
|
78
86
|
"create_resource": [_resource_tools, "create_resource"],
|
|
79
87
|
"modify_resource": [_resource_tools, "modify_resource"],
|