Compare commits
3 Commits
137369c37c
...
f46978fc31
| Author | SHA1 | Date |
|---|---|---|
|
|
f46978fc31 | |
|
|
60191063d2 | |
|
|
8e9fe8719f |
|
|
@ -8,16 +8,14 @@
|
||||||
#include <extra2d/core/math_types.h>
|
#include <extra2d/core/math_types.h>
|
||||||
#include <extra2d/core/module.h>
|
#include <extra2d/core/module.h>
|
||||||
#include <extra2d/core/registry.h>
|
#include <extra2d/core/registry.h>
|
||||||
#include <extra2d/core/string.h>
|
|
||||||
#include <extra2d/core/types.h>
|
#include <extra2d/core/types.h>
|
||||||
|
|
||||||
// Platform
|
// Window
|
||||||
#include <extra2d/platform/glfw/glfw_window.h>
|
#include <extra2d/window/keys.h>
|
||||||
#include <extra2d/platform/keys.h>
|
#include <extra2d/window/window.h>
|
||||||
#include <extra2d/platform/window_module.h>
|
#include <extra2d/window/event_converter.h>
|
||||||
|
|
||||||
// Window - SDL2 + OpenGL
|
// Window - SDL2 + OpenGL
|
||||||
#include <extra2d/window/window.h>
|
|
||||||
|
|
||||||
// Render (OpenGL 4.5)
|
// Render (OpenGL 4.5)
|
||||||
#include <extra2d/render/buffer.h>
|
#include <extra2d/render/buffer.h>
|
||||||
|
|
@ -42,15 +40,14 @@
|
||||||
#include <extra2d/event/event.h>
|
#include <extra2d/event/event.h>
|
||||||
#include <extra2d/event/event_dispatcher.h>
|
#include <extra2d/event/event_dispatcher.h>
|
||||||
#include <extra2d/event/event_queue.h>
|
#include <extra2d/event/event_queue.h>
|
||||||
#include <extra2d/event/input_codes.h>
|
|
||||||
|
|
||||||
// Audio
|
// Audio
|
||||||
#include <extra2d/audio/audio_engine.h>
|
#include <extra2d/audio/audio_engine.h>
|
||||||
#include <extra2d/audio/sound.h>
|
#include <extra2d/audio/sound.h>
|
||||||
|
|
||||||
// Utils
|
// Utils
|
||||||
#include <extra2d/utils/data.h>
|
|
||||||
#include <extra2d/utils/random.h>
|
#include <extra2d/utils/random.h>
|
||||||
|
#include <extra2d/utils/save_store.h>
|
||||||
#include <extra2d/utils/timer.h>
|
#include <extra2d/utils/timer.h>
|
||||||
|
|
||||||
// Spatial
|
// Spatial
|
||||||
|
|
|
||||||
|
|
@ -1,216 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <extra2d/core/types.h>
|
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 存档类型枚举
|
|
||||||
// ============================================================================
|
|
||||||
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 = "");
|
|
||||||
|
|
||||||
/// 获取整数值
|
|
||||||
int getInt(const std::string §ion, const std::string &key,
|
|
||||||
int defaultValue = 0);
|
|
||||||
|
|
||||||
/// 获取浮点数值
|
|
||||||
float getFloat(const std::string §ion, const std::string &key,
|
|
||||||
float defaultValue = 0.0f);
|
|
||||||
|
|
||||||
/// 获取布尔值
|
|
||||||
bool getBool(const std::string §ion, const std::string &key,
|
|
||||||
bool defaultValue = false);
|
|
||||||
|
|
||||||
/// 设置字符串值
|
|
||||||
void setString(const std::string §ion, const std::string &key,
|
|
||||||
const std::string &value);
|
|
||||||
|
|
||||||
/// 设置整数值
|
|
||||||
void setInt(const std::string §ion, const std::string &key, int value);
|
|
||||||
|
|
||||||
/// 设置浮点数值
|
|
||||||
void setFloat(const std::string §ion, const std::string &key,
|
|
||||||
float value);
|
|
||||||
|
|
||||||
/// 设置布尔值
|
|
||||||
void setBool(const std::string §ion, const std::string &key, bool value);
|
|
||||||
|
|
||||||
/// 删除键
|
|
||||||
void removeKey(const std::string §ion, const std::string &key);
|
|
||||||
|
|
||||||
/// 删除整个 section
|
|
||||||
void removeSection(const std::string §ion);
|
|
||||||
|
|
||||||
/// 检查键是否存在
|
|
||||||
bool hasKey(const std::string §ion, const std::string &key);
|
|
||||||
|
|
||||||
/// 检查 section 是否存在
|
|
||||||
bool hasSection(const std::string §ion);
|
|
||||||
|
|
||||||
/// 清除所有数据
|
|
||||||
void clear();
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 事务支持
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @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:
|
|
||||||
class Impl;
|
|
||||||
Unique<Impl> 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
|
|
||||||
|
|
@ -0,0 +1,125 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <extra2d/core/types.h>
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 存储策略接口
|
||||||
|
// ============================================================================
|
||||||
|
class ISaveStrategy {
|
||||||
|
public:
|
||||||
|
virtual ~ISaveStrategy() = default;
|
||||||
|
|
||||||
|
// 读取原始数据
|
||||||
|
virtual bool read(const std::string& path, std::vector<u8>& data) = 0;
|
||||||
|
|
||||||
|
// 写入原始数据
|
||||||
|
virtual bool write(const std::string& path, const std::vector<u8>& data) = 0;
|
||||||
|
|
||||||
|
// 提交更改(Switch存档需要)
|
||||||
|
virtual bool commit() { return true; }
|
||||||
|
|
||||||
|
// 是否可用
|
||||||
|
virtual bool valid() const = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 文件存储策略
|
||||||
|
// ============================================================================
|
||||||
|
class FileSaveStrategy : public ISaveStrategy {
|
||||||
|
public:
|
||||||
|
bool read(const std::string& path, std::vector<u8>& data) override;
|
||||||
|
bool write(const std::string& path, const std::vector<u8>& data) override;
|
||||||
|
bool valid() const override { return true; }
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Switch存档策略(仅Switch平台)
|
||||||
|
// ============================================================================
|
||||||
|
#ifdef __SWITCH__
|
||||||
|
class SwitchSaveStrategy : public ISaveStrategy {
|
||||||
|
public:
|
||||||
|
SwitchSaveStrategy();
|
||||||
|
~SwitchSaveStrategy();
|
||||||
|
|
||||||
|
bool mount(const std::string& mountName);
|
||||||
|
void unmount();
|
||||||
|
|
||||||
|
bool read(const std::string& path, std::vector<u8>& data) override;
|
||||||
|
bool write(const std::string& path, const std::vector<u8>& data) override;
|
||||||
|
bool commit() override;
|
||||||
|
bool valid() const override { return mounted_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string mountName_;
|
||||||
|
bool mounted_ = false;
|
||||||
|
};
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 存档数据(内存表示)
|
||||||
|
// ============================================================================
|
||||||
|
struct SaveData {
|
||||||
|
std::unordered_map<std::string, std::unordered_map<std::string, std::string>> sections;
|
||||||
|
|
||||||
|
void set(const std::string& section, const std::string& key, const std::string& value);
|
||||||
|
std::string get(const std::string& section, const std::string& key, const std::string& defaultValue = "");
|
||||||
|
bool has(const std::string& section, const std::string& key);
|
||||||
|
void remove(const std::string& section, const std::string& key);
|
||||||
|
void clear();
|
||||||
|
|
||||||
|
// 序列化/反序列化
|
||||||
|
std::vector<u8> serialize() const;
|
||||||
|
bool deserialize(const std::vector<u8>& data);
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 存档管理器
|
||||||
|
// ============================================================================
|
||||||
|
class SaveStore {
|
||||||
|
public:
|
||||||
|
SaveStore();
|
||||||
|
~SaveStore();
|
||||||
|
|
||||||
|
// 设置存储策略
|
||||||
|
void setStrategy(std::unique_ptr<ISaveStrategy> strategy);
|
||||||
|
|
||||||
|
// 加载/保存
|
||||||
|
bool load(const std::string& path);
|
||||||
|
bool save(const std::string& path = "");
|
||||||
|
|
||||||
|
// 数据操作
|
||||||
|
void set(const std::string& section, const std::string& key, const std::string& value);
|
||||||
|
void setInt(const std::string& section, const std::string& key, int value);
|
||||||
|
void setFloat(const std::string& section, const std::string& key, float value);
|
||||||
|
void setBool(const std::string& section, const std::string& key, bool value);
|
||||||
|
|
||||||
|
std::string get(const std::string& section, const std::string& key, const std::string& defaultValue = "");
|
||||||
|
int getInt(const std::string& section, const std::string& key, int defaultValue = 0);
|
||||||
|
float getFloat(const std::string& section, const std::string& key, float defaultValue = 0.0f);
|
||||||
|
bool getBool(const std::string& section, const std::string& key, bool defaultValue = false);
|
||||||
|
|
||||||
|
bool has(const std::string& section, const std::string& key);
|
||||||
|
void remove(const std::string& section, const std::string& key);
|
||||||
|
void clear();
|
||||||
|
|
||||||
|
// 事务
|
||||||
|
void begin();
|
||||||
|
bool commit();
|
||||||
|
void rollback();
|
||||||
|
|
||||||
|
private:
|
||||||
|
SaveData data_;
|
||||||
|
SaveData backup_; // 事务备份
|
||||||
|
std::unique_ptr<ISaveStrategy> strategy_;
|
||||||
|
std::string path_;
|
||||||
|
bool inTransaction_ = false;
|
||||||
|
bool dirty_ = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -9,47 +9,47 @@
|
||||||
namespace extra2d {
|
namespace extra2d {
|
||||||
|
|
||||||
struct WindowConfig {
|
struct WindowConfig {
|
||||||
std::string title = "Extra2D Application";
|
std::string title = "Extra2D Application";
|
||||||
int width = 1280;
|
int width = 1280;
|
||||||
int height = 720;
|
int height = 720;
|
||||||
bool fullscreen = true;
|
bool fullscreen = false;
|
||||||
bool resizable = false;
|
bool resizable = true;
|
||||||
bool centerWindow = true;
|
bool centerWindow = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
class Window {
|
class Window {
|
||||||
public:
|
public:
|
||||||
using OnEvent = std::function<void(const SDL_Event&)>;
|
using OnEvent = std::function<void(const SDL_Event &)>;
|
||||||
|
|
||||||
Window();
|
Window();
|
||||||
~Window();
|
~Window();
|
||||||
|
|
||||||
bool create(const WindowConfig& config);
|
bool create(const WindowConfig &config);
|
||||||
void destroy();
|
void destroy();
|
||||||
|
|
||||||
void poll();
|
void poll();
|
||||||
bool shouldClose() const;
|
bool shouldClose() const;
|
||||||
void close();
|
void close();
|
||||||
|
|
||||||
void setTitle(const std::string& title);
|
void setTitle(const std::string &title);
|
||||||
void setSize(int width, int height);
|
void setSize(int width, int height);
|
||||||
void setFullscreen(bool fullscreen);
|
void setFullscreen(bool fullscreen);
|
||||||
|
|
||||||
int width() const { return width_; }
|
int width() const { return width_; }
|
||||||
int height() const { return height_; }
|
int height() const { return height_; }
|
||||||
bool fullscreen() const { return fullscreen_; }
|
bool fullscreen() const { return fullscreen_; }
|
||||||
|
|
||||||
void onEvent(OnEvent cb) { onEvent_ = cb; }
|
void onEvent(OnEvent cb) { onEvent_ = cb; }
|
||||||
|
|
||||||
SDL_Window* native() const { return sdlWindow_; }
|
SDL_Window *native() const { return sdlWindow_; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
SDL_Window* sdlWindow_ = nullptr;
|
SDL_Window *sdlWindow_ = nullptr;
|
||||||
int width_ = 1280;
|
int width_ = 1280;
|
||||||
int height_ = 720;
|
int height_ = 720;
|
||||||
bool fullscreen_ = true;
|
bool fullscreen_ = true;
|
||||||
bool shouldClose_ = false;
|
bool shouldClose_ = false;
|
||||||
OnEvent onEvent_;
|
OnEvent onEvent_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace extra2d
|
} // namespace extra2d
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,446 +0,0 @@
|
||||||
#include <extra2d/services/logger_service.h>
|
|
||||||
#include <extra2d/utils/data.h>
|
|
||||||
#include <simpleini/SimpleIni.h>
|
|
||||||
|
|
||||||
// Switch 平台特定头文件
|
|
||||||
#ifdef __SWITCH__
|
|
||||||
#include <switch.h>
|
|
||||||
#include <switch/services/fs.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
class DataStore::Impl {
|
|
||||||
public:
|
|
||||||
CSimpleIniA ini;
|
|
||||||
};
|
|
||||||
|
|
||||||
DataStore::DataStore() : impl_(ptr::unique<Impl>()) {}
|
|
||||||
|
|
||||||
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_ERROR(CAT_APP, "DataStore::save: 没有指定文件名");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return internalSave(targetFile);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DataStore::internalSave(const std::string &filename) {
|
|
||||||
SI_Error rc = impl_->ini.SaveFile(filename.c_str());
|
|
||||||
if (rc < 0) {
|
|
||||||
E2D_ERROR(CAT_DATA, "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_ERROR(CAT_DATA, "DataStore::mountSaveData: 无法获取当前用户ID");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 使用 fsdevMountSaveData 挂载
|
|
||||||
// 注意:这里使用当前应用程序ID (0 表示当前应用)
|
|
||||||
u64 applicationId = 0;
|
|
||||||
rc = fsdevMountSaveData(mountName.c_str(), applicationId, uid);
|
|
||||||
|
|
||||||
if (R_FAILED(rc)) {
|
|
||||||
E2D_ERROR(CAT_DATA, "DataStore::mountSaveData: 挂载失败: 0x{:X}", rc);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
mountName_ = mountName;
|
|
||||||
saveDataMounted_ = true;
|
|
||||||
defaultUserId_ = UserId{uid.uid[0], uid.uid[1]};
|
|
||||||
|
|
||||||
E2D_INFO(CAT_DATA, "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_INFO("DataStore::unmountSaveData: 已卸载存档");
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DataStore::commitSaveData(const std::string &mountName) {
|
|
||||||
if (!saveDataMounted_) {
|
|
||||||
E2D_WARN(CAT_DATA, "DataStore::commitSaveData: 存档未挂载");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Result rc = fsdevCommitDevice(mountName.c_str());
|
|
||||||
if (R_FAILED(rc)) {
|
|
||||||
E2D_ERROR("DataStore::commitSaveData: 提交失败: 0x{:X}", rc);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
E2D_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_ERROR(CAT_DATA,
|
|
||||||
"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_DEBUG("DataStore::getCurrentUserId: 获取成功: 0x{:X}{:X}",
|
|
||||||
result.uid[1], result.uid[0]);
|
|
||||||
} else {
|
|
||||||
E2D_ERROR(CAT_DATA, "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_WARN(CAT_DATA, "非 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) {
|
|
||||||
const char *value =
|
|
||||||
impl_->ini.GetValue(section.c_str(), key.c_str(), defaultValue.c_str());
|
|
||||||
return value ? value : defaultValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
int DataStore::getInt(const std::string §ion, const std::string &key,
|
|
||||||
int defaultValue) {
|
|
||||||
return static_cast<int>(
|
|
||||||
impl_->ini.GetLongValue(section.c_str(), key.c_str(), defaultValue));
|
|
||||||
}
|
|
||||||
|
|
||||||
float DataStore::getFloat(const std::string §ion, const std::string &key,
|
|
||||||
float defaultValue) {
|
|
||||||
const char *value =
|
|
||||||
impl_->ini.GetValue(section.c_str(), key.c_str(), nullptr);
|
|
||||||
if (value) {
|
|
||||||
try {
|
|
||||||
return std::stof(value);
|
|
||||||
} catch (...) {
|
|
||||||
return defaultValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return defaultValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DataStore::getBool(const std::string §ion, const std::string &key,
|
|
||||||
bool defaultValue) {
|
|
||||||
return impl_->ini.GetBoolValue(section.c_str(), key.c_str(), defaultValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
|
||||||
return impl_->ini.GetValue(section.c_str(), key.c_str(), nullptr) != nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DataStore::hasSection(const std::string §ion) {
|
|
||||||
return impl_->ini.GetSection(section.c_str()) != nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
void DataStore::clear() {
|
|
||||||
impl_->ini.Reset();
|
|
||||||
dirty_ = true;
|
|
||||||
|
|
||||||
if (!inTransaction_ && !filename_.empty()) {
|
|
||||||
save("");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 事务支持
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
void DataStore::beginTransaction() {
|
|
||||||
if (inTransaction_) {
|
|
||||||
E2D_WARN(CAT_DATA, "DataStore::beginTransaction: 已经处于事务中");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
inTransaction_ = true;
|
|
||||||
dirty_ = false;
|
|
||||||
|
|
||||||
E2D_DEBUG(CAT_DATA, "DataStore::beginTransaction: 事务开始");
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DataStore::commit() {
|
|
||||||
if (!inTransaction_) {
|
|
||||||
E2D_WARN(CAT_DATA, "DataStore::commit: 不在事务中");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果有文件名,写入文件
|
|
||||||
bool result = true;
|
|
||||||
if (!filename_.empty() && dirty_) {
|
|
||||||
result = internalSave(filename_);
|
|
||||||
|
|
||||||
// 如果挂载了存档,提交更改
|
|
||||||
if (result && saveDataMounted_) {
|
|
||||||
result = commitSaveData(mountName_);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
inTransaction_ = false;
|
|
||||||
|
|
||||||
E2D_DEBUG(CAT_DATA, "DataStore::commit: 事务提交 {}",
|
|
||||||
result ? "成功" : "失败");
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
void DataStore::rollback() {
|
|
||||||
if (!inTransaction_) {
|
|
||||||
E2D_WARN(CAT_DATA, "DataStore::rollback: 不在事务中");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 重新加载文件来恢复数据
|
|
||||||
if (!filename_.empty()) {
|
|
||||||
impl_->ini.Reset();
|
|
||||||
SI_Error rc = impl_->ini.LoadFile(filename_.c_str());
|
|
||||||
if (rc < 0) {
|
|
||||||
E2D_ERROR(CAT_DATA, "DataStore::rollback: 重新加载文件失败: {}",
|
|
||||||
filename_);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// 如果没有文件名,清空数据
|
|
||||||
impl_->ini.Reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
inTransaction_ = false;
|
|
||||||
dirty_ = false;
|
|
||||||
|
|
||||||
E2D_DEBUG(CAT_DATA, "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_ERROR(CAT_DATA, "DataStore::loadFromSave: 存档未挂载");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string fullPath = getSaveDataPath(path);
|
|
||||||
return load(fullPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DataStore::saveToSave(const std::string &path) {
|
|
||||||
if (!saveDataMounted_) {
|
|
||||||
E2D_ERROR(CAT_DATA, "DataStore::saveToSave: 存档未挂载");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string fullPath = getSaveDataPath(path);
|
|
||||||
bool result = save(fullPath);
|
|
||||||
|
|
||||||
// 自动提交
|
|
||||||
if (result) {
|
|
||||||
result = commitSaveData(mountName_);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -0,0 +1,459 @@
|
||||||
|
#include <extra2d/utils/save_store.h>
|
||||||
|
#include <extra2d/services/logger_service.h>
|
||||||
|
#include <cstring>
|
||||||
|
#include <fstream>
|
||||||
|
|
||||||
|
// Switch 平台特定头文件
|
||||||
|
#ifdef __SWITCH__
|
||||||
|
#include <switch.h>
|
||||||
|
#include <switch/services/fs.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 二进制序列化辅助函数
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
static void writeU32(std::vector<u8>& data, u32 value) {
|
||||||
|
data.push_back((value >> 0) & 0xFF);
|
||||||
|
data.push_back((value >> 8) & 0xFF);
|
||||||
|
data.push_back((value >> 16) & 0xFF);
|
||||||
|
data.push_back((value >> 24) & 0xFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
static u32 readU32(const u8* data, size_t& offset) {
|
||||||
|
u32 value = data[offset] | (data[offset + 1] << 8) |
|
||||||
|
(data[offset + 2] << 16) | (data[offset + 3] << 24);
|
||||||
|
offset += 4;
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void writeString(std::vector<u8>& data, const std::string& str) {
|
||||||
|
writeU32(data, static_cast<u32>(str.size()));
|
||||||
|
data.insert(data.end(), str.begin(), str.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string readString(const u8* data, size_t& offset) {
|
||||||
|
u32 len = readU32(data, offset);
|
||||||
|
std::string str(reinterpret_cast<const char*>(data + offset), len);
|
||||||
|
offset += len;
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// FileSaveStrategy 实现
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
bool FileSaveStrategy::read(const std::string& path, std::vector<u8>& data) {
|
||||||
|
std::ifstream file(path, std::ios::binary | std::ios::ate);
|
||||||
|
if (!file.is_open()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::streamsize size = file.tellg();
|
||||||
|
file.seekg(0, std::ios::beg);
|
||||||
|
|
||||||
|
data.resize(size);
|
||||||
|
if (!file.read(reinterpret_cast<char*>(data.data()), size)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FileSaveStrategy::write(const std::string& path, const std::vector<u8>& data) {
|
||||||
|
std::ofstream file(path, std::ios::binary);
|
||||||
|
if (!file.is_open()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return file.write(reinterpret_cast<const char*>(data.data()), data.size()).good();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// SwitchSaveStrategy 实现(仅Switch平台)
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
#ifdef __SWITCH__
|
||||||
|
|
||||||
|
SwitchSaveStrategy::SwitchSaveStrategy() = default;
|
||||||
|
|
||||||
|
SwitchSaveStrategy::~SwitchSaveStrategy() {
|
||||||
|
if (mounted_) {
|
||||||
|
unmount();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SwitchSaveStrategy::mount(const std::string& mountName) {
|
||||||
|
if (mounted_) {
|
||||||
|
unmount();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取当前用户ID
|
||||||
|
AccountUid uid;
|
||||||
|
Result rc = accountInitialize(AccountServiceType_Application);
|
||||||
|
if (R_FAILED(rc)) {
|
||||||
|
E2D_ERROR(CAT_DATA, "SwitchSaveStrategy::mount: accountInitialize 失败: 0x{:X}", rc);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
rc = accountGetPreselectedUser(&uid);
|
||||||
|
accountExit();
|
||||||
|
|
||||||
|
if (R_FAILED(rc)) {
|
||||||
|
E2D_ERROR(CAT_DATA, "SwitchSaveStrategy::mount: 获取用户ID失败: 0x{:X}", rc);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 挂载存档
|
||||||
|
rc = fsdevMountSaveData(mountName.c_str(), 0, uid);
|
||||||
|
if (R_FAILED(rc)) {
|
||||||
|
E2D_ERROR(CAT_DATA, "SwitchSaveStrategy::mount: 挂载失败: 0x{:X}", rc);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
mountName_ = mountName;
|
||||||
|
mounted_ = true;
|
||||||
|
E2D_INFO(CAT_DATA, "SwitchSaveStrategy::mount: 成功挂载存档: {}", mountName);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SwitchSaveStrategy::unmount() {
|
||||||
|
if (!mounted_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fsdevUnmountDevice(mountName_.c_str());
|
||||||
|
mounted_ = false;
|
||||||
|
mountName_.clear();
|
||||||
|
E2D_INFO(CAT_DATA, "SwitchSaveStrategy::unmount: 已卸载存档");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SwitchSaveStrategy::read(const std::string& path, std::vector<u8>& data) {
|
||||||
|
if (!mounted_) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string fullPath = mountName_ + ":/" + path;
|
||||||
|
return FileSaveStrategy().read(fullPath, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SwitchSaveStrategy::write(const std::string& path, const std::vector<u8>& data) {
|
||||||
|
if (!mounted_) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string fullPath = mountName_ + ":/" + path;
|
||||||
|
return FileSaveStrategy().write(fullPath, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SwitchSaveStrategy::commit() {
|
||||||
|
if (!mounted_) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result rc = fsdevCommitDevice(mountName_.c_str());
|
||||||
|
if (R_FAILED(rc)) {
|
||||||
|
E2D_ERROR(CAT_DATA, "SwitchSaveStrategy::commit: 提交失败: 0x{:X}", rc);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
E2D_DEBUG(CAT_DATA, "SwitchSaveStrategy::commit: 提交成功");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// SaveData 实现
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
void SaveData::set(const std::string& section, const std::string& key, const std::string& value) {
|
||||||
|
sections[section][key] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string SaveData::get(const std::string& section, const std::string& key, const std::string& defaultValue) {
|
||||||
|
auto secIt = sections.find(section);
|
||||||
|
if (secIt == sections.end()) {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
auto keyIt = secIt->second.find(key);
|
||||||
|
if (keyIt == secIt->second.end()) {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
return keyIt->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SaveData::has(const std::string& section, const std::string& key) {
|
||||||
|
auto secIt = sections.find(section);
|
||||||
|
if (secIt == sections.end()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return secIt->second.find(key) != secIt->second.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SaveData::remove(const std::string& section, const std::string& key) {
|
||||||
|
auto secIt = sections.find(section);
|
||||||
|
if (secIt != sections.end()) {
|
||||||
|
secIt->second.erase(key);
|
||||||
|
if (secIt->second.empty()) {
|
||||||
|
sections.erase(secIt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SaveData::clear() {
|
||||||
|
sections.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 二进制格式:
|
||||||
|
// [魔数: 4字节 "E2SD"]
|
||||||
|
// [版本: 4字节]
|
||||||
|
// [section数量: 4字节]
|
||||||
|
// [section名长度: 4字节] [section名: N字节]
|
||||||
|
// [key-value对数量: 4字节]
|
||||||
|
// [key长度: 4字节] [key: N字节] [value长度: 4字节] [value: N字节]
|
||||||
|
|
||||||
|
static const char MAGIC[4] = {'E', '2', 'S', 'D'};
|
||||||
|
static const u32 VERSION = 1;
|
||||||
|
|
||||||
|
std::vector<u8> SaveData::serialize() const {
|
||||||
|
std::vector<u8> data;
|
||||||
|
|
||||||
|
// 魔数
|
||||||
|
data.insert(data.end(), MAGIC, MAGIC + 4);
|
||||||
|
|
||||||
|
// 版本
|
||||||
|
writeU32(data, VERSION);
|
||||||
|
|
||||||
|
// section数量
|
||||||
|
writeU32(data, static_cast<u32>(sections.size()));
|
||||||
|
|
||||||
|
// 写入每个section
|
||||||
|
for (const auto& [sectionName, keys] : sections) {
|
||||||
|
writeString(data, sectionName);
|
||||||
|
writeU32(data, static_cast<u32>(keys.size()));
|
||||||
|
|
||||||
|
for (const auto& [key, value] : keys) {
|
||||||
|
writeString(data, key);
|
||||||
|
writeString(data, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SaveData::deserialize(const std::vector<u8>& data) {
|
||||||
|
if (data.size() < 12) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t offset = 0;
|
||||||
|
|
||||||
|
// 检查魔数
|
||||||
|
if (std::memcmp(data.data(), MAGIC, 4) != 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
offset += 4;
|
||||||
|
|
||||||
|
// 检查版本
|
||||||
|
u32 version = readU32(data.data(), offset);
|
||||||
|
if (version != VERSION) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 读取section数量
|
||||||
|
u32 sectionCount = readU32(data.data(), offset);
|
||||||
|
|
||||||
|
sections.clear();
|
||||||
|
|
||||||
|
for (u32 i = 0; i < sectionCount; ++i) {
|
||||||
|
std::string sectionName = readString(data.data(), offset);
|
||||||
|
u32 keyCount = readU32(data.data(), offset);
|
||||||
|
|
||||||
|
for (u32 j = 0; j < keyCount; ++j) {
|
||||||
|
std::string key = readString(data.data(), offset);
|
||||||
|
std::string value = readString(data.data(), offset);
|
||||||
|
sections[sectionName][key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// SaveStore 实现
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
SaveStore::SaveStore() = default;
|
||||||
|
|
||||||
|
SaveStore::~SaveStore() {
|
||||||
|
if (inTransaction_ && dirty_) {
|
||||||
|
commit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SaveStore::setStrategy(std::unique_ptr<ISaveStrategy> strategy) {
|
||||||
|
strategy_ = std::move(strategy);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SaveStore::load(const std::string& path) {
|
||||||
|
if (!strategy_ || !strategy_->valid()) {
|
||||||
|
E2D_ERROR(CAT_DATA, "SaveStore::load: 存储策略未设置或无效");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<u8> rawData;
|
||||||
|
if (!strategy_->read(path, rawData)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!data_.deserialize(rawData)) {
|
||||||
|
E2D_ERROR(CAT_DATA, "SaveStore::load: 反序列化失败");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
path_ = path;
|
||||||
|
dirty_ = false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SaveStore::save(const std::string& path) {
|
||||||
|
if (!strategy_ || !strategy_->valid()) {
|
||||||
|
E2D_ERROR(CAT_DATA, "SaveStore::save: 存储策略未设置或无效");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inTransaction_) {
|
||||||
|
dirty_ = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string& targetPath = path.empty() ? path_ : path;
|
||||||
|
if (targetPath.empty()) {
|
||||||
|
E2D_ERROR(CAT_DATA, "SaveStore::save: 未指定路径");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<u8> rawData = data_.serialize();
|
||||||
|
if (!strategy_->write(targetPath, rawData)) {
|
||||||
|
E2D_ERROR(CAT_DATA, "SaveStore::save: 写入失败");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!strategy_->commit()) {
|
||||||
|
E2D_ERROR(CAT_DATA, "SaveStore::save: 提交失败");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
dirty_ = false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SaveStore::set(const std::string& section, const std::string& key, const std::string& value) {
|
||||||
|
data_.set(section, key, value);
|
||||||
|
dirty_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SaveStore::setInt(const std::string& section, const std::string& key, int value) {
|
||||||
|
set(section, key, std::to_string(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
void SaveStore::setFloat(const std::string& section, const std::string& key, float value) {
|
||||||
|
set(section, key, std::to_string(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
void SaveStore::setBool(const std::string& section, const std::string& key, bool value) {
|
||||||
|
set(section, key, value ? "1" : "0");
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string SaveStore::get(const std::string& section, const std::string& key, const std::string& defaultValue) {
|
||||||
|
return data_.get(section, key, defaultValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
int SaveStore::getInt(const std::string& section, const std::string& key, int defaultValue) {
|
||||||
|
std::string value = get(section, key);
|
||||||
|
if (value.empty()) {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return std::stoi(value);
|
||||||
|
} catch (...) {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
float SaveStore::getFloat(const std::string& section, const std::string& key, float defaultValue) {
|
||||||
|
std::string value = get(section, key);
|
||||||
|
if (value.empty()) {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return std::stof(value);
|
||||||
|
} catch (...) {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SaveStore::getBool(const std::string& section, const std::string& key, bool defaultValue) {
|
||||||
|
std::string value = get(section, key);
|
||||||
|
if (value.empty()) {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
return value == "1" || value == "true" || value == "yes";
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SaveStore::has(const std::string& section, const std::string& key) {
|
||||||
|
return data_.has(section, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SaveStore::remove(const std::string& section, const std::string& key) {
|
||||||
|
data_.remove(section, key);
|
||||||
|
dirty_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SaveStore::clear() {
|
||||||
|
data_.clear();
|
||||||
|
dirty_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SaveStore::begin() {
|
||||||
|
if (inTransaction_) {
|
||||||
|
E2D_WARN(CAT_DATA, "SaveStore::begin: 已经处于事务中");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
backup_ = data_;
|
||||||
|
inTransaction_ = true;
|
||||||
|
dirty_ = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SaveStore::commit() {
|
||||||
|
if (!inTransaction_) {
|
||||||
|
E2D_WARN(CAT_DATA, "SaveStore::commit: 不在事务中");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool result = true;
|
||||||
|
if (dirty_ && !path_.empty()) {
|
||||||
|
result = save(path_);
|
||||||
|
}
|
||||||
|
|
||||||
|
inTransaction_ = false;
|
||||||
|
backup_.clear();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SaveStore::rollback() {
|
||||||
|
if (!inTransaction_) {
|
||||||
|
E2D_WARN(CAT_DATA, "SaveStore::rollback: 不在事务中");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
data_ = backup_;
|
||||||
|
inTransaction_ = false;
|
||||||
|
dirty_ = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -3,122 +3,50 @@
|
||||||
using namespace extra2d;
|
using namespace extra2d;
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Hello World 场景
|
// Hello World 示例
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Hello World 场景类
|
|
||||||
* 显示简单的 "Hello World" 文字
|
|
||||||
*/
|
|
||||||
class HelloWorldScene : public Scene {
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief 场景进入时调用
|
|
||||||
*/
|
|
||||||
void onEnter() override {
|
|
||||||
E2D_INFO("HelloWorldScene::onEnter - 进入场景");
|
|
||||||
|
|
||||||
// 设置背景颜色为深蓝色
|
|
||||||
setBackgroundColor(Color(0.1f, 0.1f, 0.3f, 1.0f));
|
|
||||||
|
|
||||||
// 加载字体(支持多种字体后备)
|
|
||||||
auto &resources = Application::instance().resources();
|
|
||||||
font_ = resources.loadFont("assets/font.ttf", 48, true);
|
|
||||||
|
|
||||||
if (!font_) {
|
|
||||||
E2D_ERROR(CAT_ASSET, "字体加载失败,文字渲染将不可用!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建 "你好世界" 文本组件 - 使用屏幕空间(固定位置,不随相机移动)
|
|
||||||
auto text1 = Text::create("你好世界", font_);
|
|
||||||
text1->setCoordinateSpace(CoordinateSpace::Screen);
|
|
||||||
text1->setScreenPosition(640.0f, 360.0f); // 屏幕中心
|
|
||||||
text1->setAnchor(0.5f, 0.5f); // 中心锚点,让文字中心对准位置
|
|
||||||
text1->setTextColor(Color(1.0f, 1.0f, 1.0f, 1.0f));
|
|
||||||
addChild(text1);
|
|
||||||
|
|
||||||
// 创建提示文本组件 - 使用屏幕空间,固定在屏幕底部
|
|
||||||
auto text2 = Text::create("退出按键(START 按钮)", font_);
|
|
||||||
text2->setCoordinateSpace(CoordinateSpace::Screen);
|
|
||||||
text2->setScreenPosition(640.0f, 650.0f); // 屏幕底部
|
|
||||||
text2->setAnchor(0.5f, 0.5f);
|
|
||||||
text2->setTextColor(Color(1.0f, 1.0f, 0.0f, 1.0f));
|
|
||||||
addChild(text2);
|
|
||||||
|
|
||||||
// 创建相机空间文本 - 跟随相机但保持相对偏移
|
|
||||||
auto text3 = Text::create("相机空间文本", font_);
|
|
||||||
text3->setCoordinateSpace(CoordinateSpace::Camera);
|
|
||||||
text3->setCameraOffset(50.0f, 50.0f); // 相机左上角偏移(屏幕坐标系Y向下)
|
|
||||||
text3->setAnchor(0.0f, 0.0f); // 左上角锚点,文字从指定位置开始显示
|
|
||||||
text3->setTextColor(Color(0.0f, 1.0f, 1.0f, 1.0f));
|
|
||||||
addChild(text3);
|
|
||||||
|
|
||||||
// 创建世界空间文本 - 随相机移动(默认行为)
|
|
||||||
auto text4 = Text::create("世界空间文本", font_);
|
|
||||||
text4->setCoordinateSpace(CoordinateSpace::World);
|
|
||||||
text4->setPosition(100.0f, 100.0f); // 世界坐标
|
|
||||||
text4->setAnchor(0.0f, 0.0f); // 左上角锚点,文字从指定位置开始显示
|
|
||||||
text4->setTextColor(Color(1.0f, 0.5f, 0.5f, 1.0f));
|
|
||||||
addChild(text4);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 每帧更新时调用
|
|
||||||
* @param dt 时间间隔(秒)
|
|
||||||
*/
|
|
||||||
void onUpdate(float dt) override {
|
|
||||||
Scene::onUpdate(dt);
|
|
||||||
|
|
||||||
// 检查退出按键
|
|
||||||
auto &input = Application::instance().input();
|
|
||||||
|
|
||||||
// 使用手柄 START 按钮退出 (GamepadButton::Start)
|
|
||||||
if (input.isButtonPressed(GamepadButton::Start)) {
|
|
||||||
E2D_INFO("退出应用 (START 按钮)");
|
|
||||||
Application::instance().quit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
Ptr<FontAtlas> font_; // 字体图集
|
|
||||||
};
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 程序入口
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
int main(int argc, char **argv) {
|
int main(int argc, char **argv) {
|
||||||
E2D_INFO(CAT_APP, "========================");
|
E2D_INFO(CAT_APP, "========================");
|
||||||
E2D_INFO(CAT_APP, "Easy2D Hello World Demo");
|
E2D_INFO(CAT_APP, "Extra2D Hello World Demo");
|
||||||
E2D_INFO(CAT_APP, "========================");
|
E2D_INFO(CAT_APP, "========================");
|
||||||
|
|
||||||
// 获取应用实例
|
// 获取应用实例并初始化
|
||||||
auto &app = Application::instance();
|
auto &app = Application::instance();
|
||||||
|
|
||||||
// 配置应用
|
if (!app.init()) {
|
||||||
AppConfig config;
|
E2D_ERROR(CAT_APP, "应用初始化失败!");
|
||||||
config.title = "Easy2D - Hello World";
|
return -1;
|
||||||
config.width = 1280;
|
}
|
||||||
config.height = 720;
|
|
||||||
config.vsync = true;
|
|
||||||
config.fpsLimit = 60;
|
|
||||||
|
|
||||||
// 初始化应用
|
// 获取事件服务并订阅键盘事件
|
||||||
if (!app.init(config)) {
|
auto eventService = ServiceLocator::instance().get<IEventService>();
|
||||||
E2D_ERROR(CAT_APP, "应用初始化失败!");
|
if (eventService) {
|
||||||
return -1;
|
// 订阅 ESC 键退出
|
||||||
}
|
eventService->on(EventType::KeyPressed, [&app](Event &e) {
|
||||||
|
auto &keyEvent = std::get<KeyEvent>(e.data);
|
||||||
|
if (keyEvent.key == static_cast<i32>(Key::Escape)) {
|
||||||
|
E2D_INFO(CAT_INPUT, "ESC 键按下,退出应用");
|
||||||
|
app.quit();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// 进入 Hello World 场景
|
// 订阅手柄按钮退出
|
||||||
app.enterScene(makePtr<HelloWorldScene>());
|
eventService->on(EventType::GamepadButtonPressed, [&app](Event &e) {
|
||||||
|
auto &btnEvent = std::get<GamepadButtonEvent>(e.data);
|
||||||
|
if (btnEvent.button == static_cast<i32>(Gamepad::Start)) {
|
||||||
|
E2D_INFO(CAT_INPUT, "START 按钮按下,退出应用");
|
||||||
|
app.quit();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
E2D_INFO(CAT_APP, "开始主循环...");
|
E2D_INFO(CAT_APP, "开始主循环...");
|
||||||
|
|
||||||
// 运行应用
|
// 运行应用
|
||||||
app.run();
|
app.run();
|
||||||
|
|
||||||
E2D_INFO(CAT_APP, "应用结束");
|
E2D_INFO(CAT_APP, "应用结束");
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,11 @@
|
||||||
-- 获取当前脚本所在目录(示例根目录)
|
-- 获取当前脚本所在目录(示例根目录)
|
||||||
local example_dir = os.scriptdir()
|
local example_dir = os.scriptdir()
|
||||||
|
|
||||||
|
-- 添加依赖包
|
||||||
|
if is_plat("mingw") then
|
||||||
|
add_requires("glm", "libsdl2", "libsdl2_mixer")
|
||||||
|
end
|
||||||
|
|
||||||
-- 可执行文件目标
|
-- 可执行文件目标
|
||||||
target("hello_world")
|
target("hello_world")
|
||||||
set_kind("binary")
|
set_kind("binary")
|
||||||
|
|
@ -13,6 +18,10 @@ target("hello_world")
|
||||||
add_includedirs("../../Extra2D/include")
|
add_includedirs("../../Extra2D/include")
|
||||||
add_deps("extra2d")
|
add_deps("extra2d")
|
||||||
|
|
||||||
|
if is_plat("mingw") then
|
||||||
|
add_packages("glm", "libsdl2", "libsdl2_mixer")
|
||||||
|
end
|
||||||
|
|
||||||
-- 使用与主项目相同的平台配置
|
-- 使用与主项目相同的平台配置
|
||||||
if is_plat("switch") then
|
if is_plat("switch") then
|
||||||
set_targetdir("../../build/examples/hello_world")
|
set_targetdir("../../build/examples/hello_world")
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue