class_name Persistence extends Node @export var path = "user://saves/" @export var group_name = "persist" const SaveMethod = "on_save" const OnBeforeLoadMethod = "on_before_load" const LoadMethod = "on_load" static func get_instance_data(node: Node): return { path = node.get_path(), scene = node.scene_file_path, data = node.call(SaveMethod) } func get_file(filename: String) -> Result: if not filename.is_valid_filename(): return Result.err('invalid filename "%s"' % filename) else: var file_path = path.path_join(filename) return Result.ok(file_path) func save(filename: String) -> Result: DirAccess.make_dir_recursive_absolute(path.get_base_dir()) var file_result = get_file(filename) if file_result.is_err(): push_error(file_result.unwrap_err()) return file_result var file_path = file_result.unwrap() var file = FileAccess.open(file_path, FileAccess.WRITE) var nodes = get_tree().get_nodes_in_group(group_name) for node in nodes: if node.scene_file_path.is_empty(): push_warning('"%s" is not instanced, skipping' % node.name) continue if not node.has_method("on_save"): push_warning('"%s" does not have a "%s" method, skipping' % [node.name, SaveMethod]) continue var data = get_instance_data(node) 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 func _get_or_add(instance_data: Dictionary) -> Variant: var node_path: NodePath = instance_data['path'] var scene_path = instance_data['scene'] var node = get_node_or_null(node_path) if node.is_queued_for_deletion(): await node.tree_exited node = null if node == null: node = load(scene_path).instantiate() var parent = node_path.slice(0, node_path.get_name_count() - 1) get_node(parent).call_deferred('add_child', node) return node func load(filename: String) -> Result: var file_result = get_file(filename) if file_result.is_err(): push_error(file_result.unwrap_err()) return file_result var file_path = file_result.unwrap() if not FileAccess.file_exists(file_path): var err = 'ENOENT: "%s" does not exist' % path push_error(err) return Result.err(err) get_tree().call_group(group_name, OnBeforeLoadMethod) var file = FileAccess.open(file_path, FileAccess.READ) var length = file.get_length() while file.get_position() < length: 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): push_warning('"%s" does not have a "%s" method' % [instance.name, LoadMethod]) instance.queue_free() continue instance.call(LoadMethod, save_data.data) return Result.Unit