feat(DataStore): 添加Switch存档系统支持并增强功能
- 新增Switch平台存档挂载、卸载和提交功能 - 添加用户账户集成支持多用户存档隔离 - 实现事务支持包括开始、提交和回滚操作 - 增加自动保存和脏数据检测机制 - 添加工具方法如获取所有sections/keys - 完善文档说明和示例代码
This commit is contained in:
parent
82c44b966f
commit
5880159991
|
|
@ -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)
|
||||||
|
|
@ -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 用户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,
|
std::string getString(const std::string §ion, 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 §ion) 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
|
||||||
|
|
|
||||||
|
|
@ -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 §ion,
|
std::string DataStore::getString(const std::string §ion,
|
||||||
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 §ion, 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 §ion, const std::string &key,
|
||||||
void DataStore::setString(const std::string §ion, const std::string &key,
|
void DataStore::setString(const std::string §ion, 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 §ion, const std::string &key,
|
void DataStore::setInt(const std::string §ion, 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 §ion, const std::string &key,
|
void DataStore::setFloat(const std::string §ion, 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 §ion, const std::string &key,
|
void DataStore::setBool(const std::string §ion, 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 §ion, const std::string &key) {
|
void DataStore::removeKey(const std::string §ion, 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 §ion) {
|
void DataStore::removeSection(const std::string §ion) {
|
||||||
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 §ion, const std::string &key) {
|
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;
|
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 §ion : sectionList) {
|
||||||
|
sections.emplace_back(section.pItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
return sections;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> DataStore::getAllKeys(const std::string §ion) 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
|
||||||
|
|
|
||||||
|
|
@ -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` - 用户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<std::string> getAllSections() const;
|
||||||
|
```
|
||||||
|
|
||||||
|
获取所有 section 名称。
|
||||||
|
|
||||||
|
**返回值:**
|
||||||
|
- section 名称列表
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### getAllKeys
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
std::vector<std::string> 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 <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) - 实现文件
|
||||||
Loading…
Reference in New Issue