Compare commits
No commits in common. "f46978fc3114d9247ff47d5c374c0000c6657dcc" and "137369c37c5caf08c01fe1d8c1577888c04abd6e" have entirely different histories.
f46978fc31
...
137369c37c
|
|
@ -8,14 +8,16 @@
|
|||
#include <extra2d/core/math_types.h>
|
||||
#include <extra2d/core/module.h>
|
||||
#include <extra2d/core/registry.h>
|
||||
#include <extra2d/core/string.h>
|
||||
#include <extra2d/core/types.h>
|
||||
|
||||
// Window
|
||||
#include <extra2d/window/keys.h>
|
||||
#include <extra2d/window/window.h>
|
||||
#include <extra2d/window/event_converter.h>
|
||||
// Platform
|
||||
#include <extra2d/platform/glfw/glfw_window.h>
|
||||
#include <extra2d/platform/keys.h>
|
||||
#include <extra2d/platform/window_module.h>
|
||||
|
||||
// Window - SDL2 + OpenGL
|
||||
#include <extra2d/window/window.h>
|
||||
|
||||
// Render (OpenGL 4.5)
|
||||
#include <extra2d/render/buffer.h>
|
||||
|
|
@ -40,14 +42,15 @@
|
|||
#include <extra2d/event/event.h>
|
||||
#include <extra2d/event/event_dispatcher.h>
|
||||
#include <extra2d/event/event_queue.h>
|
||||
#include <extra2d/event/input_codes.h>
|
||||
|
||||
// Audio
|
||||
#include <extra2d/audio/audio_engine.h>
|
||||
#include <extra2d/audio/sound.h>
|
||||
|
||||
// Utils
|
||||
#include <extra2d/utils/data.h>
|
||||
#include <extra2d/utils/random.h>
|
||||
#include <extra2d/utils/save_store.h>
|
||||
#include <extra2d/utils/timer.h>
|
||||
|
||||
// Spatial
|
||||
|
|
|
|||
|
|
@ -0,0 +1,216 @@
|
|||
#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
|
||||
|
|
@ -1,125 +0,0 @@
|
|||
#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
|
||||
|
|
@ -12,26 +12,26 @@ struct WindowConfig {
|
|||
std::string title = "Extra2D Application";
|
||||
int width = 1280;
|
||||
int height = 720;
|
||||
bool fullscreen = false;
|
||||
bool resizable = true;
|
||||
bool fullscreen = true;
|
||||
bool resizable = false;
|
||||
bool centerWindow = true;
|
||||
};
|
||||
|
||||
class Window {
|
||||
public:
|
||||
using OnEvent = std::function<void(const SDL_Event &)>;
|
||||
using OnEvent = std::function<void(const SDL_Event&)>;
|
||||
|
||||
Window();
|
||||
~Window();
|
||||
|
||||
bool create(const WindowConfig &config);
|
||||
bool create(const WindowConfig& config);
|
||||
void destroy();
|
||||
|
||||
void poll();
|
||||
bool shouldClose() const;
|
||||
void close();
|
||||
|
||||
void setTitle(const std::string &title);
|
||||
void setTitle(const std::string& title);
|
||||
void setSize(int width, int height);
|
||||
void setFullscreen(bool fullscreen);
|
||||
|
||||
|
|
@ -41,10 +41,10 @@ public:
|
|||
|
||||
void onEvent(OnEvent cb) { onEvent_ = cb; }
|
||||
|
||||
SDL_Window *native() const { return sdlWindow_; }
|
||||
SDL_Window* native() const { return sdlWindow_; }
|
||||
|
||||
private:
|
||||
SDL_Window *sdlWindow_ = nullptr;
|
||||
SDL_Window* sdlWindow_ = nullptr;
|
||||
int width_ = 1280;
|
||||
int height_ = 720;
|
||||
bool fullscreen_ = true;
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,446 @@
|
|||
#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
|
||||
|
|
@ -1,459 +0,0 @@
|
|||
#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,43 +3,115 @@
|
|||
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) {
|
||||
E2D_INFO(CAT_APP, "========================");
|
||||
E2D_INFO(CAT_APP, "Extra2D Hello World Demo");
|
||||
E2D_INFO(CAT_APP, "Easy2D Hello World Demo");
|
||||
E2D_INFO(CAT_APP, "========================");
|
||||
|
||||
// 获取应用实例并初始化
|
||||
// 获取应用实例
|
||||
auto &app = Application::instance();
|
||||
|
||||
if (!app.init()) {
|
||||
// 配置应用
|
||||
AppConfig config;
|
||||
config.title = "Easy2D - Hello World";
|
||||
config.width = 1280;
|
||||
config.height = 720;
|
||||
config.vsync = true;
|
||||
config.fpsLimit = 60;
|
||||
|
||||
// 初始化应用
|
||||
if (!app.init(config)) {
|
||||
E2D_ERROR(CAT_APP, "应用初始化失败!");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// 获取事件服务并订阅键盘事件
|
||||
auto eventService = ServiceLocator::instance().get<IEventService>();
|
||||
if (eventService) {
|
||||
// 订阅 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();
|
||||
}
|
||||
});
|
||||
|
||||
// 订阅手柄按钮退出
|
||||
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();
|
||||
}
|
||||
});
|
||||
}
|
||||
// 进入 Hello World 场景
|
||||
app.enterScene(makePtr<HelloWorldScene>());
|
||||
|
||||
E2D_INFO(CAT_APP, "开始主循环...");
|
||||
|
||||
|
|
|
|||
|
|
@ -6,11 +6,6 @@
|
|||
-- 获取当前脚本所在目录(示例根目录)
|
||||
local example_dir = os.scriptdir()
|
||||
|
||||
-- 添加依赖包
|
||||
if is_plat("mingw") then
|
||||
add_requires("glm", "libsdl2", "libsdl2_mixer")
|
||||
end
|
||||
|
||||
-- 可执行文件目标
|
||||
target("hello_world")
|
||||
set_kind("binary")
|
||||
|
|
@ -18,10 +13,6 @@ target("hello_world")
|
|||
add_includedirs("../../Extra2D/include")
|
||||
add_deps("extra2d")
|
||||
|
||||
if is_plat("mingw") then
|
||||
add_packages("glm", "libsdl2", "libsdl2_mixer")
|
||||
end
|
||||
|
||||
-- 使用与主项目相同的平台配置
|
||||
if is_plat("switch") then
|
||||
set_targetdir("../../build/examples/hello_world")
|
||||
|
|
|
|||
Loading…
Reference in New Issue