fuck it im bored
This commit is contained in:
parent
aaf5b6bb72
commit
ac3f0d73a6
12 changed files with 526 additions and 673 deletions
21
godot/addons/godot_object_serializer/LICENSE
Normal file
21
godot/addons/godot_object_serializer/LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2025 Charles Crete
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
63
godot/addons/godot_object_serializer/binary_serializer.gd
Normal file
63
godot/addons/godot_object_serializer/binary_serializer.gd
Normal file
|
@ -0,0 +1,63 @@
|
|||
## Serializer to be used with Godot's built-in binary serialization ([method @GlobalScope.var_to_bytes] and [method @GlobalScope.bytes_to_var]).
|
||||
## This serializes objects but leaves built-in Godot types as-is.
|
||||
class_name BinarySerializer
|
||||
|
||||
|
||||
## Serialize [param data] to value which can be passed to [method @GlobalScope.var_to_bytes].
|
||||
static func serialize_var(value: Variant) -> Variant:
|
||||
match typeof(value):
|
||||
TYPE_OBJECT:
|
||||
var name: StringName = value.get_script().get_global_name()
|
||||
var object_entry := ObjectSerializer._get_entry(name, value.get_script())
|
||||
if !object_entry:
|
||||
assert(
|
||||
false,
|
||||
(
|
||||
"Could not find type (%s) in registry\n%s"
|
||||
% [name if name else "no name", value.get_script().source_code]
|
||||
)
|
||||
)
|
||||
|
||||
return object_entry.serialize(value, serialize_var)
|
||||
TYPE_ARRAY:
|
||||
return value.map(serialize_var)
|
||||
TYPE_DICTIONARY:
|
||||
var result := {}
|
||||
for i: Variant in value:
|
||||
result[i] = serialize_var(value[i])
|
||||
return result
|
||||
|
||||
return value
|
||||
|
||||
|
||||
## Serialize [param data] into bytes with [method BinaryObjectSerializer.serialize_var] and [method @GlobalScope.var_to_bytes].
|
||||
static func serialize_bytes(value: Variant) -> PackedByteArray:
|
||||
return var_to_bytes(serialize_var(value))
|
||||
|
||||
|
||||
## Deserialize [param data] from [method @GlobalScope.bytes_to_var] to value.
|
||||
static func deserialize_var(value: Variant) -> Variant:
|
||||
match typeof(value):
|
||||
TYPE_DICTIONARY:
|
||||
if value.has(ObjectSerializer.type_field):
|
||||
var type: String = value.get(ObjectSerializer.type_field)
|
||||
if type.begins_with(ObjectSerializer.object_type_prefix):
|
||||
var entry := ObjectSerializer._get_entry(type)
|
||||
if !entry:
|
||||
assert(false, "Could not find type (%s) in registry" % type)
|
||||
|
||||
return entry.deserialize(value, deserialize_var)
|
||||
|
||||
var result := {}
|
||||
for i: Variant in value:
|
||||
result[i] = deserialize_var(value[i])
|
||||
return result
|
||||
TYPE_ARRAY:
|
||||
return value.map(deserialize_var)
|
||||
|
||||
return value
|
||||
|
||||
|
||||
## Deserialize bytes [param data] to value with [method @GlobalScope.bytes_to_var] and [method BinaryObjectSerializer.deserialize_var].
|
||||
static func deserialize_bytes(value: PackedByteArray) -> Variant:
|
||||
return deserialize_var(bytes_to_var(value))
|
101
godot/addons/godot_object_serializer/dictionary_serializer.gd
Normal file
101
godot/addons/godot_object_serializer/dictionary_serializer.gd
Normal file
|
@ -0,0 +1,101 @@
|
|||
## Serializer to be used with JSON serialization ([method JSON.stringify] and [method JSON.parse_string]).
|
||||
## This serializes objects and built-in Godot types.
|
||||
class_name DictionarySerializer
|
||||
|
||||
# Types that can natively be represented in JSON
|
||||
const _JSON_SERIALIZABLE_TYPES = [
|
||||
TYPE_NIL, TYPE_BOOL, TYPE_INT, TYPE_FLOAT, TYPE_STRING, TYPE_STRING_NAME
|
||||
]
|
||||
|
||||
## Controls if [PackedByteArray] should be serialized as base64 (instead of array of bytes as uint8).
|
||||
## It's highly recommended to leave this enabled as it will result to smaller serialized payloads and should be faster.
|
||||
## This can be changed, but must be configured before any serialization or deserialization.
|
||||
static var bytes_as_base64 := true
|
||||
## The type of the object for [PackedByteArray] when [member bytes_as_base64] is enabled.
|
||||
## This should be set to something unlikely to clash with built-in type names or [member ObjectSerializer.object_type_prefix].
|
||||
## This can be changed, but must be configured before any serialization or deserialization.
|
||||
static var bytes_to_base64_type := "PackedByteArray_Base64"
|
||||
|
||||
|
||||
## Serialize [param data] into value which can be passed to [method JSON.stringify].
|
||||
static func serialize_var(value: Variant) -> Variant:
|
||||
match typeof(value):
|
||||
TYPE_OBJECT:
|
||||
var name: StringName = value.get_script().get_global_name()
|
||||
var object_entry := ObjectSerializer._get_entry(name, value.get_script())
|
||||
if !object_entry:
|
||||
assert(
|
||||
false,
|
||||
(
|
||||
"Could not find type (%s) in registry\n%s"
|
||||
% [name if name else "no name", value.get_script().source_code]
|
||||
)
|
||||
)
|
||||
|
||||
return object_entry.serialize(value, serialize_var)
|
||||
|
||||
TYPE_ARRAY:
|
||||
return value.map(serialize_var)
|
||||
|
||||
TYPE_DICTIONARY:
|
||||
var result := {}
|
||||
for i: Variant in value:
|
||||
result[i] = serialize_var(value[i])
|
||||
return result
|
||||
|
||||
TYPE_PACKED_BYTE_ARRAY:
|
||||
if bytes_as_base64:
|
||||
return {
|
||||
ObjectSerializer.type_field: bytes_to_base64_type,
|
||||
ObjectSerializer.args_field:
|
||||
Marshalls.raw_to_base64(value) if !value.is_empty() else ""
|
||||
}
|
||||
|
||||
if _JSON_SERIALIZABLE_TYPES.has(typeof(value)):
|
||||
return value
|
||||
|
||||
return {
|
||||
ObjectSerializer.type_field: type_string(typeof(value)),
|
||||
ObjectSerializer.args_field: JSON.from_native(value)["args"]
|
||||
}
|
||||
|
||||
|
||||
## Serialize [param data] into JSON string with [method DictionaryObjectSerializer.serialize_var] and [method JSON.stringify]. Supports same arguments as [method JSON.stringify]
|
||||
static func serialize_json(
|
||||
value: Variant, indent := "", sort_keys := true, full_precision := false
|
||||
) -> String:
|
||||
return JSON.stringify(serialize_var(value), indent, sort_keys, full_precision)
|
||||
|
||||
|
||||
## Deserialize [data] from [JSON.parse_string] into value.
|
||||
static func deserialize_var(value: Variant) -> Variant:
|
||||
match typeof(value):
|
||||
TYPE_DICTIONARY:
|
||||
if value.has(ObjectSerializer.type_field):
|
||||
var type: String = value.get(ObjectSerializer.type_field)
|
||||
if bytes_as_base64 and type == bytes_to_base64_type:
|
||||
return Marshalls.base64_to_raw(value[ObjectSerializer.args_field])
|
||||
|
||||
if type.begins_with(ObjectSerializer.object_type_prefix):
|
||||
var entry := ObjectSerializer._get_entry(type)
|
||||
if !entry:
|
||||
assert(false, "Could not find type (%s) in registry" % type)
|
||||
|
||||
return entry.deserialize(value, deserialize_var, true)
|
||||
|
||||
return JSON.to_native({"type": type, "args": value[ObjectSerializer.args_field]})
|
||||
|
||||
var result := {}
|
||||
for i: Variant in value:
|
||||
result[i] = deserialize_var(value[i])
|
||||
return result
|
||||
|
||||
TYPE_ARRAY:
|
||||
return value.map(deserialize_var)
|
||||
|
||||
return value
|
||||
|
||||
|
||||
## Deserialize JSON string [param data] to value with [method JSON.parse_string] and [method DictionaryObjectSerializer.deserialize_var].
|
||||
static func deserialize_json(value: String) -> Variant:
|
||||
return deserialize_var(JSON.parse_string(value))
|
131
godot/addons/godot_object_serializer/example
Normal file
131
godot/addons/godot_object_serializer/example
Normal file
|
@ -0,0 +1,131 @@
|
|||
extends SceneTree
|
||||
|
||||
|
||||
# Example data class. Can extend any type, include Resource
|
||||
class Data:
|
||||
# Supports all primitive types (String, int, float, bool, null), including @export variables
|
||||
@export var string: String
|
||||
# Supports all extended built-in types (Vector2/3/4/i, Rect2/i, Transform2D/3D, Color, Packed*Array, etc)
|
||||
var vector: Vector3
|
||||
# Supports enum
|
||||
var enum_state: State
|
||||
# Supports arrays, including Array[Variant]
|
||||
var array: Array[int]
|
||||
# Supports dictionaries, including Dictionary[Variant, Variant]
|
||||
var dictionary: Dictionary[String, Vector2]
|
||||
# Supports efficient byte array serialization to base64
|
||||
var packed_byte_array: PackedByteArray
|
||||
# Supports nested data, either as a field or in array/dictionary
|
||||
var nested: DataResource
|
||||
|
||||
|
||||
class DataResource:
|
||||
extends Resource
|
||||
var name: String
|
||||
|
||||
|
||||
enum State { OPENED, CLOSED }
|
||||
|
||||
|
||||
# _static_init is used to register scripts without having to instanciate the script.
|
||||
# It's recommended to either place all registrations in a single script, or have each script register itself.
|
||||
static func _static_init() -> void:
|
||||
# Required: Register possible object scripts
|
||||
ObjectSerializer.register_script("Data", Data)
|
||||
ObjectSerializer.register_script("DataResource", DataResource)
|
||||
|
||||
|
||||
# Setup testing data
|
||||
var data := Data.new()
|
||||
|
||||
|
||||
func _init() -> void:
|
||||
data.string = "Lorem ipsum"
|
||||
data.vector = Vector3(1, 2, 3)
|
||||
data.enum_state = State.CLOSED
|
||||
data.array = [1, 2]
|
||||
data.dictionary = {"position": Vector2(1, 2)}
|
||||
data.packed_byte_array = PackedByteArray([1, 2, 3, 4, 5, 6, 7, 8])
|
||||
var data_resource := DataResource.new()
|
||||
data_resource.name = "dolor sit amet"
|
||||
data.nested = data_resource
|
||||
|
||||
json_serialization()
|
||||
binary_serialization()
|
||||
|
||||
|
||||
func json_serialization() -> void:
|
||||
# Serialize to JSON
|
||||
# Alternative: DictionarySerializer.serialize_json(data)
|
||||
var serialized: Variant = DictionarySerializer.serialize_var(data)
|
||||
var json := JSON.stringify(serialized, "\t")
|
||||
print(json)
|
||||
""" Output:
|
||||
{
|
||||
"._type": "Object_Data",
|
||||
"string": "Lorem ipsum",
|
||||
"vector": {
|
||||
"._type": "Vector3",
|
||||
"._": [
|
||||
1.0,
|
||||
2.0,
|
||||
3.0
|
||||
]
|
||||
},
|
||||
"enum_state": 1,
|
||||
"array": [
|
||||
1,
|
||||
2
|
||||
],
|
||||
"dictionary": {
|
||||
"position": {
|
||||
"._type": "Vector2",
|
||||
"._": [
|
||||
1.0,
|
||||
2.0
|
||||
]
|
||||
}
|
||||
},
|
||||
"packed_byte_array": {
|
||||
"._type": "PackedByteArray_Base64",
|
||||
"._": "AQIDBAUGBwg="
|
||||
},
|
||||
"nested": {
|
||||
"._type": "Object_DataResource",
|
||||
"name": "dolor sit amet"
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
# Verify after JSON deserialization
|
||||
# Alternative: DictionarySerializer.deserialize_json(json)
|
||||
var parsed_json = JSON.parse_string(json)
|
||||
var deserialized: Data = DictionarySerializer.deserialize_var(parsed_json)
|
||||
_assert_data(deserialized)
|
||||
|
||||
|
||||
func binary_serialization() -> void:
|
||||
# Serialize to bytes
|
||||
# Alternative: BinarySerializer.serialize_bytes(data)
|
||||
var serialized: Variant = BinarySerializer.serialize_var(data)
|
||||
var bytes := var_to_bytes(serialized)
|
||||
print(bytes)
|
||||
# Output: List of bytes
|
||||
|
||||
# Verify after bytes deserialization.
|
||||
# Alternative: BinarySerializer.deserialize_bytes(bytes)
|
||||
var parsed_bytes = bytes_to_var(bytes)
|
||||
var deserialized: Data = BinarySerializer.deserialize_var(parsed_bytes)
|
||||
_assert_data(deserialized)
|
||||
|
||||
|
||||
func _assert_data(deserialized: Data) -> void:
|
||||
assert(data.string == deserialized.string, "string is different")
|
||||
assert(data.vector == deserialized.vector, "vector is different")
|
||||
assert(data.enum_state == deserialized.enum_state, "enum_state is different")
|
||||
assert(data.array == deserialized.array, "array is different")
|
||||
assert(data.dictionary == deserialized.dictionary, "dictionary is different")
|
||||
assert(
|
||||
data.packed_byte_array == deserialized.packed_byte_array, "packed_byte_array is different"
|
||||
)
|
||||
assert(data.nested.name == deserialized.nested.name, "nested.name is different")
|
185
godot/addons/godot_object_serializer/object_serializer.gd
Normal file
185
godot/addons/godot_object_serializer/object_serializer.gd
Normal file
|
@ -0,0 +1,185 @@
|
|||
## Main godot-object-serializer class. Stores the script registry.
|
||||
## [url]https://github.com/Cretezy/godot-object-serializer[/url]
|
||||
class_name ObjectSerializer
|
||||
|
||||
## The field containing the type in serialized object values. Not recommended to change.
|
||||
##
|
||||
## This should be set to something unlikely to clash with keys in objects/dictionaries.
|
||||
##
|
||||
## This can be changed, but must be configured before any serialization or deserialization.
|
||||
static var type_field := "._type"
|
||||
|
||||
## The field containing the constructor arguments in serialized object values. Not recommended to change.
|
||||
##
|
||||
## This should be set to something unlikely to clash with keys in objects.
|
||||
##
|
||||
## This can be changed, but must be configured before any serialization or deserialization.
|
||||
static var args_field := "._"
|
||||
|
||||
## The prefix for object types stored in [member type_field]. Not recommended to change.
|
||||
##
|
||||
## This should be set to something unlikely to clash with built-in type names.
|
||||
##
|
||||
## This can be changed, but must be configured before any serialization or deserialization.
|
||||
static var object_type_prefix := "Object_"
|
||||
|
||||
## By default, variables with [@GlobalScope.PROPERTY_USAGE_SCRIPT_VARIABLE] are serialized (all variables have this by default).
|
||||
## When [member require_export_storage] is true, variables will also require [@GlobalScope.PROPERTY_USAGE_STORAGE] to be serialized.
|
||||
## This can be set on variables using [annotation @GDScript.@export_storage]. Example: [code]@export_storage var name: String[/code]
|
||||
static var require_export_storage := false
|
||||
|
||||
## Registry of object types
|
||||
static var _script_registry: Dictionary[String, _ScriptRegistryEntry]
|
||||
|
||||
|
||||
## Registers a script (an object type) to be serialized/deserialized. All custom types (including nested types) must be registered [b]before[/b] using this library.
|
||||
## [param name] can be empty if script uses [code]class_name[/code] (e.g [code]ObjectSerializer.register_script("", Data)[/code]), but it's generally better to set the name.
|
||||
static func register_script(name: StringName, script: Script) -> void:
|
||||
var script_name := _get_script_name(script, name)
|
||||
assert(script_name, "Script must have name\n" + script.source_code)
|
||||
var entry := _ScriptRegistryEntry.new()
|
||||
entry.script_type = script
|
||||
entry.type = object_type_prefix + script_name
|
||||
_script_registry[entry.type] = entry
|
||||
|
||||
|
||||
## Registers multiple scripts (object types) to be serialized/deserialized from a dictionary.
|
||||
## See [method ObjectSerializer.register_script]
|
||||
static func register_scripts(scripts: Dictionary[String, Script]) -> void:
|
||||
for name in scripts:
|
||||
register_script(name, scripts[name])
|
||||
|
||||
|
||||
static func _get_script_name(script: Script, name: StringName = "") -> StringName:
|
||||
if name:
|
||||
return name
|
||||
if script.resource_name:
|
||||
return script.resource_name
|
||||
if script.get_global_name():
|
||||
return script.get_global_name()
|
||||
return ""
|
||||
|
||||
|
||||
static func _get_entry(name: StringName = "", script: Script = null) -> _ScriptRegistryEntry:
|
||||
if name:
|
||||
var entry: _ScriptRegistryEntry = _script_registry.get(name)
|
||||
if entry:
|
||||
return entry
|
||||
|
||||
if script:
|
||||
for i: String in _script_registry:
|
||||
var entry: _ScriptRegistryEntry = _script_registry.get(i)
|
||||
if entry:
|
||||
if script == entry.script_type:
|
||||
return entry
|
||||
|
||||
return null
|
||||
|
||||
|
||||
class _ScriptRegistryEntry:
|
||||
var type: String
|
||||
var script_type: Script
|
||||
|
||||
func serialize(value: Variant, next: Callable) -> Variant:
|
||||
if value.has_method("_serialize"):
|
||||
var result: Dictionary = value._serialize(next)
|
||||
result[ObjectSerializer.type_field] = type
|
||||
return result
|
||||
|
||||
var result := {ObjectSerializer.type_field: type}
|
||||
|
||||
var excluded_properties: Array[String] = []
|
||||
if value.has_method("_get_excluded_properties"):
|
||||
excluded_properties = value._get_excluded_properties()
|
||||
|
||||
var partial: Dictionary = {}
|
||||
if value.has_method("_serialize_partial"):
|
||||
partial = value._serialize_partial(next)
|
||||
|
||||
for property: Dictionary in value.get_property_list():
|
||||
if (
|
||||
property.usage & PROPERTY_USAGE_SCRIPT_VARIABLE
|
||||
and (
|
||||
!ObjectSerializer.require_export_storage
|
||||
or property.usage & PROPERTY_USAGE_STORAGE
|
||||
)
|
||||
and !excluded_properties.has(property.name)
|
||||
and !partial.has(property.name)
|
||||
):
|
||||
result[property.name] = next.call(value.get(property.name))
|
||||
|
||||
for key in partial:
|
||||
result[key] = partial[key]
|
||||
|
||||
if value.has_method("_get_constructor_args"):
|
||||
var args: Array = value._get_constructor_args()
|
||||
result[ObjectSerializer.args_field] = args
|
||||
|
||||
return result
|
||||
|
||||
## When [param json_keys] is enabled, attempt to convert int/float/bool string keys into values
|
||||
func deserialize(value: Variant, next: Callable, json_keys := false) -> Variant:
|
||||
if script_type.has_method("_deserialize"):
|
||||
return script_type._deserialize(value, next)
|
||||
|
||||
var instance: Variant
|
||||
if value.has(ObjectSerializer.args_field):
|
||||
instance = script_type.new.callv(value[ObjectSerializer.args_field])
|
||||
else:
|
||||
instance = script_type.new()
|
||||
|
||||
var excluded_properties: Array[String] = []
|
||||
if instance.has_method("_get_excluded_properties"):
|
||||
excluded_properties = instance._get_excluded_properties()
|
||||
|
||||
var partial: Dictionary = {}
|
||||
if instance.has_method("_deserialize_partial"):
|
||||
partial = instance._deserialize_partial(value, next)
|
||||
|
||||
for key: String in value:
|
||||
if (
|
||||
key == ObjectSerializer.type_field
|
||||
or key == ObjectSerializer.args_field
|
||||
or excluded_properties.has(key)
|
||||
or partial.has(key)
|
||||
):
|
||||
continue
|
||||
|
||||
var key_value: Variant = next.call(value[key])
|
||||
match typeof(key_value):
|
||||
TYPE_DICTIONARY:
|
||||
if json_keys and instance[key].is_typed_key():
|
||||
match instance[key].get_typed_key_builtin():
|
||||
TYPE_STRING:
|
||||
instance[key].assign(key_value)
|
||||
TYPE_BOOL:
|
||||
var dict: Dictionary[bool, Variant] = {}
|
||||
for i in key_value:
|
||||
dict[i == "true"] = key_value[i]
|
||||
instance[key].assign(dict)
|
||||
TYPE_INT:
|
||||
var dict: Dictionary[int, Variant] = {}
|
||||
for i in key_value:
|
||||
dict[int(i)] = key_value[i]
|
||||
instance[key].assign(dict)
|
||||
TYPE_FLOAT:
|
||||
var dict: Dictionary[float, Variant] = {}
|
||||
for i in key_value:
|
||||
dict[float(i)] = key_value[i]
|
||||
instance[key].assign(dict)
|
||||
_:
|
||||
assert(
|
||||
false,
|
||||
"Trying to deserialize from JSON to a dictionary with non-primitive (String/int/float/bool) keys"
|
||||
)
|
||||
else:
|
||||
instance[key].assign(key_value)
|
||||
TYPE_ARRAY:
|
||||
instance[key].assign(key_value)
|
||||
_:
|
||||
instance[key] = key_value
|
||||
|
||||
for key in partial:
|
||||
instance[key] = partial[key]
|
||||
|
||||
return instance
|
7
godot/addons/godot_object_serializer/plugin.cfg
Normal file
7
godot/addons/godot_object_serializer/plugin.cfg
Normal file
|
@ -0,0 +1,7 @@
|
|||
[plugin]
|
||||
|
||||
name="Godot Object Serializer"
|
||||
description="Safely serialize/deserialize objects and built-in types in Godot"
|
||||
author="Cretezy"
|
||||
version="0.3.0"
|
||||
script=""
|
|
@ -1,62 +0,0 @@
|
|||
class_name Encoding extends RefCounted
|
||||
|
||||
enum Type {
|
||||
Ascii,
|
||||
Utf8,
|
||||
Utf16,
|
||||
Utf32,
|
||||
Wchar
|
||||
}
|
||||
|
||||
static var Ascii = from(Type.Ascii)
|
||||
static var Utf8 = from(Type.Utf8)
|
||||
static var Utf16 = from(Type.Utf16)
|
||||
static var Utf32 = from(Type.Utf32)
|
||||
static var Wchar = from(Type.Wchar)
|
||||
|
||||
var type: Type
|
||||
|
||||
func _init(_type: Type) -> void:
|
||||
type = _type
|
||||
|
||||
static func from(type: Type) -> Encoding:
|
||||
return Encoding.new(type)
|
||||
|
||||
static func byte_size_of(encoding: Type) -> int:
|
||||
match encoding:
|
||||
Type.Ascii: return 1
|
||||
Type.Utf8: return 1
|
||||
Type.Utf16: return 2
|
||||
Type.Utf32: return 4
|
||||
Type.Wchar:
|
||||
match OS.get_name():
|
||||
"Windows": return 2
|
||||
_: return 4
|
||||
return -1
|
||||
|
||||
func byte_size() -> int:
|
||||
return byte_size_of(type)
|
||||
|
||||
static func encode_str_as(value: String, encoding: Type) -> PackedByteArray:
|
||||
match encoding:
|
||||
Type.Ascii: return value.to_ascii_buffer()
|
||||
Type.Utf8: return value.to_utf8_buffer()
|
||||
Type.Utf16: return value.to_utf16_buffer()
|
||||
Type.Utf32: return value.to_utf32_buffer()
|
||||
Type.Wchar: return value.to_wchar_buffer()
|
||||
return PackedByteArray()
|
||||
|
||||
func encode_str(value: String) -> PackedByteArray:
|
||||
return encode_str_as(value, type)
|
||||
|
||||
static func decode_str_as(value: PackedByteArray, encoding: Type) -> String:
|
||||
match encoding:
|
||||
Type.Ascii: return value.get_string_from_ascii()
|
||||
Type.Utf8: return value.get_string_from_utf8()
|
||||
Type.Utf16: return value.get_string_from_utf16()
|
||||
Type.Utf32: return value.get_string_from_utf32()
|
||||
Type.Wchar: return value.get_string_from_wchar()
|
||||
return ""
|
||||
|
||||
func decode_str(value: PackedByteArray) -> String:
|
||||
return decode_str_as(value, type)
|
|
@ -1,364 +0,0 @@
|
|||
class_name Json
|
||||
|
||||
static func stringify(value: Variant, allow_eval: bool = false) -> String:
|
||||
var ser = Json.Serializer.new(allow_eval)
|
||||
Serde.serialize(ser, value, allow_eval)
|
||||
return ser.output
|
||||
|
||||
static func parse_string(value: String, into: Variant = null, allow_eval: bool = false) -> Variant:
|
||||
var de = Json.Deserializer.from_string(value)
|
||||
if into != null and into.has_method(Serde.DeserializeMethod):
|
||||
return into.call(Serde.DeserializeMethod, de)
|
||||
else:
|
||||
return de.deserialize_any(Serde.GenericVisitor.new())
|
||||
|
||||
class SerializeSeq extends Serde.SerializeSeq:
|
||||
var ser: Json.Serializer
|
||||
var first: bool = true
|
||||
|
||||
func _init(ser: Json.Serializer) -> void:
|
||||
self.ser = ser
|
||||
ser.write('[')
|
||||
|
||||
func serialize_element(value: Variant) -> Result:
|
||||
if not first: ser.write(',')
|
||||
first = false
|
||||
Serde.serialize(ser, value, ser.allow_eval)
|
||||
return Result.Unit
|
||||
|
||||
func end() -> Result:
|
||||
ser.write(']')
|
||||
return Result.Unit
|
||||
|
||||
class SerializeMap extends Serde.SerializeMap:
|
||||
var ser: Json.Serializer
|
||||
var first: bool = true
|
||||
|
||||
func _init(ser: Json.Serializer) -> void:
|
||||
self.ser = ser
|
||||
ser.write('{')
|
||||
|
||||
func serialize_key(value: Variant) -> Result:
|
||||
if not first: ser.write(',')
|
||||
first = false
|
||||
Serde.serialize(ser, value, ser.allow_eval)
|
||||
return Result.Unit
|
||||
|
||||
func serialize_value(value: Variant) -> Result:
|
||||
ser.write(':')
|
||||
Serde.serialize(ser, value, ser.allow_eval)
|
||||
return Result.Unit
|
||||
|
||||
func end() -> Result:
|
||||
ser.write('}')
|
||||
return Result.Unit
|
||||
|
||||
class Serializer extends Serde.Serializer:
|
||||
var output: String
|
||||
var allow_eval: bool
|
||||
|
||||
func _init(allow_eval: bool = false) -> void:
|
||||
self.allow_eval = allow_eval
|
||||
|
||||
func write(value: String) -> Result:
|
||||
output += value
|
||||
return Result.Unit
|
||||
|
||||
func write_string(value: Variant) -> Result:
|
||||
return write(str(value))
|
||||
|
||||
func serialize_nil() -> Result:
|
||||
return write("null")
|
||||
|
||||
func serialize_bool(value: bool) -> Result:
|
||||
return write_string(value)
|
||||
|
||||
func serialize_int(value: int):
|
||||
return write_string(value)
|
||||
|
||||
func serialize_float(value: float) -> Result:
|
||||
return write_string(value)
|
||||
|
||||
func serialize_string(value: String):
|
||||
return write('"%s"' % value)
|
||||
|
||||
func serialize_seq(_len: int):
|
||||
return Json.SerializeSeq.new(self)
|
||||
|
||||
func serialize_dict(_len: int):
|
||||
return Json.SerializeMap.new(self)
|
||||
|
||||
func serialize_object(_name: StringName, len: int):
|
||||
return serialize_dict(len)
|
||||
|
||||
class Parser extends RefCounted:
|
||||
var de: Json.Deserializer
|
||||
var iter: PeekableIter
|
||||
|
||||
func _init(de: Json.Deserializer) -> void:
|
||||
self.de = de
|
||||
iter = de._iter
|
||||
|
||||
static func eq(a: Variant, b: Variant) -> bool:
|
||||
return a == b
|
||||
|
||||
static func _join(value: Array) -> String:
|
||||
return "".join(value)
|
||||
|
||||
static func _collect(results: Array[Result]) -> Result:
|
||||
return Result.collect_ok(results).map(_join)
|
||||
|
||||
func any() -> Result:
|
||||
match iter.peek():
|
||||
var x when x.is_some():
|
||||
return Result.ok(iter.next().unwrap())
|
||||
_: return Result.err(Error.new("eof"))
|
||||
|
||||
func char(ch: String) -> Result:
|
||||
match iter.peek():
|
||||
var x when x.filter(eq.bind(ch)).is_some(): return Result.ok(iter.next().unwrap())
|
||||
var x: return Result.err(Error.new('expecting "%s", got "%s"' % [ch, x.unwrap()]))
|
||||
|
||||
func str(value: String) -> Result:
|
||||
for i in range(0, value.length()):
|
||||
var ch = self.char(value[i])
|
||||
if ch.is_err(): return ch
|
||||
return Result.ok(value)
|
||||
|
||||
func any_of(parsers: Array[Callable]) -> Result:
|
||||
for parser in parsers:
|
||||
var result = parser.call()
|
||||
if result.is_ok(): return result
|
||||
return Result.err(Error.new('none of the provided parsers matched input'))
|
||||
|
||||
func all_of(parsers: Array) -> Result:
|
||||
var results: Array[String] = []
|
||||
for parser in parsers:
|
||||
match parser.call():
|
||||
var x when x.is_ok(): results.append(x.unwrap())
|
||||
var x: return x
|
||||
return Result.ok(results)
|
||||
|
||||
func many(parser: Callable, limit: int = 1000) -> Result:
|
||||
var results: Array[String] = []
|
||||
var value = parser.call()
|
||||
|
||||
while value.is_ok() and limit > 0:
|
||||
results.append(value.unwrap())
|
||||
value = parser.call()
|
||||
limit -= 1
|
||||
|
||||
if limit <= 0: return Result.err(Error.new("executed too many times (%s)" % limit))
|
||||
elif len(results) == 0: return Result.err(Error.new("many matched nothing"))
|
||||
else: return Result.ok(results)
|
||||
|
||||
func until(parser: Callable) -> Result:
|
||||
return many(
|
||||
func ():
|
||||
match parser.call():
|
||||
var x when x.is_err(): return any()
|
||||
_: return Result.err(null)
|
||||
)
|
||||
|
||||
func negate(parser: Callable) -> Result:
|
||||
match parser.call():
|
||||
var x when x.is_ok(): return Result.err('negated parser passed with value "%s"' % x.unwrap())
|
||||
_: return Result.Unit
|
||||
|
||||
func skip(parser: Callable) -> Result:
|
||||
return parser.call().chain(func(_v: Variant) -> Result: return Result.ok(''))
|
||||
|
||||
func any_char(chars: String) -> Result:
|
||||
for ch in chars:
|
||||
if self.char(ch).is_ok(): return Result.ok(ch)
|
||||
return Result.err(Error.new('expected any of "%s"' % chars))
|
||||
|
||||
func optional(parser: Callable, default_value: Variant = '') -> Result.Ok:
|
||||
match parser.call():
|
||||
var x when x.is_ok(): return x
|
||||
_: return Result.ok(default_value)
|
||||
|
||||
func ws() -> Result:
|
||||
return any_char(' \t')
|
||||
|
||||
func skip_ws() -> Result:
|
||||
return skip(many.bind(ws))
|
||||
|
||||
func parse_sign() -> Result:
|
||||
return any_char('+-')
|
||||
|
||||
func parse_digit() -> Result:
|
||||
return any_char('1234567890')
|
||||
|
||||
func parse_digits() -> Result:
|
||||
return many(parse_digit).map("".join)
|
||||
|
||||
func parse_integer() -> Result:
|
||||
if self.char('0').is_ok(): return Result.ok(0)
|
||||
var sign = optional(parse_sign)
|
||||
var digits = parse_digits()
|
||||
return _collect([sign, digits])
|
||||
|
||||
func parse_exponent() -> Result:
|
||||
var e = any_char('eE')
|
||||
var sign = optional(parse_sign)
|
||||
var digits = parse_digits()
|
||||
return _collect([e, sign, digits])
|
||||
|
||||
func parse_fractional() -> Result:
|
||||
var point = self.char('.')
|
||||
var digits = parse_digits()
|
||||
var exp = optional(parse_exponent)
|
||||
return _collect([point, digits, exp])
|
||||
|
||||
func parse_float() -> Result:
|
||||
var integer = parse_integer()
|
||||
var fractional = optional(parse_fractional)
|
||||
return _collect([integer, fractional])
|
||||
|
||||
func parse_string() -> Result:
|
||||
var quote = char.bind('"')
|
||||
var slash = self.char.bind('\\')
|
||||
var unescaped_quote = all_of.bind([negate.bind(slash), quote])
|
||||
|
||||
var open = skip(quote)
|
||||
var str_value = until(unescaped_quote).map(_join)
|
||||
return _collect([open, str_value])
|
||||
|
||||
class SeqAccess extends Serde.SeqAccess:
|
||||
var de: Json.Deserializer
|
||||
var first: bool = true
|
||||
var parser: Parser:
|
||||
get: return de._parser
|
||||
|
||||
func _init(de: Json.Deserializer) -> void:
|
||||
self.de = de
|
||||
|
||||
func next_element() -> Result:
|
||||
parser.skip_ws()
|
||||
|
||||
if parser.char(']').is_ok():
|
||||
return Result.ok(Option.Unit)
|
||||
|
||||
if not first and parser.char(',').is_err():
|
||||
return Result.err(Error.new('expected comma'))
|
||||
|
||||
first = false
|
||||
|
||||
parser.skip_ws()
|
||||
return de.deserialize_any(Serde.GenericVisitor.new()).map(Option.some)
|
||||
|
||||
class MapAccess extends Serde.MapAccess:
|
||||
var de: Json.Deserializer
|
||||
var first: bool = true
|
||||
|
||||
var iter: PeekableIter:
|
||||
get: return de._iter
|
||||
|
||||
var parser: Parser:
|
||||
get: return de._parser
|
||||
|
||||
func _init(de: Json.Deserializer) -> void:
|
||||
self.de = de
|
||||
|
||||
func next_key() -> Result:
|
||||
parser.skip_ws()
|
||||
|
||||
if iter.peek().filter(Parser.eq.bind('}')).is_some():
|
||||
return Result.ok(Option.Unit)
|
||||
|
||||
if not first:
|
||||
var comma = parser.char(',')
|
||||
if comma.is_err(): return comma
|
||||
|
||||
first = false
|
||||
parser.skip_ws()
|
||||
var key = de.deserialize_any(Serde.GenericVisitor.new()).map(Option.some)
|
||||
return key
|
||||
|
||||
func next_value() -> Result:
|
||||
parser.skip_ws()
|
||||
|
||||
var colon = parser.char(':')
|
||||
if colon.is_err(): return colon
|
||||
|
||||
parser.skip_ws()
|
||||
return de.deserialize_any(Serde.GenericVisitor.new()).map(Option.some)
|
||||
|
||||
class Deserializer extends Serde.Deserializer:
|
||||
var _input: String
|
||||
var _iter: PeekableIter
|
||||
var _parser: Parser
|
||||
|
||||
func _init(value: String) -> void:
|
||||
_input = value
|
||||
_iter = ListIterator.new(_input, RangeIterator.new(0, len(_input))).into_peekable()
|
||||
_parser = Parser.new(self)
|
||||
|
||||
static func from_string(value: String) -> Json.Deserializer:
|
||||
return Json.Deserializer.new(value)
|
||||
|
||||
func deserialize_any(visitor: Visitor):
|
||||
var next = _iter.peek()
|
||||
if next.is_none(): return Result.err(Error.new("eof"))
|
||||
match next.unwrap():
|
||||
'n': return deserialize_nil(visitor)
|
||||
't', 'f': return deserialize_bool(visitor)
|
||||
'"': return deserialize_string(visitor)
|
||||
'+', '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
|
||||
return deserialize_float(visitor)
|
||||
'[': return deserialize_seq(visitor)
|
||||
'{': return deserialize_dict(visitor)
|
||||
var x: return Result.err(Error.new('unexpected character "%s" at position %s' % [x, 0]))
|
||||
|
||||
func deserialize_nil(visitor: Visitor):
|
||||
_parser.skip_ws()
|
||||
match _parser.str('null'):
|
||||
var x when x.is_ok(): return Result.ok(visitor.visit_nil())
|
||||
var x: return x
|
||||
|
||||
func deserialize_bool(visitor: Visitor):
|
||||
_parser.skip_ws()
|
||||
return _parser.any_of([_parser.str.bind('true'), _parser.str.bind('false')]).map(visitor.visit_bool)
|
||||
|
||||
func deserialize_int(visitor: Visitor):
|
||||
_parser.skip_ws()
|
||||
return _parser.parse_integer().chain(Convert.FromString.to_int).map(visitor.visit_int)
|
||||
|
||||
func deserialize_float(visitor: Visitor):
|
||||
_parser.skip_ws()
|
||||
return _parser.parse_float().chain(Convert.FromString.to_float).map(visitor.visit_float)
|
||||
|
||||
func deserialize_string(visitor: Visitor):
|
||||
_parser.skip_ws()
|
||||
return _parser.parse_string().map(visitor.visit_string)
|
||||
|
||||
func _deserialize_complex(open: Callable, close: Callable, on_success: Callable):
|
||||
_parser.skip_ws()
|
||||
var open_result = open.call()
|
||||
if open_result.is_err(): return open_result
|
||||
|
||||
var value = on_success.call()
|
||||
|
||||
var close_result = close.call()
|
||||
if close_result.is_err(): return close_result
|
||||
return Result.ok(value)
|
||||
|
||||
func deserialize_seq(visitor: Visitor):
|
||||
var open = _parser.char.bind('[')
|
||||
var close = _parser.char.bind(']')
|
||||
var visit = func() -> Variant:
|
||||
return visitor.visit_seq(Json.SeqAccess.new(self))
|
||||
|
||||
return _deserialize_complex(open, close, visit)
|
||||
|
||||
func deserialize_dict(visitor: Visitor):
|
||||
var open = _parser.char.bind('{')
|
||||
var close = _parser.char.bind('}')
|
||||
var visit = func() -> Variant:
|
||||
return visitor.visit_dict(Json.MapAccess.new(self))
|
||||
|
||||
return _deserialize_complex(open, close, visit)
|
||||
|
||||
func deserialize_object(_name: StringName, _fields: Array[StringName], visitor: Visitor):
|
||||
return deserialize_dict(visitor)
|
|
@ -1,228 +0,0 @@
|
|||
class_name Serde
|
||||
|
||||
const SerializeMethod = &"serialize"
|
||||
const DeserializeMethod = &"deserialize"
|
||||
|
||||
enum SerdeType {
|
||||
Nil,
|
||||
Bool,
|
||||
Int,
|
||||
Float,
|
||||
String,
|
||||
Seq,
|
||||
Dict,
|
||||
Object
|
||||
}
|
||||
|
||||
static func to_serde_type(godot_type: int) -> Result:
|
||||
match godot_type:
|
||||
TYPE_NIL: return Result.ok(Result.ok(SerdeType.Nil))
|
||||
TYPE_BOOL: return Result.ok(Result.ok(SerdeType.Bool))
|
||||
TYPE_INT: return Result.ok(Result.ok(SerdeType.Int))
|
||||
TYPE_FLOAT: return Result.ok(SerdeType.Float)
|
||||
TYPE_STRING: return Result.ok(SerdeType.String)
|
||||
TYPE_STRING_NAME: return Result.ok(SerdeType.String)
|
||||
TYPE_NODE_PATH: return Result.ok(SerdeType.String)
|
||||
|
||||
TYPE_VECTOR2: return Result.ok(SerdeType.Seq)
|
||||
TYPE_VECTOR2I: return Result.ok(SerdeType.Seq)
|
||||
TYPE_VECTOR3: return Result.ok(SerdeType.Seq)
|
||||
TYPE_VECTOR3I: return Result.ok(SerdeType.Seq)
|
||||
TYPE_VECTOR4I: return Result.ok(SerdeType.Seq)
|
||||
TYPE_VECTOR4I: return Result.ok(SerdeType.Seq)
|
||||
|
||||
TYPE_RECT2: return Result.ok(SerdeType.Object)
|
||||
TYPE_RECT2I: return Result.ok(SerdeType.Object)
|
||||
TYPE_AABB: return Result.ok(SerdeType.Object)
|
||||
|
||||
TYPE_TRANSFORM2D: return Result.ok(SerdeType.Object)
|
||||
TYPE_BASIS: return Result.ok(SerdeType.Object)
|
||||
TYPE_TRANSFORM3D: return Result.ok(SerdeType.Object)
|
||||
TYPE_PLANE: return Result.ok(SerdeType.Object)
|
||||
TYPE_PROJECTION: return Result.ok(SerdeType.Object)
|
||||
TYPE_QUATERNION: return Result.ok(SerdeType.Seq)
|
||||
|
||||
TYPE_COLOR: return Result.ok(SerdeType.Seq)
|
||||
TYPE_RID: return Result.ok(SerdeType.Int)
|
||||
TYPE_OBJECT: return Result.ok(SerdeType.Object)
|
||||
TYPE_DICTIONARY: return Result.ok(SerdeType.Dict)
|
||||
|
||||
TYPE_ARRAY: return Result.ok(SerdeType.Seq)
|
||||
TYPE_PACKED_BYTE_ARRAY: return Result.ok(SerdeType.Seq)
|
||||
TYPE_PACKED_COLOR_ARRAY: return Result.ok(SerdeType.Seq)
|
||||
TYPE_PACKED_INT32_ARRAY: return Result.ok(SerdeType.Seq)
|
||||
TYPE_PACKED_INT64_ARRAY: return Result.ok(SerdeType.Seq)
|
||||
TYPE_PACKED_STRING_ARRAY: return Result.ok(SerdeType.Seq)
|
||||
TYPE_PACKED_FLOAT32_ARRAY: return Result.ok(SerdeType.Seq)
|
||||
TYPE_PACKED_FLOAT64_ARRAY: return Result.ok(SerdeType.Seq)
|
||||
TYPE_PACKED_VECTOR2_ARRAY: return Result.ok(SerdeType.Seq)
|
||||
TYPE_PACKED_VECTOR3_ARRAY: return Result.ok(SerdeType.Seq)
|
||||
TYPE_PACKED_VECTOR4_ARRAY: return Result.ok(SerdeType.Seq)
|
||||
|
||||
TYPE_SIGNAL: return Result.err("Refusing to (de)serialize Signal")
|
||||
TYPE_CALLABLE: return Result.err("Refusing to (de)serialize Callable")
|
||||
|
||||
_: return Result.err(Error.InvalidArgument.new(godot_type))
|
||||
|
||||
static func _serialize_seq(serializer: Serializer, value: Variant) -> Variant:
|
||||
var ser = serializer.serialize_seq(len(value))
|
||||
for element in value:
|
||||
ser.serialize_element(element)
|
||||
return ser.end()
|
||||
|
||||
static func _serialize_dict(serializer: Serializer, value: Dictionary) -> Variant:
|
||||
var ser: SerializeMap = serializer.serialize_dict(value.size())
|
||||
for key in value.keys():
|
||||
ser.serialize_entry(key, value[key])
|
||||
return ser.end()
|
||||
|
||||
static func get_object_name(value: Variant) -> Option:
|
||||
var name: Option = Option.none
|
||||
|
||||
if value.has_method("get_script"):
|
||||
var script_name = value.get_script().get_global_name()
|
||||
if script_name != null: name = Option.some(script_name)
|
||||
|
||||
if name == null and value.has_method("get_class"):
|
||||
var native_name = value.get_class()
|
||||
if native_name != null: name = Option.some(native_name)
|
||||
|
||||
return name
|
||||
|
||||
static func _serialize_object(serializer: Serializer, value: Variant) -> Variant:
|
||||
var object_name = get_object_name(value).unwrap()
|
||||
var ser = serializer.serialize_object(object_name, len(value.get_property_list()))
|
||||
ser.serialize(value)
|
||||
return ser.end()
|
||||
|
||||
static func default_serialize(serializer: Serializer, value: Variant, allow_eval: bool = false) -> Variant:
|
||||
match to_serde_type(typeof(value)).unwrap():
|
||||
SerdeType.Nil: return serializer.serialize_nil()
|
||||
SerdeType.Bool: return serializer.serialize_bool(value)
|
||||
SerdeType.Int: return serializer.serialize_int(value)
|
||||
SerdeType.Float: return serializer.serialize_float(value)
|
||||
SerdeType.String: return serializer.serialize_string(value)
|
||||
SerdeType.Seq: return _serialize_seq(serializer, value)
|
||||
SerdeType.Dict: return _serialize_dict(serializer, value)
|
||||
SerdeType.Object: return _serialize_object(serializer, value)
|
||||
return Result.err("could not deserialize type %s" % typeof(value))
|
||||
|
||||
|
||||
static func serialize(serializer: Serializer, value: Variant, allow_eval: bool = false) -> Variant:
|
||||
if typeof(value) == TYPE_OBJECT and value.has_method(SerializeMethod):
|
||||
return value.call(serializer, value, allow_eval)
|
||||
else:
|
||||
return default_serialize(serializer, value, allow_eval)
|
||||
|
||||
class SerializeSeq extends RefCounted:
|
||||
func serialize_element(element: Variant): Error.NotImplemented.raise('serialize_element')
|
||||
func end(): Error.NotImplemented.raise('end')
|
||||
|
||||
class SerializeMap extends RefCounted:
|
||||
func serialize_key(key: Variant): Error.NotImplemented.raise('serialize_key')
|
||||
func serialize_value(value: Variant): Error.NotImplemented.raise('serialize_value')
|
||||
func end(): Error.NotImplemented.raise('end')
|
||||
func serialize_entry(key: Variant, value: Variant):
|
||||
return Result.collect_ok([serialize_key(key), serialize_value(value)])
|
||||
|
||||
class SerializeObject extends RefCounted:
|
||||
func serialize_field(name: StringName, value: Variant): Error.NotImplemented.raise('serialize_field')
|
||||
func serialize(object: Variant): Error.NotImplemented.raise('serialize')
|
||||
func end(): Error.NotImplemented.raise('end')
|
||||
|
||||
class Serializer extends RefCounted:
|
||||
func serialize_nil(): Error.NotImplemented.raise('serialize_nil')
|
||||
func serialize_bool(_value: bool): Error.NotImplemented.raise('serialize_bool')
|
||||
func serialize_int(_value: int): Error.NotImplemented.raise('serialize_int')
|
||||
func serialize_float(_value: float): Error.NotImplemented.raise('serialize_float')
|
||||
func serialize_string(_value: String): Error.NotImplemented.raise('serialize_string')
|
||||
func serialize_seq(_len: int): Error.NotImplemented.raise('serialize_string')
|
||||
func serialize_dict(_len: int): Error.NotImplemented.raise('serialize_string')
|
||||
func serialize_object(_name: StringName, _len: int): Error.NotImplemented.raise('serialize_string')
|
||||
|
||||
class Visitor extends RefCounted:
|
||||
func visit_nil(): Error.NotImplemented.raise('visit_nil')
|
||||
func visit_bool(_value: bool): Error.NotImplemented.raise('visit_bool')
|
||||
func visit_int(_value: int): Error.NotImplemented.raise('visit_int')
|
||||
func visit_float(_value: float): Error.NotImplemented.raise('visit_float')
|
||||
func visit_string(_value: String): Error.NotImplemented.raise('visit_string')
|
||||
func visit_seq(_access: SeqAccess): Error.NotImplemented.raise('visit_seq')
|
||||
func visit_dict(_access: MapAccess): Error.NotImplemented.raise('visit_dict')
|
||||
|
||||
class GenericVisitor extends Visitor:
|
||||
func visit_nil(): return null
|
||||
func visit_bool(value: bool):
|
||||
print('bool', value)
|
||||
return value
|
||||
func visit_int(value: int):
|
||||
print('int', value)
|
||||
return value
|
||||
func visit_float(value: float):
|
||||
print('float', value)
|
||||
return value
|
||||
func visit_string(value: String):
|
||||
print('str', value)
|
||||
return value
|
||||
func visit_seq(access: SeqAccess):
|
||||
print('seq', access)
|
||||
return access.collect()
|
||||
func visit_dict(access: MapAccess):
|
||||
var a = access.collect()
|
||||
print('dict', a)
|
||||
return a
|
||||
|
||||
class AccessIterable extends Iterator:
|
||||
var _next: Callable
|
||||
|
||||
func _init(next: Callable) -> void:
|
||||
_next = next
|
||||
|
||||
func next() -> Option:
|
||||
return _next.call().to_option().flatten()
|
||||
|
||||
class SeqAccess extends RefCounted:
|
||||
func next_element(): Error.NotImplemented.raise('next_element')
|
||||
|
||||
func iterate() -> AccessIterable:
|
||||
return AccessIterable.new(next_element)
|
||||
|
||||
func collect() -> Array:
|
||||
var list = []
|
||||
|
||||
for item in iterate():
|
||||
list.append(item)
|
||||
|
||||
return list
|
||||
|
||||
class MapAccess extends RefCounted:
|
||||
func next_key(): Error.NotImplemented.raise('next_key')
|
||||
func next_value(): Error.NotImplemented.raise('next_value')
|
||||
|
||||
func next_entry() -> Result:
|
||||
var key = next_key()
|
||||
var val = next_value()
|
||||
return Result.collect_ok([key, val])
|
||||
|
||||
func iterate() -> AccessIterable:
|
||||
return AccessIterable.new(next_entry)
|
||||
|
||||
func collect() -> Dictionary:
|
||||
var dict: Dictionary = {}
|
||||
|
||||
print('collecting map')
|
||||
for entry in iterate():
|
||||
print('entry', entry)
|
||||
dict.set(entry[0], entry[1])
|
||||
|
||||
return dict
|
||||
|
||||
class Deserializer extends RefCounted:
|
||||
func deserialize_any(_visitor: Visitor): Error.NotImplemented.raise('deserialize_any')
|
||||
func deserialize_nil(_visitor: Visitor): Error.NotImplemented.raise('deserialize_nil')
|
||||
func deserialize_bool(_visitor: Visitor): Error.NotImplemented.raise('deserialize_bool')
|
||||
func deserialize_int(_visitor: Visitor): Error.NotImplemented.raise('deserialize_int')
|
||||
func deserialize_float(_visitor: Visitor): Error.NotImplemented.raise('deserialize_float')
|
||||
func deserialize_string(_visitor: Visitor): Error.NotImplemented.raise('deserialize_string')
|
||||
func deserialize_seq(_visitor: Visitor): Error.NotImplemented.raise('deserialize_seq')
|
||||
func deserialize_dict(_visitor: Visitor): Error.NotImplemented.raise('deserialize_dict')
|
||||
func deserialize_object(_name: StringName, _fields: Array[StringName], _visitor: Visitor): Error.NotImplemented.raise('deserialize_object')
|
|
@ -24,31 +24,37 @@ persist=""
|
|||
move_up={
|
||||
"deadzone": 0.2,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":87,"key_label":0,"unicode":119,"location":0,"echo":false,"script":null)
|
||||
, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":1,"axis_value":-1.0,"script":null)
|
||||
]
|
||||
}
|
||||
move_down={
|
||||
"deadzone": 0.2,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":83,"key_label":0,"unicode":115,"location":0,"echo":false,"script":null)
|
||||
, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":1,"axis_value":1.0,"script":null)
|
||||
]
|
||||
}
|
||||
move_left={
|
||||
"deadzone": 0.2,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":65,"key_label":0,"unicode":97,"location":0,"echo":false,"script":null)
|
||||
, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":0,"axis_value":-1.0,"script":null)
|
||||
]
|
||||
}
|
||||
move_right={
|
||||
"deadzone": 0.2,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":68,"key_label":0,"unicode":100,"location":0,"echo":false,"script":null)
|
||||
, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":0,"axis_value":1.0,"script":null)
|
||||
]
|
||||
}
|
||||
run={
|
||||
"deadzone": 0.2,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194325,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":0,"pressure":0.0,"pressed":true,"script":null)
|
||||
]
|
||||
}
|
||||
interact={
|
||||
"deadzone": 0.2,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":69,"key_label":0,"unicode":101,"location":0,"echo":false,"script":null)
|
||||
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":1,"pressure":0.0,"pressed":true,"script":null)
|
||||
]
|
||||
}
|
||||
|
||||
|
|
|
@ -3,18 +3,15 @@ class_name Persistence extends Node
|
|||
@export var path = "user://saves/data.sav"
|
||||
@export var group_name = "persist"
|
||||
|
||||
const PersistenceOptionsMethod = "_get_persistence_options"
|
||||
const SaveMethod = "on_save"
|
||||
const OnBeforeLoadMethod = "on_before_load"
|
||||
const LoadMethod = "on_load"
|
||||
|
||||
# TODO: binary (de)serialization
|
||||
|
||||
static func get_instance_data(node: Node):
|
||||
return {
|
||||
path = node.get_path(),
|
||||
scene = node.scene_file_path,
|
||||
data = JSON.from_native(node.call(SaveMethod))
|
||||
data = node.call(SaveMethod)
|
||||
}
|
||||
|
||||
func save() -> Result:
|
||||
|
@ -32,7 +29,11 @@ func save() -> Result:
|
|||
continue
|
||||
|
||||
var data = get_instance_data(node)
|
||||
file.store_line(Json.stringify(data))
|
||||
var serial = BinarySerializer.serialize_var(data)
|
||||
var bytes = var_to_bytes(serial)
|
||||
var length = bytes.size()
|
||||
file.store_32(length)
|
||||
file.store_buffer(var_to_bytes(serial))
|
||||
|
||||
return Result.Unit
|
||||
|
||||
|
@ -52,7 +53,7 @@ func _get_or_add(instance_data: Dictionary) -> Variant:
|
|||
|
||||
return node
|
||||
|
||||
func load(fail_on_err: bool = false) -> Result:
|
||||
func load() -> Result:
|
||||
if not FileAccess.file_exists(path):
|
||||
return Result.err('ENOENT: "%s" does not exist' % path)
|
||||
|
||||
|
@ -62,17 +63,10 @@ func load(fail_on_err: bool = false) -> Result:
|
|||
|
||||
var length = file.get_length()
|
||||
while file.get_position() < length:
|
||||
var line = file.get_line()
|
||||
var result = Json.parse_string(line)
|
||||
|
||||
if result.is_err():
|
||||
if fail_on_err: return result
|
||||
else:
|
||||
push_error('could not read save entry. error: "%s"' % result.unwrap_err())
|
||||
continue
|
||||
|
||||
var save_data = result.unwrap()
|
||||
|
||||
var byte_len = file.get_32()
|
||||
var bytes = file.get_buffer(byte_len)
|
||||
var result = bytes_to_var(bytes)
|
||||
var save_data = BinarySerializer.deserialize_var(result)
|
||||
var instance = await _get_or_add(save_data)
|
||||
|
||||
if not instance.has_method(LoadMethod):
|
||||
|
@ -80,7 +74,6 @@ func load(fail_on_err: bool = false) -> Result:
|
|||
instance.queue_free()
|
||||
continue
|
||||
|
||||
var instance_data = JSON.to_native(save_data.data)
|
||||
instance.call(LoadMethod, instance_data)
|
||||
instance.call(LoadMethod, save_data.data)
|
||||
|
||||
return Result.Unit
|
||||
|
|
Loading…
Add table
Reference in a new issue