gdharness 1.2.36 → 1.2.38
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 +918 -439
- package/build/godot/addons/gdharness_editor/headless_check.gd +411 -0
- package/build/godot/addons/gdharness_editor/headless_check.gd.uid +1 -0
- package/build/godot/addons/gdharness_editor/property_values.gd +3 -0
- package/build/godot/addons/gdharness_editor/scene_file.gd +408 -10
- package/build/godot/addons/gdharness_editor/serialisation.gd +103 -5
- package/build/godot/addons/gdharness_editor/tools/resource_tools.gd +88 -25
- package/build/godot/addons/gdharness_editor/tools/scene_tools.gd +68 -19
- package/build/godot/addons/gdharness_runtime/runtime_changes.gd +9 -0
- package/build/godot/addons/gdharness_runtime/runtime_values.gd +5 -0
- package/build/godot/addons/gdharness_runtime/runtime_waits.gd +3 -0
- package/build/godot/addons/gdharness_runtime/serialisation.gd +103 -5
- package/build/godot/operations/editor_tools.gd +57 -0
- package/build/godot/operations/gdscript_analysis.gd +26 -2
- package/build/godot/operations/gdscript_authoring.gd +5 -2
- package/build/godot/operations/godot_operations.gd +58 -1
- package/build/godot/operations/property_values.gd +3 -0
- package/build/godot/operations/serialisation.gd +103 -5
- package/build/index.js +838 -366
- package/package.json +1 -1
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
extends RefCounted
|
|
2
|
+
|
|
3
|
+
## The editor addon's scene and resource tools, run by a headless engine for a project no editor
|
|
4
|
+
## serves.
|
|
5
|
+
##
|
|
6
|
+
## They are the editor's own scripts, loaded from beside these operations in the package, so a write
|
|
7
|
+
## made here passes the same checks and answers in the same shape. What the engine does that an
|
|
8
|
+
## editor does not, running every script, is put back in scene_file.gd and headless_check.gd.
|
|
9
|
+
|
|
10
|
+
const ResourceTools = preload("../addons/gdharness_editor/tools/resource_tools.gd")
|
|
11
|
+
const SceneTools = preload("../addons/gdharness_editor/tools/scene_tools.gd")
|
|
12
|
+
|
|
13
|
+
const SCENE_COMMANDS: Array[String] = [
|
|
14
|
+
"create_scene",
|
|
15
|
+
"save_scene",
|
|
16
|
+
"add_node",
|
|
17
|
+
"get_node_properties",
|
|
18
|
+
"set_node_properties",
|
|
19
|
+
"duplicate_node",
|
|
20
|
+
"reparent_node",
|
|
21
|
+
"delete_node",
|
|
22
|
+
"connect_signal",
|
|
23
|
+
"disconnect_signal",
|
|
24
|
+
"list_connections",
|
|
25
|
+
]
|
|
26
|
+
const RESOURCE_COMMANDS: Array[String] = [
|
|
27
|
+
"set_tilemap_cells",
|
|
28
|
+
"create_resource",
|
|
29
|
+
"modify_resource",
|
|
30
|
+
"create_shader",
|
|
31
|
+
"create_tileset",
|
|
32
|
+
"set_theme_color",
|
|
33
|
+
"set_theme_font_size",
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
## Runs the tool [code]command[/code] names with [code]arguments[/code], answering what it answers.
|
|
38
|
+
static func run(params: Dictionary) -> Dictionary:
|
|
39
|
+
var command: String = str(params.get("command", ""))
|
|
40
|
+
var given: Variant = params.get("arguments", {})
|
|
41
|
+
if not given is Dictionary:
|
|
42
|
+
return {"ok": false, "error": "arguments must be an object."}
|
|
43
|
+
var arguments: Dictionary = given
|
|
44
|
+
var tools: Node = null
|
|
45
|
+
if command in SCENE_COMMANDS:
|
|
46
|
+
tools = SceneTools.new()
|
|
47
|
+
elif command in RESOURCE_COMMANDS:
|
|
48
|
+
tools = ResourceTools.new()
|
|
49
|
+
else:
|
|
50
|
+
return {"ok": false, "error": "No editor tool runs headless as " + command}
|
|
51
|
+
var answer: Variant = tools.call(command, arguments)
|
|
52
|
+
tools.free()
|
|
53
|
+
if answer is Dictionary:
|
|
54
|
+
return answer
|
|
55
|
+
return {
|
|
56
|
+
"ok": false, "error": "%s answered %s rather than an object." % [command, type_string(typeof(answer))]
|
|
57
|
+
}
|
|
@@ -154,6 +154,21 @@ func get_gdscript_info(params: Dictionary) -> Dictionary:
|
|
|
154
154
|
if not declaring in dependencies:
|
|
155
155
|
dependencies.append(declaring)
|
|
156
156
|
|
|
157
|
+
# An autoload is named the way a class is, and its script is one this one needs to compile, but
|
|
158
|
+
# the project loads it rather than this script, so it is answered apart from dependencies.
|
|
159
|
+
var global_autoloads: Dictionary = {}
|
|
160
|
+
for property: Dictionary in ProjectSettings.get_property_list():
|
|
161
|
+
var setting: String = str(property.get("name", ""))
|
|
162
|
+
var value: String = (
|
|
163
|
+
str(ProjectSettings.get_setting(setting)) if setting.begins_with("autoload/") else ""
|
|
164
|
+
)
|
|
165
|
+
if value.begins_with("*"):
|
|
166
|
+
global_autoloads[setting.trim_prefix("autoload/")] = value.trim_prefix("*")
|
|
167
|
+
var autoloads_used: Array[Dictionary] = []
|
|
168
|
+
for used: String in Dependencies.classes_used_in(content, global_autoloads):
|
|
169
|
+
if str(global_autoloads[used]) != full_script_path:
|
|
170
|
+
autoloads_used.append({"name": used, "path": str(global_autoloads[used])})
|
|
171
|
+
|
|
157
172
|
var answer: Dictionary = {
|
|
158
173
|
"path": script_path,
|
|
159
174
|
"full_path": full_script_path,
|
|
@@ -167,6 +182,7 @@ func get_gdscript_info(params: Dictionary) -> Dictionary:
|
|
|
167
182
|
"inner_classes": inner_classes,
|
|
168
183
|
"dependencies": dependencies,
|
|
169
184
|
"classes_used": classes_used,
|
|
185
|
+
"autoloads_used": autoloads_used,
|
|
170
186
|
"line_count": lines.size()
|
|
171
187
|
}
|
|
172
188
|
if Read.as_bool(params.get("include_inherited", false)):
|
|
@@ -386,10 +402,15 @@ func _parse_variable(line: String, line_num: int) -> Dictionary:
|
|
|
386
402
|
else:
|
|
387
403
|
name = var_text.split(" ")[0].strip_edges()
|
|
388
404
|
|
|
405
|
+
# Null for a declaration with no value, which as an empty string read as `= ""`.
|
|
406
|
+
var stated_default: Variant = null
|
|
407
|
+
if "=" in var_text:
|
|
408
|
+
stated_default = default_value
|
|
409
|
+
|
|
389
410
|
return {
|
|
390
411
|
"name": name,
|
|
391
412
|
"type": type_hint,
|
|
392
|
-
"default_value":
|
|
413
|
+
"default_value": stated_default,
|
|
393
414
|
"is_export": is_export,
|
|
394
415
|
"export_hint": export_hint,
|
|
395
416
|
"is_onready": is_onready,
|
|
@@ -582,7 +603,10 @@ func _parse_param(param_text: String) -> Dictionary:
|
|
|
582
603
|
if is_rest:
|
|
583
604
|
name = name.substr(3).strip_edges()
|
|
584
605
|
|
|
585
|
-
|
|
606
|
+
var stated_default: Variant = null
|
|
607
|
+
if "=" in param_text:
|
|
608
|
+
stated_default = default_value
|
|
609
|
+
return {"name": name, "type": type_hint, "default": stated_default, "is_rest": is_rest}
|
|
586
610
|
|
|
587
611
|
|
|
588
612
|
func _collect_dependencies(line: String, into: Array[String]) -> void:
|
|
@@ -396,8 +396,11 @@ func _add_function(lines: Array[String], mod: Dictionary, placed: Array[Dictiona
|
|
|
396
396
|
var declared_at: int = insert_line + func_lines.size()
|
|
397
397
|
func_lines.append("func " + func_name + "(" + func_params + ") -> " + return_type + ":")
|
|
398
398
|
|
|
399
|
-
|
|
400
|
-
|
|
399
|
+
# The body's statements relative to the function, whether or not the caller indented them for
|
|
400
|
+
# it: a body written with its tab already on came out two levels in. A blank line stays empty,
|
|
401
|
+
# since an indent alone on it is trailing whitespace to every formatter.
|
|
402
|
+
for bl: String in body.dedent().split("\n"):
|
|
403
|
+
func_lines.append("" if bl.strip_edges().is_empty() else "\t" + bl)
|
|
401
404
|
|
|
402
405
|
var below: int = _blank_run(lines, insert_line, 1)
|
|
403
406
|
if insert_line + below < lines.size():
|
|
@@ -38,6 +38,27 @@ const ScriptCheck = preload("script_check.gd")
|
|
|
38
38
|
## step with `ANSWERED` in src/headless.ts.
|
|
39
39
|
const ANSWERED: String = "[gdharness] answered"
|
|
40
40
|
|
|
41
|
+
## Printed in `_init` by an operation that runs once the project's autoloads are in the tree, and
|
|
42
|
+
## when it begins there. Kept in step with `WAITING` and `BEGUN` in src/headless.ts, which files
|
|
43
|
+
## what the autoloads print in between as theirs rather than the operation's.
|
|
44
|
+
const WAITING: String = "[gdharness] waiting for the autoloads"
|
|
45
|
+
const BEGUN: String = "[gdharness] begun"
|
|
46
|
+
|
|
47
|
+
## The operations that compile the project's scripts. The engine knows an autoload's name only once
|
|
48
|
+
## the autoload is in the tree, which is after `_init`, so a script naming one compiled here failed
|
|
49
|
+
## with "Identifier not found" while the game and the editor ran it. A scene tool, and a read of a
|
|
50
|
+
## scene or a resource, loads the scripts of the file it opens.
|
|
51
|
+
const AFTER_THE_AUTOLOADS: Array[String] = [
|
|
52
|
+
"check_scripts",
|
|
53
|
+
"create_script",
|
|
54
|
+
"modify_script",
|
|
55
|
+
"read_animation_states",
|
|
56
|
+
"read_animations",
|
|
57
|
+
"read_resource",
|
|
58
|
+
"read_scene_tree",
|
|
59
|
+
"run_editor_tool",
|
|
60
|
+
]
|
|
61
|
+
|
|
41
62
|
var _log: Log
|
|
42
63
|
|
|
43
64
|
|
|
@@ -76,12 +97,31 @@ func _init() -> void:
|
|
|
76
97
|
|
|
77
98
|
_log.info("Executing operation: " + operation)
|
|
78
99
|
|
|
100
|
+
var answer_path: String = args[answer_index]
|
|
101
|
+
if operation in AFTER_THE_AUTOLOADS:
|
|
102
|
+
printerr(WAITING)
|
|
103
|
+
var waiting: int = process_frame.connect(
|
|
104
|
+
_answer_after_the_autoloads.bind(operation, params, answer_path), CONNECT_ONE_SHOT
|
|
105
|
+
)
|
|
106
|
+
if waiting != OK:
|
|
107
|
+
_log.error("Could not wait for the project's autoloads: " + error_string(waiting))
|
|
108
|
+
quit(1)
|
|
109
|
+
return
|
|
110
|
+
_answer(operation, params, answer_path)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
func _answer_after_the_autoloads(operation: String, params: Dictionary, answer_path: String) -> void:
|
|
114
|
+
printerr(BEGUN)
|
|
115
|
+
_answer(operation, params, answer_path)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
## Runs [param operation] and writes its answer to [param answer_path], then quits.
|
|
119
|
+
func _answer(operation: String, params: Dictionary, answer_path: String) -> void:
|
|
79
120
|
var payload: Dictionary = _run(operation, params)
|
|
80
121
|
if payload.is_empty():
|
|
81
122
|
quit(1)
|
|
82
123
|
return
|
|
83
124
|
|
|
84
|
-
var answer_path: String = args[answer_index]
|
|
85
125
|
var answer: FileAccess = FileAccess.open(answer_path, FileAccess.WRITE)
|
|
86
126
|
if answer == null:
|
|
87
127
|
_log.error(
|
|
@@ -173,6 +213,9 @@ func _run(operation: String, params: Dictionary) -> Dictionary:
|
|
|
173
213
|
)
|
|
174
214
|
)
|
|
175
215
|
|
|
216
|
+
"run_editor_tool":
|
|
217
|
+
payload = _read(_editor_tool(params))
|
|
218
|
+
|
|
176
219
|
# Import and export pipeline
|
|
177
220
|
"get_import_status":
|
|
178
221
|
payload = ImportPipeline.new(_log).get_import_status(params)
|
|
@@ -259,6 +302,20 @@ func _run(operation: String, params: Dictionary) -> Dictionary:
|
|
|
259
302
|
return payload
|
|
260
303
|
|
|
261
304
|
|
|
305
|
+
## A scene or resource tool of the editor addon, run as editor_tools.gd runs it. Loaded for this op
|
|
306
|
+
## alone, because the tools bring the editor addon's scene and resource scripts with them: preloaded
|
|
307
|
+
## here, every op compiled them, and an operations directory without the addons beside it lost every
|
|
308
|
+
## op rather than this one.
|
|
309
|
+
func _editor_tool(params: Dictionary) -> Dictionary:
|
|
310
|
+
var own: Script = get_script()
|
|
311
|
+
var path: String = own.resource_path.get_base_dir().path_join("editor_tools.gd")
|
|
312
|
+
var tools: Script = ResourceLoader.load(path, "Script")
|
|
313
|
+
if tools == null or not tools.can_instantiate():
|
|
314
|
+
return {"ok": false, "error": "The editor tools did not load from %s." % path}
|
|
315
|
+
var answer: Dictionary = tools.call("run", params)
|
|
316
|
+
return answer
|
|
317
|
+
|
|
318
|
+
|
|
262
319
|
## A read the editor answers too, in the shape an operation answers: its refusal as the run's error.
|
|
263
320
|
func _read(read: Dictionary) -> Dictionary:
|
|
264
321
|
if not read.get("ok", false):
|
|
@@ -272,6 +272,9 @@ func value_for(target: Object, declared: Dictionary, raw: Variant) -> Array:
|
|
|
272
272
|
var elements: Array = elements_of(declared)
|
|
273
273
|
if _fraction(raw, expected_type, elements):
|
|
274
274
|
return [false, _fraction_refusal(target, property, raw)]
|
|
275
|
+
var rounded: String = Serialisation.inexact(raw, expected_type)
|
|
276
|
+
if not rounded.is_empty():
|
|
277
|
+
return [false, "%s.%s holds int: %s." % [target.get_class(), property, rounded]]
|
|
275
278
|
_refused = ""
|
|
276
279
|
value = parse(raw, expected_type, elements)
|
|
277
280
|
if not _refused.is_empty():
|
|
@@ -16,6 +16,10 @@ const Read = preload("reading.gd")
|
|
|
16
16
|
const NUMBERS: Array[int] = [TYPE_BOOL, TYPE_INT, TYPE_FLOAT]
|
|
17
17
|
const TEXTS: Array[int] = [TYPE_STRING, TYPE_STRING_NAME, TYPE_NODE_PATH]
|
|
18
18
|
|
|
19
|
+
# 2^53. A JSON number is a double, and every whole number below this one in size has a double of
|
|
20
|
+
# its own; from here on two or more share each double, so one arriving tells nobody which was sent.
|
|
21
|
+
const EXACT: int = 9007199254740992
|
|
22
|
+
|
|
19
23
|
# The types that cross as a plain JSON list and have to be built back into themselves. A list is
|
|
20
24
|
# what a caller can write by hand and what the serialiser answers with, and the parameter or
|
|
21
25
|
# property being written is the only thing that knows which of these it wanted.
|
|
@@ -53,6 +57,8 @@ const LIST_TYPES: Array[int] = [
|
|
|
53
57
|
# type it wanted.
|
|
54
58
|
const SERIALISERS: Dictionary = {
|
|
55
59
|
TYPE_NIL: "_serialize_nil",
|
|
60
|
+
TYPE_INT: "_serialize_int",
|
|
61
|
+
TYPE_PACKED_INT64_ARRAY: "_serialize_ints",
|
|
56
62
|
TYPE_FLOAT: "_serialize_float",
|
|
57
63
|
TYPE_PACKED_FLOAT32_ARRAY: "_serialize_floats",
|
|
58
64
|
TYPE_PACKED_FLOAT64_ARRAY: "_serialize_floats",
|
|
@@ -109,6 +115,8 @@ const BUILDERS: Dictionary = {
|
|
|
109
115
|
"NodePath": "_build_node_path",
|
|
110
116
|
"PackedByteArray": "_build_bytes",
|
|
111
117
|
"float": "_build_float",
|
|
118
|
+
"int": "_build_int",
|
|
119
|
+
"RID": "_build_rid",
|
|
112
120
|
"Dictionary": "_build_dictionary",
|
|
113
121
|
}
|
|
114
122
|
|
|
@@ -136,6 +144,8 @@ const REQUIRED: Dictionary = {
|
|
|
136
144
|
"NodePath": [["path"]],
|
|
137
145
|
"PackedByteArray": [["base64"]],
|
|
138
146
|
"float": [["value"]],
|
|
147
|
+
"int": [["value"]],
|
|
148
|
+
"RID": [["id"]],
|
|
139
149
|
"Dictionary": [["entries"]],
|
|
140
150
|
}
|
|
141
151
|
|
|
@@ -243,9 +253,40 @@ func deserialize_value(value: Variant) -> Variant:
|
|
|
243
253
|
return fields
|
|
244
254
|
if tag == "Dictionary" and not pairs(fields["entries"]):
|
|
245
255
|
return fields
|
|
256
|
+
# Digits only: the tag is there to carry what a number cannot, and {"value": 5} meant for some
|
|
257
|
+
# other shape is not to be read as 5.
|
|
258
|
+
if tag == "int" and not (fields["value"] is String and _exact_int(fields["value"]) != null):
|
|
259
|
+
return fields
|
|
260
|
+
if tag == "RID" and _exact_int(fields["id"]) == null:
|
|
261
|
+
return fields
|
|
246
262
|
return call(builder, fields)
|
|
247
263
|
|
|
248
264
|
|
|
265
|
+
## [param held] as the whole number it spells exactly, or null: an int, a JSON number that is whole
|
|
266
|
+
## and no larger than a double holds exactly, the digits of one, or the int tag carrying them. A
|
|
267
|
+
## fraction, or a number past 2^53 that arrived as a number and so may already be a neighbour of the
|
|
268
|
+
## one meant, is no id.
|
|
269
|
+
func _exact_int(held: Variant) -> Variant:
|
|
270
|
+
if held is int:
|
|
271
|
+
return held
|
|
272
|
+
if held is float:
|
|
273
|
+
var number: float = held
|
|
274
|
+
if is_finite(number) and number == floorf(number) and absf(number) < EXACT:
|
|
275
|
+
return int(number)
|
|
276
|
+
return null
|
|
277
|
+
if held is String:
|
|
278
|
+
var digits: String = held
|
|
279
|
+
var parsed: int = digits.to_int()
|
|
280
|
+
if digits.is_valid_int() and str(parsed) == digits:
|
|
281
|
+
return parsed
|
|
282
|
+
return null
|
|
283
|
+
if held is Dictionary:
|
|
284
|
+
var tagged: Dictionary = held
|
|
285
|
+
if str(tagged.get("_type", "")) == "int" and tagged.has("value"):
|
|
286
|
+
return _exact_int(tagged["value"])
|
|
287
|
+
return null
|
|
288
|
+
|
|
289
|
+
|
|
249
290
|
## The components a path reads off [param value], by the names GDScript gives them, or none for a
|
|
250
291
|
## value that has none: a vector's axes, a colour's channels, a rectangle's corners and size, a
|
|
251
292
|
## transform's basis and origin. A value of one of these is a copy wherever it is read, so a write
|
|
@@ -393,6 +434,11 @@ func fitted(value: Variant, type: int) -> Variant:
|
|
|
393
434
|
if not simple.has(type) or not simple.has(typeof(rebuilt)):
|
|
394
435
|
return rebuilt
|
|
395
436
|
|
|
437
|
+
# Digits are read as digits, since read as JSON they become a double and one past 2^53 a
|
|
438
|
+
# neighbour of the number written.
|
|
439
|
+
if rebuilt is String and type == TYPE_INT and _exact_int(rebuilt) != null:
|
|
440
|
+
return _exact_int(rebuilt)
|
|
441
|
+
|
|
396
442
|
if rebuilt is String and type != TYPE_STRING:
|
|
397
443
|
var text: String = rebuilt
|
|
398
444
|
var parsed: Variant = Read.json_or_null(text)
|
|
@@ -408,13 +454,33 @@ func fitted(value: Variant, type: int) -> Variant:
|
|
|
408
454
|
return type_convert(rebuilt, type)
|
|
409
455
|
|
|
410
456
|
|
|
457
|
+
## Why [param given], meant for an int, may not be the number that was sent, or "" when it is. A
|
|
458
|
+
## JSON number is a double wherever it is read, so a whole number from 2^53 on arrives as the nearest
|
|
459
|
+
## double to the one written: 9007199254740993 sent as a number arrives as 9007199254740992, and a
|
|
460
|
+
## seed written that way would be stored as its neighbour with the answer saying it was written.
|
|
461
|
+
static func inexact(given: Variant, type: int) -> String:
|
|
462
|
+
if type != TYPE_INT or (typeof(given) != TYPE_FLOAT and typeof(given) != TYPE_INT):
|
|
463
|
+
return ""
|
|
464
|
+
var number: float = Read.as_float(given)
|
|
465
|
+
if not is_finite(number) or absf(number) < EXACT:
|
|
466
|
+
return ""
|
|
467
|
+
return (
|
|
468
|
+
(
|
|
469
|
+
"%s is 2^53 or more, where a JSON number holds a whole number only to the nearest double,"
|
|
470
|
+
+ ' so it may not be the number sent: send the digits as {"_type": "int", "value": "..."}'
|
|
471
|
+
+ ", the form a read answers with"
|
|
472
|
+
)
|
|
473
|
+
% String.num(number, 0)
|
|
474
|
+
)
|
|
475
|
+
|
|
476
|
+
|
|
411
477
|
## How a value of [param type] is written to be read as one, for a refusal to name, or empty for a
|
|
412
478
|
## type that is not built from fields. A refusal naming only the two types left a caller to find
|
|
413
479
|
## the shape in the description of another op.
|
|
414
480
|
static func written_as(type: int) -> String:
|
|
415
481
|
var tag: String = type_string(type)
|
|
416
482
|
var alternatives: Array = REQUIRED.get(tag, [])
|
|
417
|
-
if alternatives.is_empty() or tag
|
|
483
|
+
if alternatives.is_empty() or tag in ["float", "int", "Dictionary"]:
|
|
418
484
|
return ""
|
|
419
485
|
var keys: Array = alternatives[0]
|
|
420
486
|
var parts: Array[String] = []
|
|
@@ -530,10 +596,32 @@ func _serialize_node_path(value: NodePath) -> Dictionary:
|
|
|
530
596
|
return {"path": str(value), "_type": "NodePath"}
|
|
531
597
|
|
|
532
598
|
|
|
533
|
-
#
|
|
534
|
-
#
|
|
599
|
+
# By its id, which is all an RID is from outside the server holding it, and which builds it back:
|
|
600
|
+
# a viewport's RID read off one call is what RenderingServer's render timing takes in the next.
|
|
535
601
|
func _serialize_rid(value: RID) -> Dictionary:
|
|
536
|
-
return {"id": value.get_id(), "_type": "RID"}
|
|
602
|
+
return {"id": _serialize_int(value.get_id()), "_type": "RID"}
|
|
603
|
+
|
|
604
|
+
|
|
605
|
+
# A whole number from 2^53 on as its digits, because JSON numbers are doubles on the server's side
|
|
606
|
+
# and on the engine's, and from there on neighbouring whole numbers share a double. A RefCounted's
|
|
607
|
+
# instance id is one, with its top bit set, and a resource uid another; read as numbers they came
|
|
608
|
+
# back as a neighbouring id, which named nothing or something else.
|
|
609
|
+
func _serialize_int(value: int) -> Variant:
|
|
610
|
+
if value > -EXACT and value < EXACT:
|
|
611
|
+
return value
|
|
612
|
+
return {"_type": "int", "value": str(value)}
|
|
613
|
+
|
|
614
|
+
|
|
615
|
+
# A packed list of 64-bit numbers stays the JSON list it is unless something in it is too large for
|
|
616
|
+
# a double to hold.
|
|
617
|
+
func _serialize_ints(value: PackedInt64Array) -> Variant:
|
|
618
|
+
for number: int in value:
|
|
619
|
+
if number <= -EXACT or number >= EXACT:
|
|
620
|
+
var items: Array = []
|
|
621
|
+
for each: int in value:
|
|
622
|
+
items.append(_serialize_int(each))
|
|
623
|
+
return items
|
|
624
|
+
return value
|
|
537
625
|
|
|
538
626
|
|
|
539
627
|
# A bound method, said in the terms a caller could act on: what it is on, and what it is called.
|
|
@@ -672,7 +760,7 @@ func _describe_object(value: Object) -> Dictionary:
|
|
|
672
760
|
# in a costume and reads as if the object had been asked and had nothing to say.
|
|
673
761
|
if value.has_method("_to_string"):
|
|
674
762
|
described["says"] = value.to_string()
|
|
675
|
-
described["id"] = value.get_instance_id()
|
|
763
|
+
described["id"] = _serialize_int(value.get_instance_id())
|
|
676
764
|
return described
|
|
677
765
|
|
|
678
766
|
|
|
@@ -808,6 +896,16 @@ func _build_float(fields: Dictionary) -> float:
|
|
|
808
896
|
return Read.as_float(fields)
|
|
809
897
|
|
|
810
898
|
|
|
899
|
+
func _build_int(fields: Dictionary) -> int:
|
|
900
|
+
var exact: int = _exact_int(fields["value"])
|
|
901
|
+
return exact
|
|
902
|
+
|
|
903
|
+
|
|
904
|
+
func _build_rid(fields: Dictionary) -> RID:
|
|
905
|
+
var id: int = _exact_int(fields["id"])
|
|
906
|
+
return rid_from_int64(id)
|
|
907
|
+
|
|
908
|
+
|
|
811
909
|
## The entries of a dictionary whose keys are not all text. JSON carries every number as a float,
|
|
812
910
|
## and a dictionary keys 1.0 apart from 1, so a whole-number key is built as an int: the key a
|
|
813
911
|
## game uses far more often, and one JSON cannot tell from the float.
|