190 lines
8.2 KiB
GDScript
190 lines
8.2 KiB
GDScript
## 存档槽位文件域件(D3 拆件——自 save_manager 收编槽位族):槽位挂载与模板、激活与建删、
|
|
## 摘要窥视(peek 族)的唯一所有者。槽位态(路径/激活号/模板)随件承载;数据重置与读写盘
|
|
## 经注入的 save 内部协作家族(_reset_to_default/_load_parsed/_rename_overwrite)完成——
|
|
## 本件零 _data 副本零缓存(load 整体换新不陈旧)。
|
|
class_name SlotStore
|
|
extends RefCounted
|
|
|
|
var _save # SaveManagerCore 注入引用(IO 与数据域协作单点)
|
|
var _path: String = "" # 当前写盘目标=激活槽文件(或 attach_path 注入路径);""=无激活态——一切写盘路径拒绝
|
|
var _active_slot := 0 # 激活槽号 1..SLOT_COUNT;0=无激活态(注入非槽派生名路径时亦 0——写盘判定以 _path 非空为准)
|
|
var _slot_template: String # 槽文件名模板(生产=常量;测试经 attach_slot_template 注入隔离命名空间,禁在呈现层/探针各自拼槽名)
|
|
|
|
|
|
func _init(save_ref, path: String = "") -> void:
|
|
_save = save_ref
|
|
_slot_template = _save.SLOT_PATH_TEMPLATE
|
|
_path = path
|
|
_active_slot = _slot_index_of(path)
|
|
|
|
|
|
## 进档注入口:换路径+回默认数据+重读盘(对象引用恒定,MetaProgression 持有的 save 引用零失效)。
|
|
## 三槽化后单路径语义(契约 7)=注入路径即激活槽文件(写盘落该路径、无激活态时注入即激活);
|
|
## 注入槽派生名时激活号同步记账,注入任意隔离路径时激活号 0(写盘仍合法——隔离面行为零变化)。
|
|
func attach_path(path: String) -> void:
|
|
_path = path
|
|
_save._read_only = false
|
|
_active_slot = _slot_index_of(path)
|
|
_save._reset_to_default()
|
|
_save.load_from_disk()
|
|
|
|
|
|
## 测试隔离注入口(契约 7 同源——槽文件名模板整体换注,探针/单测经此隔离到自命名空间,
|
|
## 禁各自拼槽文件名;换注后回无激活态,槽原语全部落注入命名空间)
|
|
func attach_slot_template(template: String) -> void:
|
|
_slot_template = template
|
|
_path = ""
|
|
_active_slot = 0
|
|
_save._read_only = false
|
|
_save._reset_to_default()
|
|
|
|
|
|
# 槽文件路径唯一派生口(模板单点——禁第二处拼名)
|
|
func slot_path(slot: int) -> String:
|
|
return _slot_template % slot
|
|
|
|
|
|
# 激活槽号查询(0 = 无激活态——启动态钉死后 boot 需经选档显式进档/新档)
|
|
func active_slot() -> int:
|
|
return _active_slot
|
|
|
|
|
|
# 写盘目标在位与否(激活槽或注入路径任一;无激活态一切写盘路径拒绝——sv3 判定口)
|
|
func has_write_target() -> bool:
|
|
return _path != ""
|
|
|
|
|
|
# 进档原语(sv5,契约 6/8):激活槽 N+读盘,复用 load_from_disk 迁移/兜底链按槽成立。
|
|
# 高版本拒读槽:激活不成立、恢复切换前三态(路径/激活号/禁写位/数据)——防误写他槽;
|
|
# 损坏槽:兜底链接管(.corrupt.bak 改名+空态继续,激活成立)。
|
|
func activate_slot(slot: int) -> bool:
|
|
if slot < 1 or slot > _save.SLOT_COUNT:
|
|
return false
|
|
var prev_path := _path
|
|
var prev_slot := _active_slot
|
|
var prev_ro: bool = _save._read_only
|
|
var prev_data = _save._data
|
|
_path = slot_path(slot)
|
|
_active_slot = slot
|
|
_save._read_only = false
|
|
_save._reset_to_default()
|
|
if not _save.load_from_disk():
|
|
_path = prev_path
|
|
_active_slot = prev_slot
|
|
_save._read_only = prev_ro
|
|
_save._data = prev_data
|
|
return false
|
|
return true
|
|
|
|
|
|
# 新档原语(sv4):空槽创建=合法白板态(_default_data 全零可玩纪律),创建即激活并落盘。
|
|
# 生产路径只在空槽调用(有档槽开新局=先删档回空再新档);写盘失败回退切换前三态。
|
|
func create_slot(slot: int) -> bool:
|
|
if slot < 1 or slot > _save.SLOT_COUNT:
|
|
return false
|
|
var prev_path := _path
|
|
var prev_slot := _active_slot
|
|
var prev_ro: bool = _save._read_only
|
|
var prev_data = _save._data
|
|
_path = slot_path(slot)
|
|
_active_slot = slot
|
|
_save._read_only = false
|
|
_save._reset_to_default()
|
|
if not _save.write_to_disk():
|
|
_path = prev_path
|
|
_active_slot = prev_slot
|
|
_save._read_only = prev_ro
|
|
_save._data = prev_data
|
|
return false
|
|
return true
|
|
|
|
|
|
# 删档原语(sv6,契约 8):该槽文件删除+盘面即时生效+无 .tmp 残留;删激活槽时激活指针连清除
|
|
# (内存回默认空态,该槽不再可写——激活态生命周期终点)。
|
|
# .corrupt.bak 损坏备份不追删(用户数据留盘可考;同槽再损坏时兜底流按槽覆盖同名备份)。
|
|
# 删不存在文件的空槽=幂等成功;越界槽号拒绝。
|
|
func delete_slot(slot: int) -> bool:
|
|
if slot < 1 or slot > _save.SLOT_COUNT:
|
|
return false
|
|
var p := slot_path(slot)
|
|
if FileAccess.file_exists(p) and not _remove_file(p):
|
|
push_error("[存档序列化] 删档失败(文件删除被拒):%s" % p)
|
|
return false
|
|
var tmp := p + ".tmp"
|
|
if FileAccess.file_exists(tmp):
|
|
_remove_file(tmp) # 残留清偿尽力而为(主文件已删即达成空槽语义)
|
|
if _active_slot == slot:
|
|
_path = ""
|
|
_active_slot = 0
|
|
_save._reset_to_default()
|
|
return true
|
|
|
|
|
|
# 摘要三项按槽窥视读器(sv7/sv8,契约 5/9):钥石余额/冒险成功局数/冒险总局数。
|
|
# 只读零副作用:不激活、不写盘、不触发 _corrupt_fallback 改名(.corrupt.bak 只在正式进档路径产生)。
|
|
# 迁移链在此只读推演(内核 _peek_summary_at 归 save IO 域——本方法经槽路径派生委托)。
|
|
func peek_slot_summary(slot: int) -> Dictionary:
|
|
if slot < 1 or slot > _save.SLOT_COUNT:
|
|
return {"readable": false, "has_file": false, "keystone": 0, "cleared": 0, "total": 0} # 越界=非槽位,非空槽
|
|
return _save._peek_summary_at(slot_path(slot))
|
|
|
|
|
|
# 窥视注入口(契约 7 隔离面同款——测试/探针经路径注入隔离,禁各自拼槽文件名)
|
|
func peek_summary_at(path: String) -> Dictionary:
|
|
return _save._peek_summary_at(path)
|
|
|
|
|
|
# 注入路径的槽号反查(命中槽派生名返回槽号,否则 0——激活号记账用,写盘判定以 _path 非空为准)
|
|
func _slot_index_of(path: String) -> int:
|
|
for i in range(1, _save.SLOT_COUNT + 1):
|
|
if path == slot_path(i):
|
|
return i
|
|
return 0
|
|
|
|
|
|
# 旧隐式单档一次性迁移(Q1 甲,sv12):src 字节级原样搬运到 dst(零解析零改造——损坏/高版本
|
|
# 旧档字节原样落槽 1,读取时由正式进档路径的兜底/拒读链接管,不静默丢数据);
|
|
# dst 已存在不重迁(幂等判据——判别锚④:去判据则重复迁移覆盖槽 1);迁移成功后 src 改名
|
|
# bak 留痕(不删——处置字节留痕,防再次隐式读写)。任一步失败:源保留原地,下次启动重试。
|
|
# tmp+rename 原子形态与 write_to_disk 同纪律(改名原语 _rename_overwrite 归 save IO 域单点)。生产调用=src/dst/bak 为 旧单档/槽 1/留痕名。
|
|
func _migrate_legacy_save(src: String, dst: String, bak: String) -> void:
|
|
if not FileAccess.file_exists(src):
|
|
return # 执行前置:旧档不存在=无操作
|
|
if FileAccess.file_exists(dst):
|
|
return # 幂等判据:槽 1 已存在不重迁
|
|
var tmp := dst + ".tmp"
|
|
if not _copy_file(src, tmp):
|
|
push_warning("[存档序列化] 旧单档迁移失败(源保留,下次启动重试):%s → %s" % [src, tmp])
|
|
return
|
|
if not _save._rename_overwrite(tmp, dst):
|
|
push_warning("[存档序列化] 旧单档迁移改名失败(tmp 保留待查,源保留重试):%s → %s" % [tmp, dst])
|
|
return
|
|
_save._rename_overwrite(src, bak)
|
|
push_warning("[存档序列化] 旧单档已迁入 %s,原件留痕于 %s" % [dst, bak])
|
|
|
|
|
|
# 字节级文件拷贝(迁移专用——buffer 直拷,零解析零改造)
|
|
func _copy_file(from: String, to: String) -> bool:
|
|
var src := FileAccess.open(from, FileAccess.READ)
|
|
if src == null:
|
|
return false
|
|
var dst := FileAccess.open(to, FileAccess.WRITE)
|
|
if dst == null:
|
|
src.close()
|
|
return false
|
|
while not src.eof_reached():
|
|
var chunk := src.get_buffer(65536)
|
|
if chunk.size() > 0:
|
|
dst.store_buffer(chunk)
|
|
src.close()
|
|
dst.close()
|
|
return true
|
|
|
|
|
|
# 文件删除(删档原语专用——呈现层禁 DirAccess 直删,承载面单点)
|
|
func _remove_file(p: String) -> bool:
|
|
var dir := DirAccess.open(p.get_base_dir())
|
|
if dir == null:
|
|
return false
|
|
return dir.remove(p.get_file()) == OK
|