fix: integrate SQLite event storage into load/save system

- Fix load_game to use World.create_with_db() for SQLite event storage
- Add get_events_db_path() to compute event database path from save path
- Add JSON to SQLite migration for backward compatibility with old saves
- Close old EventManager before loading new save to prevent connection leaks
- Add events_db metadata to save file
- Add comprehensive tests for database switching bug and save/load cycle
This commit is contained in:
Zihao Xu
2026-01-07 23:21:55 -08:00
parent a6b8198c3f
commit 06d1bed987
4 changed files with 606 additions and 16 deletions

View File

@@ -3,6 +3,7 @@
主要功能:
- load_game: 从JSON文件加载游戏完整状态
- get_events_db_path: 根据存档路径计算事件数据库路径
- check_save_compatibility: 检查存档版本兼容性(当前未实现严格检查)
加载流程(两阶段):
@@ -17,9 +18,12 @@
- 无法重建的动作会被置为None
- 不存在的Avatar引用会被忽略
事件存储:
- 事件存储在 SQLite 数据库中({save_name}_events.db
- 旧存档的 JSON 事件会自动迁移到 SQLite
注意事项:
- 读档后会重置前端UI状态头像图像、插值等
- 事件历史完整恢复(受限于保存时的数量)
- 地图从头重建(因为地图是固定的),但会恢复宗门总部位置
"""
import json
@@ -37,6 +41,15 @@ from src.classes.relation import Relation
from src.utils.config import CONFIG
def get_events_db_path(save_path: Path) -> Path:
"""
根据存档路径计算事件数据库路径。
例如save_20260105_1423.json -> save_20260105_1423_events.db
"""
return save_path.with_suffix("").with_name(save_path.stem + "_events.db")
def load_game(save_path: Optional[Path] = None) -> Tuple["World", "Simulator", List["Sect"]]:
"""
从文件加载游戏状态
@@ -85,8 +98,15 @@ def load_game(save_path: Optional[Path] = None) -> Tuple["World", "Simulator", L
world_data = save_data.get("world", {})
month_stamp = MonthStamp(world_data["month_stamp"])
# 重建World对象
world = World(map=game_map, month_stamp=month_stamp)
# 计算事件数据库路径。
events_db_path = get_events_db_path(save_path)
# 重建World对象使用 SQLite 事件存储)。
world = World.create_with_db(
map=game_map,
month_stamp=month_stamp,
events_db_path=events_db_path,
)
# 重建天地灵机
from src.classes.celestial_phenomenon import celestial_phenomena_by_id
@@ -153,18 +173,26 @@ def load_game(save_path: Optional[Path] = None) -> Tuple["World", "Simulator", L
if t_name in techniques_by_name:
sect.techniques.append(techniques_by_name[t_name])
# 重建事件历史
# 检查是否需要从 JSON 迁移事件(向后兼容旧存档)。
db_event_count = world.event_manager.count()
events_data = save_data.get("events", [])
for event_data in events_data:
event = Event.from_dict(event_data)
world.event_manager.add_event(event)
if db_event_count == 0 and len(events_data) > 0:
# SQLite 数据库是空的,但 JSON 中有事件,执行迁移。
print(f"正在从 JSON 迁移 {len(events_data)} 条事件到 SQLite...")
for event_data in events_data:
event = Event.from_dict(event_data)
world.event_manager.add_event(event)
print("事件迁移完成")
else:
print(f"已从 SQLite 加载 {db_event_count} 条事件")
# 重建Simulator
simulator_data = save_data.get("simulator", {})
simulator = Simulator(world)
simulator.birth_rate = simulator_data.get("birth_rate", CONFIG.game.npc_birth_rate_per_month)
print(f"存档加载成功!共加载 {len(all_avatars)} 个角色{len(events_data)} 条事件")
print(f"存档加载成功!共加载 {len(all_avatars)} 个角色")
return world, simulator, existed_sects
except Exception as e: