initial shit
This commit is contained in:
188
addons/savekit/binary_deserializer.gd
Normal file
188
addons/savekit/binary_deserializer.gd
Normal file
@@ -0,0 +1,188 @@
|
||||
extends "deserializer.gd"
|
||||
## Deserializes save data from binary format.
|
||||
|
||||
const BinarySerializer := preload("binary_serializer.gd")
|
||||
|
||||
var _node_deserialization_stack: Array[NodePath] = []
|
||||
var _saved_nodes: Dictionary[NodePath, Dictionary]
|
||||
var _saved_resources_by_id: Dictionary[int, Dictionary]
|
||||
var _loaded_resources_by_id: Dictionary[int, SaveKitResource]
|
||||
|
||||
func prepare_load_from_memory(data: PackedByteArray) -> bool:
|
||||
if data.size() < BinarySerializer._FILE_HEADER_SIZE:
|
||||
push_error("Save data is too small to contain required header information")
|
||||
return false
|
||||
|
||||
var version := data.decode_u32(BinarySerializer._SERIALIZATION_VERSION_U32_OFFSET)
|
||||
if version != BinarySerializer._SERIALIZATION_VERSION:
|
||||
push_error("Unsupported save data version: ", version)
|
||||
return false
|
||||
|
||||
var saved_nodes_length := data.decode_var_size(BinarySerializer._FILE_HEADER_SIZE)
|
||||
_saved_nodes.assign(data.decode_var(BinarySerializer._FILE_HEADER_SIZE) as Dictionary)
|
||||
_saved_resources_by_id.assign(data.decode_var(BinarySerializer._FILE_HEADER_SIZE + saved_nodes_length) as Dictionary)
|
||||
|
||||
_node_deserialization_stack.assign(_saved_nodes.keys())
|
||||
sort_node_paths_in_load_order(_node_deserialization_stack)
|
||||
return true
|
||||
|
||||
func decode_var(value: Variant, expected_type: Variant.Type, expected_class_name: StringName = &"") -> Variant:
|
||||
match expected_type:
|
||||
TYPE_CALLABLE:
|
||||
push_error("Cannot deserialize callable value ", value)
|
||||
return null
|
||||
|
||||
TYPE_OBJECT:
|
||||
var buffer := value as PackedByteArray
|
||||
if not buffer:
|
||||
push_warning("Expected a PackedByteArray when deserializing an object, got: ", value)
|
||||
return null
|
||||
|
||||
var type_tag := buffer.get(0)
|
||||
match type_tag:
|
||||
BinarySerializer._ENCODED_RESOURCE_REFERENCE_TAG:
|
||||
return _decode_resource_reference(buffer, expected_class_name)
|
||||
|
||||
BinarySerializer._ENCODED_NODE_REFERENCE_TAG:
|
||||
var path_length := buffer.decode_u32(BinarySerializer._ENCODED_NODE_REFERENCE_PATH_LENGTH_U32_OFFSET)
|
||||
if path_length <= 0:
|
||||
push_warning("Invalid path length ", path_length, " found when deserializing a node reference")
|
||||
return null
|
||||
|
||||
var path_buffer := buffer.slice(BinarySerializer._ENCODED_NODE_REFERENCE_DATA_OFFSET, BinarySerializer._ENCODED_NODE_REFERENCE_DATA_OFFSET + path_length)
|
||||
var node_path := NodePath(path_buffer.get_string_from_utf8())
|
||||
return _decode_node_reference(node_path)
|
||||
|
||||
BinarySerializer._SAVED_RESOURCE_REFERENCE_TAG:
|
||||
return _load_resource(buffer)
|
||||
|
||||
_:
|
||||
push_warning("Unknown type tag ", type_tag, " found when deserializing an object")
|
||||
return null
|
||||
|
||||
TYPE_ARRAY:
|
||||
var array := value as Array
|
||||
if not array:
|
||||
push_warning("Expected an array when deserializing an array, got: ", value)
|
||||
return null
|
||||
|
||||
return array.map(_decode_var_with_type_info)
|
||||
|
||||
TYPE_DICTIONARY:
|
||||
var dictionary := value as Dictionary
|
||||
if not dictionary:
|
||||
push_warning("Expected a dictionary when deserializing a dictionary, got: ", value)
|
||||
return null
|
||||
|
||||
var decoded_dictionary: Dictionary
|
||||
for key: Variant in dictionary:
|
||||
var decoded_key: Variant = _decode_var_with_type_info(key)
|
||||
var decoded_value: Variant = _decode_var_with_type_info(dictionary[key])
|
||||
decoded_dictionary[decoded_key] = decoded_value
|
||||
|
||||
return decoded_dictionary
|
||||
|
||||
_:
|
||||
return value
|
||||
|
||||
func _decode_var_with_type_info(value: Variant) -> Variant:
|
||||
var buffer := value as PackedByteArray
|
||||
if not buffer:
|
||||
push_warning("Expected a PackedByteArray when decoding a typed value, got: ", value)
|
||||
return null
|
||||
|
||||
var type := buffer.decode_u8(BinarySerializer._ENCODED_TYPED_VALUE_TYPE_U8_OFFSET) as Variant.Type
|
||||
var classname_length := buffer.decode_u16(BinarySerializer._ENCODED_TYPED_VALUE_CLASS_NAME_LENGTH_U16_OFFSET)
|
||||
|
||||
var classname: StringName = ""
|
||||
if classname_length:
|
||||
var classname_buffer := buffer.slice(BinarySerializer._ENCODED_TYPED_VALUE_DATA_OFFSET, BinarySerializer._ENCODED_TYPED_VALUE_DATA_OFFSET + classname_length)
|
||||
classname = StringName(classname_buffer.get_string_from_utf8())
|
||||
|
||||
var encoded_value := buffer.slice(BinarySerializer._ENCODED_TYPED_VALUE_DATA_OFFSET + classname_length)
|
||||
return decode_var(bytes_to_var(encoded_value), type, classname)
|
||||
|
||||
func _decode_node_reference(node_path: NodePath) -> Node:
|
||||
# To ensure we can convert this node path into a valid node reference, we need to effectively "preload" the target node and all of its ancestors.
|
||||
# This process is similar to load_node(), but circumventing the normal order and without actually loading data into the nodes yet.
|
||||
if node_path.get_name_count() > 1:
|
||||
var parent_node := _decode_node_reference(node_path.slice(0, -1))
|
||||
if not parent_node:
|
||||
return null
|
||||
|
||||
var save_dict: Dictionary = _saved_nodes.get(node_path, {})
|
||||
var scene_file_path: String = save_dict.get(BinarySerializer._NODE_SCENE_FILE_PATH_KEY, "")
|
||||
return find_or_instantiate_node(node_path, scene_file_path, false)
|
||||
|
||||
## Decodes a reference to a resource, loading it by UID or path as appropriate.
|
||||
##
|
||||
## Note: [param expected_class_name] should refer to a class that exists within [ClassDB] (i.e., built-in or GDExtension classes). It should [i]not[/i] contain the name of a script-defined [code]class_name[/code].
|
||||
func _decode_resource_reference(buffer: PackedByteArray, expected_class_name: StringName) -> Resource:
|
||||
var path_length := buffer.decode_u32(BinarySerializer._ENCODED_RESOURCE_REFERENCE_PATH_LENGTH_U32_OFFSET)
|
||||
var uid_length := buffer.decode_u32(BinarySerializer._ENCODED_RESOURCE_REFERENCE_UID_LENGTH_U32_OFFSET)
|
||||
|
||||
var path_buffer := buffer.slice(BinarySerializer._ENCODED_RESOURCE_REFERENCE_DATA_OFFSET, BinarySerializer._ENCODED_RESOURCE_REFERENCE_DATA_OFFSET + path_length)
|
||||
|
||||
var uid_buffer: PackedByteArray
|
||||
if uid_length:
|
||||
uid_buffer = buffer.slice(BinarySerializer._ENCODED_RESOURCE_REFERENCE_DATA_OFFSET + path_length, BinarySerializer._ENCODED_RESOURCE_REFERENCE_DATA_OFFSET + path_length + uid_length)
|
||||
|
||||
var resource_path := path_buffer.get_string_from_utf8()
|
||||
if uid_buffer:
|
||||
var id := ResourceUID.text_to_id(uid_buffer.get_string_from_utf8())
|
||||
if ResourceUID.has_id(id):
|
||||
resource_path = ResourceUID.get_id_path(id)
|
||||
|
||||
var allowed_extensions := ResourceLoader.get_recognized_extensions_for_type(expected_class_name if expected_class_name else &"Resource")
|
||||
return ResourceUtils.safe_load_resource(resource_path, allowed_extensions)
|
||||
|
||||
## Returns how many nodes remain to be loaded from the save data. This can be used to determine loading progress.
|
||||
func get_remaining_node_count() -> int:
|
||||
return _node_deserialization_stack.size()
|
||||
|
||||
func is_finished() -> bool:
|
||||
return not _node_deserialization_stack
|
||||
|
||||
func load_node() -> Node:
|
||||
var node_path: NodePath = _node_deserialization_stack.pop_back()
|
||||
if not node_path:
|
||||
return null
|
||||
|
||||
var save_dict: Dictionary = _saved_nodes[node_path]
|
||||
_saved_nodes.erase(node_path)
|
||||
|
||||
var scene_file_path: String = save_dict.get(BinarySerializer._NODE_SCENE_FILE_PATH_KEY, "")
|
||||
save_dict.erase(BinarySerializer._NODE_SCENE_FILE_PATH_KEY)
|
||||
|
||||
var node := find_or_instantiate_node(node_path, scene_file_path, true)
|
||||
if node:
|
||||
load_node_from_dict(node, save_dict)
|
||||
|
||||
return node
|
||||
|
||||
## Loads a [SaveKitResource] from the save data. If the resource has already been loaded, the existing instance will be returned.
|
||||
func _load_resource(buffer: PackedByteArray) -> SaveKitResource:
|
||||
var resource_id := buffer.decode_u64(BinarySerializer._SAVED_RESOURCE_REFERENCE_ID_U64_OFFSET)
|
||||
|
||||
var resource: SaveKitResource = _loaded_resources_by_id.get(resource_id)
|
||||
if not resource:
|
||||
var save_dict: Dictionary = _saved_resources_by_id.get(resource_id, {})
|
||||
if not save_dict:
|
||||
push_error("No saved resource found with ID ", resource_id)
|
||||
return null
|
||||
|
||||
_saved_resources_by_id.erase(resource_id)
|
||||
|
||||
var script: Script = _decode_resource_reference(save_dict.get(BinarySerializer._SAVED_RESOURCE_SCRIPT_KEY) as PackedByteArray, "Script")
|
||||
if not script:
|
||||
push_error("Failed to decode script for resource with ID ", resource_id, ", cannot load resource")
|
||||
return null
|
||||
|
||||
save_dict.erase(BinarySerializer._SAVED_RESOURCE_SCRIPT_KEY)
|
||||
|
||||
@warning_ignore("unsafe_method_access")
|
||||
resource = script.new()
|
||||
resource.load_from_dict(self , save_dict)
|
||||
_loaded_resources_by_id[resource_id] = resource
|
||||
|
||||
return resource
|
||||
Reference in New Issue
Block a user