feat: integrate regional caravan persistence
This commit is contained in:
@@ -15,16 +15,36 @@ func _init(directory: String = DEFAULT_DIRECTORY) -> void:
|
||||
|
||||
func save(manager: Node, slot_name: String = DEFAULT_SLOT) -> bool:
|
||||
last_error = ""
|
||||
if manager == null or not manager.has_method("serialize_state"):
|
||||
return _fail("Save source cannot serialize simulation state")
|
||||
if (
|
||||
manager == null
|
||||
or not (
|
||||
manager.has_method("serialize_save_manifest") or manager.has_method("serialize_state")
|
||||
)
|
||||
):
|
||||
return _fail("Save source cannot serialize authoritative state")
|
||||
if not _is_valid_slot_name(slot_name):
|
||||
return _fail("Invalid save slot name")
|
||||
|
||||
var json_text: String = manager.serialize_state()
|
||||
var use_manifest := manager.has_method("serialize_save_manifest")
|
||||
if use_manifest and manager.has_method("uses_combined_save_manifest"):
|
||||
use_manifest = bool(manager.call("uses_combined_save_manifest"))
|
||||
if (
|
||||
not use_manifest
|
||||
and manager.has_method("can_serialize_local_state_only")
|
||||
and not bool(manager.call("can_serialize_local_state_only"))
|
||||
):
|
||||
return _fail("Save source has regional authority outside its active manifest scope")
|
||||
var json_text: String
|
||||
if use_manifest:
|
||||
json_text = String(manager.call("serialize_save_manifest"))
|
||||
elif manager.has_method("serialize_state"):
|
||||
json_text = String(manager.call("serialize_state"))
|
||||
else:
|
||||
return _fail("Save source cannot serialize its active authority scope")
|
||||
if json_text.to_utf8_buffer().size() > MAX_SAVE_BYTES:
|
||||
return _fail("Save exceeds the supported size limit")
|
||||
if SimulationStateRecord.from_json(json_text) == null:
|
||||
return _fail("Simulation produced an invalid save record")
|
||||
if not _is_valid_save_json(json_text):
|
||||
return _fail("Simulation produced an invalid save payload")
|
||||
if not _ensure_directory():
|
||||
return false
|
||||
|
||||
@@ -39,7 +59,7 @@ func save(manager: Node, slot_name: String = DEFAULT_SLOT) -> bool:
|
||||
file.store_string(json_text)
|
||||
file.flush()
|
||||
file.close()
|
||||
if _read_record(temporary_path) == null:
|
||||
if _read_payload(temporary_path).is_empty():
|
||||
_remove_if_present(temporary_path)
|
||||
return _fail("Temporary save failed validation")
|
||||
|
||||
@@ -60,19 +80,29 @@ func save(manager: Node, slot_name: String = DEFAULT_SLOT) -> bool:
|
||||
|
||||
func load_into(manager: Node, slot_name: String = DEFAULT_SLOT) -> bool:
|
||||
last_error = ""
|
||||
if manager == null or not manager.has_method("restore_state"):
|
||||
return _fail("Save target cannot restore simulation state")
|
||||
if manager == null:
|
||||
return _fail("Save target is missing")
|
||||
if not _is_valid_slot_name(slot_name):
|
||||
return _fail("Invalid save slot name")
|
||||
|
||||
var final_path := get_slot_path(slot_name)
|
||||
var record := _read_record(final_path)
|
||||
if record == null:
|
||||
record = _read_record(final_path + ".bak")
|
||||
if record == null:
|
||||
var payload := _read_payload(final_path)
|
||||
if payload.is_empty():
|
||||
payload = _read_payload(final_path + ".bak")
|
||||
if payload.is_empty():
|
||||
return _fail("Save slot is missing, invalid, or unsupported")
|
||||
if not bool(manager.restore_state(record)):
|
||||
return _fail("Simulation refused the validated save record")
|
||||
if payload.has("manifest"):
|
||||
if not manager.has_method("restore_save_manifest"):
|
||||
return _fail("Save target cannot restore a combined save manifest")
|
||||
if not bool(manager.call("restore_save_manifest", payload["manifest"])):
|
||||
return _fail("Simulation refused the validated save manifest")
|
||||
elif payload.has("local_state"):
|
||||
if not manager.has_method("restore_state"):
|
||||
return _fail("Save target cannot restore legacy local state")
|
||||
if not bool(manager.call("restore_state", payload["local_state"])):
|
||||
return _fail("Simulation refused the validated legacy save record")
|
||||
else:
|
||||
return _fail("Save slot payload kind is unsupported")
|
||||
return true
|
||||
|
||||
|
||||
@@ -80,7 +110,10 @@ func has_slot(slot_name: String = DEFAULT_SLOT) -> bool:
|
||||
if not _is_valid_slot_name(slot_name):
|
||||
return false
|
||||
var final_path := get_slot_path(slot_name)
|
||||
return _read_record(final_path) != null or _read_record(final_path + ".bak") != null
|
||||
return (
|
||||
not _read_payload(final_path).is_empty()
|
||||
or not _read_payload(final_path + ".bak").is_empty()
|
||||
)
|
||||
|
||||
|
||||
func delete_slot(slot_name: String = DEFAULT_SLOT) -> bool:
|
||||
@@ -97,18 +130,29 @@ func get_slot_path(slot_name: String = DEFAULT_SLOT) -> String:
|
||||
return save_directory.path_join(slot_name + ".json")
|
||||
|
||||
|
||||
func _read_record(path: String) -> SimulationStateRecord:
|
||||
func _read_payload(path: String) -> Dictionary:
|
||||
if not FileAccess.file_exists(path):
|
||||
return null
|
||||
return {}
|
||||
var file := FileAccess.open(path, FileAccess.READ)
|
||||
if file == null:
|
||||
return null
|
||||
return {}
|
||||
if file.get_length() > MAX_SAVE_BYTES:
|
||||
file.close()
|
||||
return null
|
||||
return {}
|
||||
var json_text := file.get_as_text()
|
||||
file.close()
|
||||
return SimulationStateRecord.from_json(json_text)
|
||||
var manifest := SimulationSaveManifest.from_json(json_text)
|
||||
if manifest != null:
|
||||
return {"manifest": manifest}
|
||||
var local_state := SimulationStateRecord.from_json(json_text)
|
||||
return {"local_state": local_state} if local_state != null else {}
|
||||
|
||||
|
||||
func _is_valid_save_json(json_text: String) -> bool:
|
||||
return (
|
||||
SimulationSaveManifest.from_json(json_text) != null
|
||||
or SimulationStateRecord.from_json(json_text) != null
|
||||
)
|
||||
|
||||
|
||||
func _ensure_directory() -> bool:
|
||||
|
||||
Reference in New Issue
Block a user