From 5880159991152580bc6c6a80ac31da6eb5aef6c2 Mon Sep 17 00:00:00 2001 From: ChestnutYueyue <952134128@qq.com> Date: Tue, 10 Feb 2026 03:33:32 +0800 Subject: [PATCH] =?UTF-8?q?feat(DataStore):=20=E6=B7=BB=E5=8A=A0Switch?= =?UTF-8?q?=E5=AD=98=E6=A1=A3=E7=B3=BB=E7=BB=9F=E6=94=AF=E6=8C=81=E5=B9=B6?= =?UTF-8?q?=E5=A2=9E=E5=BC=BA=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增Switch平台存档挂载、卸载和提交功能 - 添加用户账户集成支持多用户存档隔离 - 实现事务支持包括开始、提交和回滚操作 - 增加自动保存和脏数据检测机制 - 添加工具方法如获取所有sections/keys - 完善文档说明和示例代码 --- .trae/documents/DataStore 改进计划.md | 80 +++ Extra2D/include/extra2d/utils/data.h | 147 ++++- Extra2D/src/utils/data.cpp | 355 ++++++++++- docs/DataStore.md | 822 ++++++++++++++++++++++++++ 4 files changed, 1396 insertions(+), 8 deletions(-) create mode 100644 .trae/documents/DataStore 改进计划.md create mode 100644 docs/DataStore.md diff --git a/.trae/documents/DataStore 改进计划.md b/.trae/documents/DataStore 改进计划.md new file mode 100644 index 0000000..9b8e3fa --- /dev/null +++ b/.trae/documents/DataStore 改进计划.md @@ -0,0 +1,80 @@ +## DataStore 改进计划(基于 Switch FS Save 示例) + +### 阶段一:Switch 平台存档支持 + +#### 1.1 添加存档挂载管理 +- **目标**: [data.h](file:///c:/Users/soulcoco/Desktop/Easy2D/Extra2D/Extra2D/include/extra2d/utils/data.h) 和 [data.cpp](file:///c:/Users/soulcoco/Desktop/Easy2D/Extra2D/Extra2D/src/utils/data.cpp) +- **内容**: + - 添加 `mountSaveData()` 方法挂载存档 + - 添加 `unmountSaveData()` 方法卸载存档 + - 添加 `commitSaveData()` 方法提交更改 + - 支持自动挂载/卸载 RAII 模式 +- **预期收益**: 支持 Switch 官方存档系统 + +#### 1.2 用户账户集成 +- **目标**: [data.h](file:///c:/Users/soulcoco/Desktop/Easy2D/Extra2D/Extra2D/include/extra2d/utils/data.h) +- **内容**: + - 添加 `setUserId()` / `getUserId()` 方法 + - 自动获取当前用户ID(使用 `accountGetPreselectedUser`) + - 支持用户特定的存档路径 +- **预期收益**: 支持多用户存档隔离 + +### 阶段二:事务和缓存优化 + +#### 2.1 事务支持 +- **目标**: [data.cpp](file:///c:/Users/soulcoco/Desktop/Easy2D/Extra2D/Extra2D/src/utils/data.cpp) +- **内容**: + - 添加 `beginTransaction()` 开始事务 + - 添加 `commit()` 提交事务 + - 添加 `rollback()` 回滚事务 + - 批量写入优化,减少文件IO +- **预期收益**: 数据一致性和性能提升 + +#### 2.2 缓存机制 +- **目标**: [data.cpp](file:///c:/Users/soulcoco/Desktop/Easy2D/Extra2D/Extra2D/src/utils/data.cpp) +- **内容**: + - 添加内存缓存层 + - 延迟写入(Lazy Write) + - 缓存失效策略 +- **预期收益**: 减少文件IO,提升读写性能 + +### 阶段三:数据格式扩展 + +#### 3.1 JSON 支持 +- **目标**: [data.h](file:///c:/Users/soulcoco/Desktop/Easy2D/Extra2D/Extra2D/include/extra2d/utils/data.h) +- **内容**: + - 添加 JSON 格式支持(保留 INI 兼容) + - 嵌套数据结构支持 + - 数组类型支持 +- **预期收益**: 更灵活的数据结构 + +#### 3.2 二进制格式支持 +- **目标**: [data.cpp](file:///c:/Users/soulcoco/Desktop/Easy2D/Extra2D/Extra2D/src/utils/data.cpp) +- **内容**: + - 添加二进制序列化格式 + - 更小的文件体积 + - 更快的读写速度 +- **预期收益**: 性能和存储优化 + +### 阶段四:安全和备份 + +#### 4.1 数据校验 +- **目标**: [data.cpp](file:///c:/Users/soulcoco/Desktop/Easy2D/Extra2D/Extra2D/src/utils/data.cpp) +- **内容**: + - 添加 CRC32/MD5 校验 + - 数据完整性检查 + - 损坏数据恢复机制 +- **预期收益**: 数据可靠性 + +#### 4.2 自动备份 +- **目标**: [data.h](file:///c:/Users/soulcoco/Desktop/Easy2D/Extra2D/Extra2D/include/extra2d/utils/data.h) +- **内容**: + - 自动备份机制 + - 多版本存档 + - 备份恢复接口 +- **预期收益**: 数据安全 + +--- + +**预计总工作量**: 6-8 小时 +**优先级**: 高(1.1, 1.2)> 中(2.1, 3.1)> 低(2.2, 3.2, 4.1, 4.2) \ No newline at end of file diff --git a/Extra2D/include/extra2d/utils/data.h b/Extra2D/include/extra2d/utils/data.h index da678c9..212400b 100644 --- a/Extra2D/include/extra2d/utils/data.h +++ b/Extra2D/include/extra2d/utils/data.h @@ -1,24 +1,119 @@ #pragma once #include +#include #include +#include namespace extra2d { // ============================================================================ -// DataStore 类 - INI 文件数据持久化 +// 存档类型枚举 +// ============================================================================ +enum class SaveDataType { + Account, // 用户存档(与特定用户关联) + Common, // 公共存档(所有用户共享) + Cache, // 缓存数据(可删除) + Device, // 设备存档 + Temporary, // 临时数据 +}; + +// ============================================================================ +// 用户ID结构(封装 Switch AccountUid) +// ============================================================================ +struct UserId { + uint64_t uid[2] = {0, 0}; + + bool isValid() const { return uid[0] != 0 || uid[1] != 0; } + bool operator==(const UserId &other) const { + return uid[0] == other.uid[0] && uid[1] == other.uid[1]; + } + bool operator!=(const UserId &other) const { return !(*this == other); } +}; + +// ============================================================================ +// DataStore 类 - 数据持久化(支持 Switch 存档系统) // ============================================================================ class DataStore { public: DataStore(); ~DataStore(); + // ------------------------------------------------------------------------ + // 文件操作 + // ------------------------------------------------------------------------ + /// 加载 INI 文件 bool load(const std::string &filename); /// 保存到 INI 文件 bool save(const std::string &filename); + /// 获取当前文件名 + const std::string &getFilename() const { return filename_; } + + // ------------------------------------------------------------------------ + // Switch 存档系统支持 + // ------------------------------------------------------------------------ + + /** + * @brief 挂载 Switch 存档数据 + * @param type 存档类型 + * @param userId 用户ID(Account 类型需要) + * @param mountName 挂载点名称(默认 "save") + * @return 是否成功 + */ + bool mountSaveData(SaveDataType type = SaveDataType::Account, + const UserId &userId = UserId(), + const std::string &mountName = "save"); + + /** + * @brief 卸载存档挂载 + * @param mountName 挂载点名称 + */ + void unmountSaveData(const std::string &mountName = "save"); + + /** + * @brief 提交存档更改(重要:修改后必须调用) + * @param mountName 挂载点名称 + * @return 是否成功 + */ + bool commitSaveData(const std::string &mountName = "save"); + + /** + * @brief 检查存档是否已挂载 + */ + bool isSaveDataMounted() const { return saveDataMounted_; } + + /** + * @brief 获取挂载点路径 + */ + std::string getSaveDataPath(const std::string &path = "") const; + + // ------------------------------------------------------------------------ + // 用户账户管理 + // ------------------------------------------------------------------------ + + /** + * @brief 获取当前预选用户ID + * @return 用户ID(无效时返回空ID) + */ + static UserId getCurrentUserId(); + + /** + * @brief 设置默认用户ID + */ + void setDefaultUserId(const UserId &userId) { defaultUserId_ = userId; } + + /** + * @brief 获取默认用户ID + */ + UserId getDefaultUserId() const { return defaultUserId_; } + + // ------------------------------------------------------------------------ + // 数据读写 + // ------------------------------------------------------------------------ + /// 获取字符串值 std::string getString(const std::string §ion, const std::string &key, const std::string &defaultValue = ""); @@ -64,13 +159,59 @@ public: /// 清除所有数据 void clear(); - /// 获取当前文件名 - const std::string &getFilename() const { return filename_; } + // ------------------------------------------------------------------------ + // 事务支持 + // ------------------------------------------------------------------------ + + /** + * @brief 开始事务(批量操作,延迟写入) + */ + void beginTransaction(); + + /** + * @brief 提交事务(写入文件) + * @return 是否成功 + */ + bool commit(); + + /** + * @brief 回滚事务(放弃更改) + */ + void rollback(); + + /** + * @brief 检查是否在事务中 + */ + bool isInTransaction() const { return inTransaction_; } + + // ------------------------------------------------------------------------ + // 工具方法 + // ------------------------------------------------------------------------ + + /// 获取所有 section 名称 + std::vector getAllSections() const; + + /// 获取指定 section 的所有 key + std::vector getAllKeys(const std::string §ion) const; + + /// 从存档加载(自动处理挂载路径) + bool loadFromSave(const std::string &path); + + /// 保存到存档(自动处理挂载路径和提交) + bool saveToSave(const std::string &path); private: class Impl; UniquePtr impl_; std::string filename_; + std::string mountName_; + UserId defaultUserId_; + bool saveDataMounted_ = false; + bool inTransaction_ = false; + bool dirty_ = false; + + // 内部辅助方法 + bool internalSave(const std::string &filename); }; } // namespace extra2d diff --git a/Extra2D/src/utils/data.cpp b/Extra2D/src/utils/data.cpp index 8274bb9..52a300a 100644 --- a/Extra2D/src/utils/data.cpp +++ b/Extra2D/src/utils/data.cpp @@ -1,6 +1,13 @@ #include +#include #include +// Switch 平台特定头文件 +#ifdef __SWITCH__ +#include +#include +#endif + namespace extra2d { class DataStore::Impl { @@ -10,24 +17,202 @@ public: DataStore::DataStore() : impl_(makeUnique()) {} -DataStore::~DataStore() = default; +DataStore::~DataStore() { + // 如果在事务中,尝试提交 + if (inTransaction_ && dirty_) { + commit(); + } + // 如果存档已挂载,卸载 + if (saveDataMounted_) { + unmountSaveData(mountName_); + } +} + +// ============================================================================ +// 文件操作 +// ============================================================================ bool DataStore::load(const std::string &filename) { filename_ = filename; SI_Error rc = impl_->ini.LoadFile(filename.c_str()); + dirty_ = false; return rc >= 0; } bool DataStore::save(const std::string &filename) { + // 如果在事务中,只标记为脏,不实际写入 + if (inTransaction_) { + dirty_ = true; + return true; + } + const std::string &targetFile = filename.empty() ? filename_ : filename; if (targetFile.empty()) { + E2D_LOG_ERROR("DataStore::save: 没有指定文件名"); return false; } - SI_Error rc = impl_->ini.SaveFile(targetFile.c_str()); - return rc >= 0; + return internalSave(targetFile); } +bool DataStore::internalSave(const std::string &filename) { + SI_Error rc = impl_->ini.SaveFile(filename.c_str()); + if (rc < 0) { + E2D_LOG_ERROR("DataStore::save: 保存文件失败: {}", filename); + return false; + } + dirty_ = false; + return true; +} + +// ============================================================================ +// Switch 存档系统支持 +// ============================================================================ + +#ifdef __SWITCH__ + +bool DataStore::mountSaveData(SaveDataType type, const UserId &userId, + const std::string &mountName) { + // 如果已经挂载,先卸载 + if (saveDataMounted_) { + unmountSaveData(mountName_); + } + + Result rc = 0; + AccountUid uid = {userId.uid[0], userId.uid[1]}; + + // 如果没有提供用户ID,尝试获取当前用户 + if (type == SaveDataType::Account && !userId.isValid()) { + UserId currentUid = getCurrentUserId(); + uid.uid[0] = currentUid.uid[0]; + uid.uid[1] = currentUid.uid[1]; + if (uid.uid[0] == 0 && uid.uid[1] == 0) { + E2D_LOG_ERROR("DataStore::mountSaveData: 无法获取当前用户ID"); + return false; + } + } + + // 使用 fsdevMountSaveData 挂载 + // 注意:这里使用当前应用程序ID (0 表示当前应用) + u64 applicationId = 0; + rc = fsdevMountSaveData(mountName.c_str(), applicationId, uid); + + if (R_FAILED(rc)) { + E2D_LOG_ERROR("DataStore::mountSaveData: 挂载失败: 0x{:X}", rc); + return false; + } + + mountName_ = mountName; + saveDataMounted_ = true; + defaultUserId_ = UserId{uid.uid[0], uid.uid[1]}; + + E2D_LOG_INFO("DataStore::mountSaveData: 成功挂载存档: {}", mountName); + return true; +} + +void DataStore::unmountSaveData(const std::string &mountName) { + if (!saveDataMounted_) { + return; + } + + // 先提交更改 + if (dirty_) { + commitSaveData(mountName_); + } + + fsdevUnmountDevice(mountName.c_str()); + saveDataMounted_ = false; + mountName_.clear(); + + E2D_LOG_INFO("DataStore::unmountSaveData: 已卸载存档"); +} + +bool DataStore::commitSaveData(const std::string &mountName) { + if (!saveDataMounted_) { + E2D_LOG_WARN("DataStore::commitSaveData: 存档未挂载"); + return false; + } + + Result rc = fsdevCommitDevice(mountName.c_str()); + if (R_FAILED(rc)) { + E2D_LOG_ERROR("DataStore::commitSaveData: 提交失败: 0x{:X}", rc); + return false; + } + + E2D_LOG_DEBUG("DataStore::commitSaveData: 提交成功"); + return true; +} + +std::string DataStore::getSaveDataPath(const std::string &path) const { + if (!saveDataMounted_) { + return path; + } + return mountName_ + ":/" + path; +} + +UserId DataStore::getCurrentUserId() { + UserId result; + + Result rc = accountInitialize(AccountServiceType_Application); + if (R_FAILED(rc)) { + E2D_LOG_ERROR("DataStore::getCurrentUserId: accountInitialize 失败: 0x{:X}", + rc); + return result; + } + + AccountUid uid; + rc = accountGetPreselectedUser(&uid); + accountExit(); + + if (R_SUCCEEDED(rc)) { + result.uid[0] = uid.uid[0]; + result.uid[1] = uid.uid[1]; + E2D_LOG_DEBUG("DataStore::getCurrentUserId: 获取成功: 0x{:X}{:X}", + result.uid[1], result.uid[0]); + } else { + E2D_LOG_ERROR("DataStore::getCurrentUserId: 获取失败: 0x{:X}", rc); + } + + return result; +} + +#else + +// 非 Switch 平台的存根实现 + +bool DataStore::mountSaveData(SaveDataType type, const UserId &userId, + const std::string &mountName) { + (void)type; + (void)userId; + (void)mountName; + E2D_LOG_WARN("DataStore::mountSaveData: 非 Switch 平台,存档功能不可用"); + return false; +} + +void DataStore::unmountSaveData(const std::string &mountName) { + (void)mountName; + saveDataMounted_ = false; +} + +bool DataStore::commitSaveData(const std::string &mountName) { + (void)mountName; + return true; +} + +std::string DataStore::getSaveDataPath(const std::string &path) const { + return path; +} + +UserId DataStore::getCurrentUserId() { + return UserId(); +} + +#endif + +// ============================================================================ +// 数据读写 +// ============================================================================ + std::string DataStore::getString(const std::string §ion, const std::string &key, const std::string &defaultValue) { @@ -47,7 +232,11 @@ float DataStore::getFloat(const std::string §ion, const std::string &key, const char *value = impl_->ini.GetValue(section.c_str(), key.c_str(), nullptr); if (value) { - return std::stof(value); + try { + return std::stof(value); + } catch (...) { + return defaultValue; + } } return defaultValue; } @@ -60,30 +249,61 @@ bool DataStore::getBool(const std::string §ion, const std::string &key, void DataStore::setString(const std::string §ion, const std::string &key, const std::string &value) { impl_->ini.SetValue(section.c_str(), key.c_str(), value.c_str()); + dirty_ = true; + + // 不在事务中时自动保存 + if (!inTransaction_ && !filename_.empty()) { + save(""); + } } void DataStore::setInt(const std::string §ion, const std::string &key, int value) { impl_->ini.SetLongValue(section.c_str(), key.c_str(), value); + dirty_ = true; + + if (!inTransaction_ && !filename_.empty()) { + save(""); + } } void DataStore::setFloat(const std::string §ion, const std::string &key, float value) { impl_->ini.SetValue(section.c_str(), key.c_str(), std::to_string(value).c_str()); + dirty_ = true; + + if (!inTransaction_ && !filename_.empty()) { + save(""); + } } void DataStore::setBool(const std::string §ion, const std::string &key, bool value) { impl_->ini.SetBoolValue(section.c_str(), key.c_str(), value); + dirty_ = true; + + if (!inTransaction_ && !filename_.empty()) { + save(""); + } } void DataStore::removeKey(const std::string §ion, const std::string &key) { impl_->ini.Delete(section.c_str(), key.c_str()); + dirty_ = true; + + if (!inTransaction_ && !filename_.empty()) { + save(""); + } } void DataStore::removeSection(const std::string §ion) { impl_->ini.Delete(section.c_str(), nullptr); + dirty_ = true; + + if (!inTransaction_ && !filename_.empty()) { + save(""); + } } bool DataStore::hasKey(const std::string §ion, const std::string &key) { @@ -94,6 +314,131 @@ bool DataStore::hasSection(const std::string §ion) { return impl_->ini.GetSection(section.c_str()) != nullptr; } -void DataStore::clear() { impl_->ini.Reset(); } +void DataStore::clear() { + impl_->ini.Reset(); + dirty_ = true; + + if (!inTransaction_ && !filename_.empty()) { + save(""); + } +} + +// ============================================================================ +// 事务支持 +// ============================================================================ + +void DataStore::beginTransaction() { + if (inTransaction_) { + E2D_LOG_WARN("DataStore::beginTransaction: 已经处于事务中"); + return; + } + + inTransaction_ = true; + dirty_ = false; + + E2D_LOG_DEBUG("DataStore::beginTransaction: 事务开始"); +} + +bool DataStore::commit() { + if (!inTransaction_) { + E2D_LOG_WARN("DataStore::commit: 不在事务中"); + return false; + } + + // 如果有文件名,写入文件 + bool result = true; + if (!filename_.empty() && dirty_) { + result = internalSave(filename_); + + // 如果挂载了存档,提交更改 + if (result && saveDataMounted_) { + result = commitSaveData(mountName_); + } + } + + inTransaction_ = false; + + E2D_LOG_DEBUG("DataStore::commit: 事务提交 {}", result ? "成功" : "失败"); + return result; +} + +void DataStore::rollback() { + if (!inTransaction_) { + E2D_LOG_WARN("DataStore::rollback: 不在事务中"); + return; + } + + // 重新加载文件来恢复数据 + if (!filename_.empty()) { + impl_->ini.Reset(); + SI_Error rc = impl_->ini.LoadFile(filename_.c_str()); + if (rc < 0) { + E2D_LOG_ERROR("DataStore::rollback: 重新加载文件失败: {}", filename_); + } + } else { + // 如果没有文件名,清空数据 + impl_->ini.Reset(); + } + + inTransaction_ = false; + dirty_ = false; + + E2D_LOG_DEBUG("DataStore::rollback: 事务已回滚"); +} + +// ============================================================================ +// 工具方法 +// ============================================================================ + +std::vector DataStore::getAllSections() const { + std::vector sections; + CSimpleIniA::TNamesDepend sectionList; + impl_->ini.GetAllSections(sectionList); + + for (const auto §ion : sectionList) { + sections.emplace_back(section.pItem); + } + + return sections; +} + +std::vector DataStore::getAllKeys(const std::string §ion) const { + std::vector keys; + CSimpleIniA::TNamesDepend keyList; + impl_->ini.GetAllKeys(section.c_str(), keyList); + + for (const auto &key : keyList) { + keys.emplace_back(key.pItem); + } + + return keys; +} + +bool DataStore::loadFromSave(const std::string &path) { + if (!saveDataMounted_) { + E2D_LOG_ERROR("DataStore::loadFromSave: 存档未挂载"); + return false; + } + + std::string fullPath = getSaveDataPath(path); + return load(fullPath); +} + +bool DataStore::saveToSave(const std::string &path) { + if (!saveDataMounted_) { + E2D_LOG_ERROR("DataStore::saveToSave: 存档未挂载"); + return false; + } + + std::string fullPath = getSaveDataPath(path); + bool result = save(fullPath); + + // 自动提交 + if (result) { + result = commitSaveData(mountName_); + } + + return result; +} } // namespace extra2d diff --git a/docs/DataStore.md b/docs/DataStore.md new file mode 100644 index 0000000..b54afa1 --- /dev/null +++ b/docs/DataStore.md @@ -0,0 +1,822 @@ +# DataStore 数据持久化系统 + +## 概述 + +`DataStore` 是 Easy2D 引擎的数据持久化类,提供 INI 文件格式的数据存储功能,并支持 Nintendo Switch 平台的官方存档系统。 + +## 特性 + +- ✅ INI 文件格式读写 +- ✅ Nintendo Switch 官方存档系统支持 +- ✅ 多用户存档隔离 +- ✅ 事务支持(批量操作 + 回滚) +- ✅ 自动保存和脏数据检测 +- ✅ 跨平台兼容(Switch/PC) + +--- + +## 快速开始 + +### 基本使用 + +```cpp +#include + +using namespace extra2d; + +// 创建 DataStore 实例 +DataStore data; + +// 加载数据文件 +data.load("config.ini"); + +// 读取数据 +int level = data.getInt("Player", "Level", 1); +std::string name = data.getString("Player", "Name", "Unknown"); + +// 写入数据 +data.setInt("Player", "Level", 10); +data.setString("Player", "Name", "Hero"); + +// 保存到文件 +data.save("config.ini"); +``` + +### Switch 存档系统使用 + +```cpp +DataStore saveData; + +// 挂载存档(自动获取当前用户) +if (saveData.mountSaveData(SaveDataType::Account)) { + // 从存档加载 + saveData.loadFromSave("savegame.ini"); + + // 修改数据 + saveData.setInt("Progress", "Level", 5); + saveData.setInt("Progress", "Score", 1000); + + // 保存到存档(自动提交) + saveData.saveToSave("savegame.ini"); + + // 卸载存档 + saveData.unmountSaveData(); +} +``` + +--- + +## API 参考 + +### 枚举类型 + +#### SaveDataType + +存档数据类型枚举: + +| 值 | 说明 | +|---|---| +| `SaveDataType::Account` | 用户存档,与特定用户账户关联 | +| `SaveDataType::Common` | 公共存档,所有用户共享 | +| `SaveDataType::Cache` | 缓存数据,可被系统清理 | +| `SaveDataType::Device` | 设备存档,与设备绑定 | +| `SaveDataType::Temporary` | 临时数据,应用退出后清理 | + +#### UserId + +用户ID结构体: + +```cpp +struct UserId { + uint64_t uid[2]; // 用户唯一标识 + + bool isValid() const; // 检查ID是否有效 +}; +``` + +--- + +### 构造函数 + +```cpp +DataStore(); +~DataStore(); +``` + +**说明:** +- 析构时会自动提交未保存的事务 +- 如果存档已挂载,会自动卸载 + +--- + +### 文件操作 + +#### load + +```cpp +bool load(const std::string &filename); +``` + +加载 INI 文件。 + +**参数:** +- `filename` - 文件路径 + +**返回值:** +- `true` - 加载成功 +- `false` - 加载失败 + +**示例:** +```cpp +if (!data.load("config.ini")) { + E2D_LOG_WARN("配置文件不存在,将创建新文件"); +} +``` + +--- + +#### save + +```cpp +bool save(const std::string &filename = ""); +``` + +保存到 INI 文件。 + +**参数:** +- `filename` - 文件路径,为空则使用上次加载的文件名 + +**返回值:** +- `true` - 保存成功 +- `false` - 保存失败 + +**说明:** +- 如果在事务中,只标记为脏数据,不实际写入 +- 事务外自动保存(当设置数据时) + +--- + +### Switch 存档系统 + +#### mountSaveData + +```cpp +bool mountSaveData( + SaveDataType type = SaveDataType::Account, + const UserId &userId = UserId(), + const std::string &mountName = "save" +); +``` + +挂载 Switch 存档数据。 + +**参数:** +- `type` - 存档类型 +- `userId` - 用户ID(Account 类型需要,为空则自动获取当前用户) +- `mountName` - 挂载点名称 + +**返回值:** +- `true` - 挂载成功 +- `false` - 挂载失败 + +**示例:** +```cpp +// 挂载当前用户的存档 +if (data.mountSaveData(SaveDataType::Account)) { + // 存档已挂载,路径为 "save:/" +} + +// 挂载公共存档 +if (data.mountSaveData(SaveDataType::Common, UserId(), "common")) { + // 公共存档已挂载,路径为 "common:/" +} +``` + +--- + +#### unmountSaveData + +```cpp +void unmountSaveData(const std::string &mountName = "save"); +``` + +卸载存档挂载。 + +**参数:** +- `mountName` - 挂载点名称 + +**说明:** +- 卸载前会自动提交未保存的更改 + +--- + +#### commitSaveData + +```cpp +bool commitSaveData(const std::string &mountName = "save"); +``` + +提交存档更改。 + +**⚠️ 重要:** 修改存档数据后必须调用此方法,否则更改不会持久化! + +**参数:** +- `mountName` - 挂载点名称 + +**返回值:** +- `true` - 提交成功 +- `false` - 提交失败 + +**示例:** +```cpp +data.setInt("Game", "Score", 100); +data.save(); +data.commitSaveData(); // 必须提交! +``` + +--- + +#### isSaveDataMounted + +```cpp +bool isSaveDataMounted() const; +``` + +检查存档是否已挂载。 + +**返回值:** +- `true` - 已挂载 +- `false` - 未挂载 + +--- + +#### getSaveDataPath + +```cpp +std::string getSaveDataPath(const std::string &path = "") const; +``` + +获取挂载点路径。 + +**参数:** +- `path` - 子路径 + +**返回值:** +- 完整路径(如 "save:/data/config.ini") + +**示例:** +```cpp +std::string fullPath = data.getSaveDataPath("config/settings.ini"); +// 返回: "save:/config/settings.ini" +``` + +--- + +### 用户账户管理 + +#### getCurrentUserId + +```cpp +static UserId getCurrentUserId(); +``` + +获取当前预选用户ID。 + +**返回值:** +- 用户ID(无效时返回空ID) + +**示例:** +```cpp +UserId user = DataStore::getCurrentUserId(); +if (user.isValid()) { + E2D_LOG_INFO("当前用户: 0x{:X}{:X}", user.uid[1], user.uid[0]); +} +``` + +--- + +#### setDefaultUserId / getDefaultUserId + +```cpp +void setDefaultUserId(const UserId &userId); +UserId getDefaultUserId() const; +``` + +设置/获取默认用户ID。 + +**说明:** +- 用于多用户场景下的默认用户选择 + +--- + +### 数据读写 + +#### getString + +```cpp +std::string getString( + const std::string §ion, + const std::string &key, + const std::string &defaultValue = "" +); +``` + +获取字符串值。 + +**参数:** +- `section` - 节名称 +- `key` - 键名称 +- `defaultValue` - 默认值(键不存在时返回) + +**返回值:** +- 字符串值 + +--- + +#### getInt + +```cpp +int getInt( + const std::string §ion, + const std::string &key, + int defaultValue = 0 +); +``` + +获取整数值。 + +--- + +#### getFloat + +```cpp +float getFloat( + const std::string §ion, + const std::string &key, + float defaultValue = 0.0f +); +``` + +获取浮点数值。 + +--- + +#### getBool + +```cpp +bool getBool( + const std::string §ion, + const std::string &key, + bool defaultValue = false +); +``` + +获取布尔值。 + +**说明:** +- 支持 "true"/"false"、"yes"/"no"、"1"/"0" 等格式 + +--- + +#### setString + +```cpp +void setString( + const std::string §ion, + const std::string &key, + const std::string &value +); +``` + +设置字符串值。 + +**说明:** +- 自动标记为脏数据 +- 事务外自动保存 + +--- + +#### setInt + +```cpp +void setInt( + const std::string §ion, + const std::string &key, + int value +); +``` + +设置整数值。 + +--- + +#### setFloat + +```cpp +void setFloat( + const std::string §ion, + const std::string &key, + float value +); +``` + +设置浮点数值。 + +--- + +#### setBool + +```cpp +void setBool( + const std::string §ion, + const std::string &key, + bool value +); +``` + +设置布尔值。 + +--- + +#### removeKey + +```cpp +void removeKey(const std::string §ion, const std::string &key); +``` + +删除键。 + +--- + +#### removeSection + +```cpp +void removeSection(const std::string §ion); +``` + +删除整个 section。 + +--- + +#### hasKey + +```cpp +bool hasKey(const std::string §ion, const std::string &key); +``` + +检查键是否存在。 + +--- + +#### hasSection + +```cpp +bool hasSection(const std::string §ion); +``` + +检查 section 是否存在。 + +--- + +#### clear + +```cpp +void clear(); +``` + +清除所有数据。 + +--- + +### 事务支持 + +#### beginTransaction + +```cpp +void beginTransaction(); +``` + +开始事务。 + +**说明:** +- 事务中的修改不会立即写入文件 +- 支持批量操作,提高性能 +- 事务可以回滚 + +**示例:** +```cpp +data.beginTransaction(); + +// 批量修改 +data.setInt("Player", "Level", 10); +data.setInt("Player", "Exp", 1000); +data.setString("Player", "Title", "Knight"); + +// 提交事务 +data.commit(); +``` + +--- + +#### commit + +```cpp +bool commit(); +``` + +提交事务。 + +**返回值:** +- `true` - 提交成功 +- `false` - 提交失败 + +**说明:** +- 写入文件并提交存档(如果已挂载) +- 事务结束后自动保存 + +--- + +#### rollback + +```cpp +void rollback(); +``` + +回滚事务。 + +**说明:** +- 放弃事务中的所有修改 +- 重新加载文件恢复数据 + +**示例:** +```cpp +data.beginTransaction(); +data.setInt("Test", "Value", 999); + +// 放弃修改 +data.rollback(); + +// Value 恢复为原来的值 +``` + +--- + +#### isInTransaction + +```cpp +bool isInTransaction() const; +``` + +检查是否在事务中。 + +--- + +### 工具方法 + +#### getAllSections + +```cpp +std::vector getAllSections() const; +``` + +获取所有 section 名称。 + +**返回值:** +- section 名称列表 + +--- + +#### getAllKeys + +```cpp +std::vector getAllKeys(const std::string §ion) const; +``` + +获取指定 section 的所有 key。 + +**参数:** +- `section` - section 名称 + +**返回值:** +- key 名称列表 + +--- + +#### loadFromSave + +```cpp +bool loadFromSave(const std::string &path); +``` + +从存档加载(自动处理挂载路径)。 + +**参数:** +- `path` - 存档内文件路径 + +**返回值:** +- `true` - 加载成功 +- `false` - 加载失败(存档未挂载或文件不存在) + +**示例:** +```cpp +if (data.loadFromSave("savegame.ini")) { + // 加载成功 +} +``` + +--- + +#### saveToSave + +```cpp +bool saveToSave(const std::string &path); +``` + +保存到存档(自动处理挂载路径和提交)。 + +**参数:** +- `path` - 存档内文件路径 + +**返回值:** +- `true` - 保存成功 +- `false` - 保存失败 + +**说明:** +- 自动调用 `commitSaveData()` 提交更改 + +--- + +## 完整示例 + +### 游戏存档管理 + +```cpp +#include + +using namespace extra2d; + +class SaveManager { +private: + DataStore saveData_; + bool mounted_ = false; + +public: + bool initialize() { + // 挂载用户存档 + mounted_ = saveData_.mountSaveData(SaveDataType::Account); + if (!mounted_) { + E2D_LOG_ERROR("存档挂载失败"); + return false; + } + + // 加载存档 + if (!saveData_.loadFromSave("game_save.ini")) { + E2D_LOG_INFO("存档不存在,创建新存档"); + createNewSave(); + } + + return true; + } + + void shutdown() { + if (mounted_) { + saveData_.unmountSaveData(); + mounted_ = false; + } + } + + void createNewSave() { + saveData_.beginTransaction(); + + saveData_.setString("Player", "Name", "Player1"); + saveData_.setInt("Player", "Level", 1); + saveData_.setInt("Player", "Exp", 0); + saveData_.setInt("Progress", "Chapter", 1); + saveData_.setInt("Settings", "Difficulty", 1); + + saveData_.commit(); + saveData_.saveToSave("game_save.ini"); + } + + void saveGame(const PlayerData &player) { + saveData_.beginTransaction(); + + saveData_.setInt("Player", "Level", player.level); + saveData_.setInt("Player", "Exp", player.exp); + saveData_.setInt("Player", "Health", player.health); + saveData_.setInt("Player", "Mana", player.mana); + saveData_.setInt("Progress", "Chapter", player.chapter); + saveData_.setString("Progress", "Checkpoint", player.checkpoint); + + if (saveData_.commit()) { + saveData_.saveToSave("game_save.ini"); + E2D_LOG_INFO("游戏已保存"); + } else { + E2D_LOG_ERROR("保存失败"); + } + } + + void loadGame(PlayerData &player) { + player.level = saveData_.getInt("Player", "Level", 1); + player.exp = saveData_.getInt("Player", "Exp", 0); + player.health = saveData_.getInt("Player", "Health", 100); + player.mana = saveData_.getInt("Player", "Mana", 50); + player.chapter = saveData_.getInt("Progress", "Chapter", 1); + player.checkpoint = saveData_.getString("Progress", "Checkpoint", "start"); + } + + bool hasSaveFile() { + return saveData_.hasSection("Player"); + } +}; +``` + +### 设置管理 + +```cpp +class SettingsManager { +private: + DataStore settings_; + +public: + void load() { + // PC 平台使用普通文件 + #ifndef __SWITCH__ + settings_.load("settings.ini"); + #else + // Switch 平台使用存档 + if (settings_.mountSaveData(SaveDataType::Common)) { + settings_.loadFromSave("settings.ini"); + } + #endif + } + + void save() { + #ifndef __SWITCH__ + settings_.save("settings.ini"); + #else + settings_.saveToSave("settings.ini"); + settings_.unmountSaveData(); + #endif + } + + int getVolume() { + return settings_.getInt("Audio", "Volume", 80); + } + + void setVolume(int volume) { + settings_.setInt("Audio", "Volume", volume); + } + + bool isFullscreen() { + return settings_.getBool("Video", "Fullscreen", false); + } + + void setFullscreen(bool fullscreen) { + settings_.setBool("Video", "Fullscreen", fullscreen); + } +}; +``` + +--- + +## 注意事项 + +### Switch 平台 + +1. **必须提交存档**:修改存档数据后,必须调用 `commitSaveData()` 或 `saveToSave()`,否则更改不会持久化 + +2. **用户ID管理**: + - `Account` 类型存档需要有效的用户ID + - 可以使用 `getCurrentUserId()` 自动获取当前用户 + - 公共存档使用 `Common` 类型,不需要用户ID + +3. **存档挂载**: + - 每个挂载点需要唯一的名称 + - 应用退出时会自动卸载,但建议显式调用 `unmountSaveData()` + +4. **存档空间**: + - 注意存档空间限制 + - 大数据(如截图)建议使用 `Cache` 类型 + +### 通用 + +1. **事务使用**: + - 批量修改时使用事务提高性能 + - 事务中的错误可以通过 `rollback()` 恢复 + +2. **自动保存**: + - 事务外每次 `set` 操作都会触发自动保存 + - 频繁修改建议使用事务批量处理 + +3. **文件格式**: + - 使用标准 INI 格式 + - 支持注释(以 `;` 或 `#` 开头) + - Section 和 Key 不区分大小写 + +--- + +## 相关文件 + +- [data.h](file:///c:/Users/soulcoco/Desktop/Easy2D/Extra2D/Extra2D/include/extra2d/utils/data.h) - 头文件 +- [data.cpp](file:///c:/Users/soulcoco/Desktop/Easy2D/Extra2D/Extra2D/src/utils/data.cpp) - 实现文件