AnvilKitAnvilKit

持久化

设置、存档管理器和世界存储,用于游戏数据

持久化(feature "persistence"

持久化模块提供三个层次:玩家设置(Settings)、多存档槽位存档管理器(SaveManager)和原始键值世界存储(WorldStorage)

Resource 派生: 当启用 bevy_ecs feature 时,所有持久化类型 — SaveManagerSettingsWorldStorageAutoSaveConfigAutoSaveStateMigrationRunner — 均派生 Resource,可以直接插入到 ECS world 中。

PersistencePlugin

PersistencePlugin"persistence" feature 门控,在 ECS 调度中配置自动存档支持:

  • AutoSaveConfigAutoSaveState 注册为 ECS 资源。
  • 向调度中添加 auto_save_tick_system,该系统计算经过时间,并在配置的间隔到期时触发存档。
use anvilkit_app::plugins::PersistencePlugin;

app.add_plugins(PersistencePlugin);

// Optionally configure the auto-save interval (default: 300 seconds)
app.insert_resource(AutoSaveConfig { interval_secs: 120.0 });

SettingsApplyPlugin

SettingsApplyPlugin 在每帧的 PostUpdate 阶段运行,将 Settings 资源同步到运行时游戏状态:

  • 音频:Settings.audio 字段(主音量、音乐、音效音量)复制到 AudioBus 资源中。
  • 输入:Settings.input.mouse_sensitivity 复制到 CameraController 资源中。

这意味着游戏只需修改 Settings — 插件会自动传播这些值。

错误分类

持久化函数返回类型化的 AnvilKitError::Persistence 变体,取代旧的 generic() 错误。使用专用构造器:

AnvilKitError::persistence("save slot corrupted");
AnvilKitError::persistence_with_path("failed to write", &path);

Settings

以 RON 格式存储的类型化配置。当文件缺失或损坏时,回退到默认值。

子结构

GraphicsSettings

字段类型默认值说明
widthu321280窗口宽度
heightu32720窗口高度
fullscreenboolfalse全屏模式
vsyncbooltrue垂直同步
msaau324MSAA 采样数(1/2/4)
bloombooltrue泛光后处理
ssaobooltrue屏幕空间环境光遮蔽
shadow_qualityu3220=关闭, 1=低, 2=中, 3=高

AudioSettings

字段类型默认值说明
master_volumef321.0主音量 0.0..1.0
music_volumef320.8音乐音量 0.0..1.0
sfx_volumef321.0音效音量 0.0..1.0

InputSettings

字段类型默认值说明
mouse_sensitivityf320.003鼠标灵敏度倍率
invert_yboolfalse反转 Y 轴
action_overridesHashMap<String, String>{}自定义按键重绑定

Settings API

方法签名说明
load(path: &Path) -> Self从 RON 加载;反序列化失败时输出警告并返回默认值
save(&self, path: &Path) -> Result<(), AnvilKitError>写入 RON(自动创建目录)
default_path() -> PathBuf"config/settings.ron"
use anvilkit_core::persistence::Settings;

// 加载(或使用默认值)
let mut settings = Settings::load(&Settings::default_path());
settings.graphics.vsync = false;
settings.audio.master_volume = 0.7;
settings.save(&Settings::default_path()).unwrap();

SaveManager

管理多个存档槽位,每个槽位包含元数据(meta.ron)和一个用于 WorldStoragedata/ 目录。

目录结构

saves/
  quick/
    meta.ron
    data/        <-- WorldStorage 根目录
  slot_1/
    meta.ron
    data/

SaveSlotInfo 字段

字段类型说明
nameString槽位名称(如 "quick""slot_1"
timestampu64Unix 时间戳(秒)
play_time_secsf64累计游戏时间
game_versionString游戏版本字符串
metadataHashMap<String, String>游戏特定的元数据

SaveManager API

方法签名说明
new(dir: impl AsRef<Path>, version: &str) -> Result<Self, AnvilKitError>创建/打开存档目录
save(&self, slot: &str, play_time: f64, metadata: HashMap) -> Result<PathBuf, AnvilKitError>写入元数据,返回 data/ 路径
list_saves(&self) -> Vec<SaveSlotInfo>所有槽位按最新排序
get_save_info(&self, slot: &str) -> Option<SaveSlotInfo>读取单个槽位的元数据
delete(&self, slot: &str) -> Result<(), AnvilKitError>递归删除槽位目录
slot_data_path(&self, slot: &str) -> PathBuf获取 data/ 路径而不写入元数据
use anvilkit_core::persistence::SaveManager;
use std::collections::HashMap;

let mgr = SaveManager::new("saves", "1.0.0").unwrap();

// 创建存档
let mut meta = HashMap::new();
meta.insert("level".into(), "3".into());
let data_path = mgr.save("slot_1", 3600.0, meta).unwrap();

// 列出存档
for info in mgr.list_saves() {
    println!("{}: played {}s", info.name, info.play_time_secs);
}

// 删除
mgr.delete("slot_1").unwrap();

WorldStorage

基于文件系统的键值存储。每个键映射到基目录下的一个文件。键中的斜杠会创建子目录。写入是原子的(先写入 .tmp 文件再重命名)。

API

方法签名说明
open(path: impl AsRef<Path>) -> Result<Self, AnvilKitError>打开/创建存储目录
get(&self, key: &str) -> Option<Vec<u8>>读取值;缺失时返回 None
put(&self, key: &str, value: &[u8]) -> Result<(), AnvilKitError>原子写入(tmp + 重命名)
delete(&self, key: &str) -> Result<(), AnvilKitError>删除一个键
keys_with_prefix(&self, prefix: &str) -> Vec<String>列出以指定前缀开头的键
batch_put(&self, entries: &[(&str, &[u8])]) -> Result<(), AnvilKitError>批量写入多个键(每个均为原子操作)
path(&self) -> &Path基目录
use anvilkit_core::persistence::WorldStorage;

let storage = WorldStorage::open("saves/slot_1/data").unwrap();

// 存储区块数据
storage.put("chunk/0/0", &bincode::serialize(&chunk).unwrap()).unwrap();

// 读取回来
if let Some(data) = storage.get("chunk/0/0") {
    let chunk: Chunk = bincode::deserialize(&data).unwrap();
}

// 枚举所有区块
let keys = storage.keys_with_prefix("chunk/");
println!("Stored {} chunks", keys.len());

// 批量写入
storage.batch_put(&[
    ("meta/version", b"1"),
    ("meta/seed", b"42"),
]).unwrap();

目录