189 lines
8.1 KiB
GDScript
189 lines
8.1 KiB
GDScript
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
|