Extra2D/docs/DataStore.md

823 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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) - 实现文件