feat(DataStore): 添加Switch存档系统支持并增强功能

- 新增Switch平台存档挂载、卸载和提交功能
- 添加用户账户集成支持多用户存档隔离
- 实现事务支持包括开始、提交和回滚操作
- 增加自动保存和脏数据检测机制
- 添加工具方法如获取所有sections/keys
- 完善文档说明和示例代码
This commit is contained in:
ChestnutYueyue 2026-02-10 03:33:32 +08:00
parent 82c44b966f
commit 5880159991
4 changed files with 1396 additions and 8 deletions

View File

@ -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

View File

@ -1,24 +1,119 @@
#pragma once #pragma once
#include <extra2d/core/types.h> #include <extra2d/core/types.h>
#include <functional>
#include <string> #include <string>
#include <vector>
namespace extra2d { 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 { class DataStore {
public: public:
DataStore(); DataStore();
~DataStore(); ~DataStore();
// ------------------------------------------------------------------------
// 文件操作
// ------------------------------------------------------------------------
/// 加载 INI 文件 /// 加载 INI 文件
bool load(const std::string &filename); bool load(const std::string &filename);
/// 保存到 INI 文件 /// 保存到 INI 文件
bool save(const std::string &filename); bool save(const std::string &filename);
/// 获取当前文件名
const std::string &getFilename() const { return filename_; }
// ------------------------------------------------------------------------
// Switch 存档系统支持
// ------------------------------------------------------------------------
/**
* @brief Switch
* @param type
* @param userId IDAccount
* @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 IDID
*/
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 &section, const std::string &key, std::string getString(const std::string &section, const std::string &key,
const std::string &defaultValue = ""); const std::string &defaultValue = "");
@ -64,13 +159,59 @@ public:
/// 清除所有数据 /// 清除所有数据
void clear(); 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<std::string> getAllSections() const;
/// 获取指定 section 的所有 key
std::vector<std::string> getAllKeys(const std::string &section) const;
/// 从存档加载(自动处理挂载路径)
bool loadFromSave(const std::string &path);
/// 保存到存档(自动处理挂载路径和提交)
bool saveToSave(const std::string &path);
private: private:
class Impl; class Impl;
UniquePtr<Impl> impl_; UniquePtr<Impl> impl_;
std::string filename_; 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 } // namespace extra2d

View File

@ -1,6 +1,13 @@
#include <extra2d/utils/data.h> #include <extra2d/utils/data.h>
#include <extra2d/utils/logger.h>
#include <simpleini/SimpleIni.h> #include <simpleini/SimpleIni.h>
// Switch 平台特定头文件
#ifdef __SWITCH__
#include <switch.h>
#include <switch/services/fs.h>
#endif
namespace extra2d { namespace extra2d {
class DataStore::Impl { class DataStore::Impl {
@ -10,24 +17,202 @@ public:
DataStore::DataStore() : impl_(makeUnique<Impl>()) {} DataStore::DataStore() : impl_(makeUnique<Impl>()) {}
DataStore::~DataStore() = default; DataStore::~DataStore() {
// 如果在事务中,尝试提交
if (inTransaction_ && dirty_) {
commit();
}
// 如果存档已挂载,卸载
if (saveDataMounted_) {
unmountSaveData(mountName_);
}
}
// ============================================================================
// 文件操作
// ============================================================================
bool DataStore::load(const std::string &filename) { bool DataStore::load(const std::string &filename) {
filename_ = filename; filename_ = filename;
SI_Error rc = impl_->ini.LoadFile(filename.c_str()); SI_Error rc = impl_->ini.LoadFile(filename.c_str());
dirty_ = false;
return rc >= 0; return rc >= 0;
} }
bool DataStore::save(const std::string &filename) { bool DataStore::save(const std::string &filename) {
// 如果在事务中,只标记为脏,不实际写入
if (inTransaction_) {
dirty_ = true;
return true;
}
const std::string &targetFile = filename.empty() ? filename_ : filename; const std::string &targetFile = filename.empty() ? filename_ : filename;
if (targetFile.empty()) { if (targetFile.empty()) {
E2D_LOG_ERROR("DataStore::save: 没有指定文件名");
return false; return false;
} }
SI_Error rc = impl_->ini.SaveFile(targetFile.c_str()); return internalSave(targetFile);
return rc >= 0;
} }
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 &section, std::string DataStore::getString(const std::string &section,
const std::string &key, const std::string &key,
const std::string &defaultValue) { const std::string &defaultValue) {
@ -47,7 +232,11 @@ float DataStore::getFloat(const std::string &section, const std::string &key,
const char *value = const char *value =
impl_->ini.GetValue(section.c_str(), key.c_str(), nullptr); impl_->ini.GetValue(section.c_str(), key.c_str(), nullptr);
if (value) { if (value) {
return std::stof(value); try {
return std::stof(value);
} catch (...) {
return defaultValue;
}
} }
return defaultValue; return defaultValue;
} }
@ -60,30 +249,61 @@ bool DataStore::getBool(const std::string &section, const std::string &key,
void DataStore::setString(const std::string &section, const std::string &key, void DataStore::setString(const std::string &section, const std::string &key,
const std::string &value) { const std::string &value) {
impl_->ini.SetValue(section.c_str(), key.c_str(), value.c_str()); 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 &section, const std::string &key, void DataStore::setInt(const std::string &section, const std::string &key,
int value) { int value) {
impl_->ini.SetLongValue(section.c_str(), key.c_str(), value); impl_->ini.SetLongValue(section.c_str(), key.c_str(), value);
dirty_ = true;
if (!inTransaction_ && !filename_.empty()) {
save("");
}
} }
void DataStore::setFloat(const std::string &section, const std::string &key, void DataStore::setFloat(const std::string &section, const std::string &key,
float value) { float value) {
impl_->ini.SetValue(section.c_str(), key.c_str(), impl_->ini.SetValue(section.c_str(), key.c_str(),
std::to_string(value).c_str()); std::to_string(value).c_str());
dirty_ = true;
if (!inTransaction_ && !filename_.empty()) {
save("");
}
} }
void DataStore::setBool(const std::string &section, const std::string &key, void DataStore::setBool(const std::string &section, const std::string &key,
bool value) { bool value) {
impl_->ini.SetBoolValue(section.c_str(), key.c_str(), value); impl_->ini.SetBoolValue(section.c_str(), key.c_str(), value);
dirty_ = true;
if (!inTransaction_ && !filename_.empty()) {
save("");
}
} }
void DataStore::removeKey(const std::string &section, const std::string &key) { void DataStore::removeKey(const std::string &section, const std::string &key) {
impl_->ini.Delete(section.c_str(), key.c_str()); impl_->ini.Delete(section.c_str(), key.c_str());
dirty_ = true;
if (!inTransaction_ && !filename_.empty()) {
save("");
}
} }
void DataStore::removeSection(const std::string &section) { void DataStore::removeSection(const std::string &section) {
impl_->ini.Delete(section.c_str(), nullptr); impl_->ini.Delete(section.c_str(), nullptr);
dirty_ = true;
if (!inTransaction_ && !filename_.empty()) {
save("");
}
} }
bool DataStore::hasKey(const std::string &section, const std::string &key) { bool DataStore::hasKey(const std::string &section, const std::string &key) {
@ -94,6 +314,131 @@ bool DataStore::hasSection(const std::string &section) {
return impl_->ini.GetSection(section.c_str()) != nullptr; 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<std::string> DataStore::getAllSections() const {
std::vector<std::string> sections;
CSimpleIniA::TNamesDepend sectionList;
impl_->ini.GetAllSections(sectionList);
for (const auto &section : sectionList) {
sections.emplace_back(section.pItem);
}
return sections;
}
std::vector<std::string> DataStore::getAllKeys(const std::string &section) const {
std::vector<std::string> 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 } // namespace extra2d

822
docs/DataStore.md Normal file
View File

@ -0,0 +1,822 @@
# DataStore 数据持久化系统
## 概述
`DataStore` 是 Easy2D 引擎的数据持久化类,提供 INI 文件格式的数据存储功能,并支持 Nintendo Switch 平台的官方存档系统。
## 特性
- ✅ INI 文件格式读写
- ✅ Nintendo Switch 官方存档系统支持
- ✅ 多用户存档隔离
- ✅ 事务支持(批量操作 + 回滚)
- ✅ 自动保存和脏数据检测
- ✅ 跨平台兼容Switch/PC
---
## 快速开始
### 基本使用
```cpp
#include <extra2d/extra2d.h>
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` - 用户IDAccount 类型需要,为空则自动获取当前用户)
- `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 &section,
const std::string &key,
const std::string &defaultValue = ""
);
```
获取字符串值。
**参数:**
- `section` - 节名称
- `key` - 键名称
- `defaultValue` - 默认值(键不存在时返回)
**返回值:**
- 字符串值
---
#### getInt
```cpp
int getInt(
const std::string &section,
const std::string &key,
int defaultValue = 0
);
```
获取整数值。
---
#### getFloat
```cpp
float getFloat(
const std::string &section,
const std::string &key,
float defaultValue = 0.0f
);
```
获取浮点数值。
---
#### getBool
```cpp
bool getBool(
const std::string &section,
const std::string &key,
bool defaultValue = false
);
```
获取布尔值。
**说明:**
- 支持 "true"/"false"、"yes"/"no"、"1"/"0" 等格式
---
#### setString
```cpp
void setString(
const std::string &section,
const std::string &key,
const std::string &value
);
```
设置字符串值。
**说明:**
- 自动标记为脏数据
- 事务外自动保存
---
#### setInt
```cpp
void setInt(
const std::string &section,
const std::string &key,
int value
);
```
设置整数值。
---
#### setFloat
```cpp
void setFloat(
const std::string &section,
const std::string &key,
float value
);
```
设置浮点数值。
---
#### setBool
```cpp
void setBool(
const std::string &section,
const std::string &key,
bool value
);
```
设置布尔值。
---
#### removeKey
```cpp
void removeKey(const std::string &section, const std::string &key);
```
删除键。
---
#### removeSection
```cpp
void removeSection(const std::string &section);
```
删除整个 section。
---
#### hasKey
```cpp
bool hasKey(const std::string &section, const std::string &key);
```
检查键是否存在。
---
#### hasSection
```cpp
bool hasSection(const std::string &section);
```
检查 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<std::string> getAllSections() const;
```
获取所有 section 名称。
**返回值:**
- section 名称列表
---
#### getAllKeys
```cpp
std::vector<std::string> getAllKeys(const std::string &section) 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 <extra2d/extra2d.h>
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) - 实现文件