Compare commits

..

No commits in common. "f46978fc3114d9247ff47d5c374c0000c6657dcc" and "137369c37c5caf08c01fe1d8c1577888c04abd6e" have entirely different histories.

10 changed files with 4472 additions and 662 deletions

View File

@ -8,14 +8,16 @@
#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>
// Window // Platform
#include <extra2d/window/keys.h> #include <extra2d/platform/glfw/glfw_window.h>
#include <extra2d/window/window.h> #include <extra2d/platform/keys.h>
#include <extra2d/window/event_converter.h> #include <extra2d/platform/window_module.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>
@ -40,14 +42,15 @@
#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

View File

@ -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 IDAccount
* @param mountName "save"
* @return
*/
bool mountSaveData(SaveDataType type = SaveDataType::Account,
const UserId &userId = UserId(),
const std::string &mountName = "save");
/**
* @brief
* @param mountName
*/
void unmountSaveData(const std::string &mountName = "save");
/**
* @brief
* @param mountName
* @return
*/
bool commitSaveData(const std::string &mountName = "save");
/**
* @brief
*/
bool isSaveDataMounted() const { return saveDataMounted_; }
/**
* @brief
*/
std::string getSaveDataPath(const std::string &path = "") const;
// ------------------------------------------------------------------------
// 用户账户管理
// ------------------------------------------------------------------------
/**
* @brief ID
* @return IDID
*/
static UserId getCurrentUserId();
/**
* @brief ID
*/
void setDefaultUserId(const UserId &userId) { defaultUserId_ = userId; }
/**
* @brief ID
*/
UserId getDefaultUserId() const { return defaultUserId_; }
// ------------------------------------------------------------------------
// 数据读写
// ------------------------------------------------------------------------
/// 获取字符串值
std::string getString(const std::string &section, const std::string &key,
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);
/// 设置字符串值
void setString(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);
/// 删除键
void removeKey(const std::string &section, const std::string &key);
/// 删除整个 section
void removeSection(const std::string &section);
/// 检查键是否存在
bool hasKey(const std::string &section, const std::string &key);
/// 检查 section 是否存在
bool hasSection(const std::string &section);
/// 清除所有数据
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 &section) 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

View File

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

View File

@ -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 = false; bool fullscreen = true;
bool resizable = true; bool resizable = false;
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

446
Extra2D/src/utils/data.cpp Normal file
View File

@ -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 &section,
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 &section, 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 &section, 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 &section, const std::string &key,
bool defaultValue) {
return impl_->ini.GetBoolValue(section.c_str(), key.c_str(), defaultValue);
}
void DataStore::setString(const std::string &section, 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 &section, 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 &section, 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 &section, 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 &section, 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 &section) {
impl_->ini.Delete(section.c_str(), nullptr);
dirty_ = true;
if (!inTransaction_ && !filename_.empty()) {
save("");
}
}
bool DataStore::hasKey(const std::string &section, const std::string &key) {
return impl_->ini.GetValue(section.c_str(), key.c_str(), nullptr) != nullptr;
}
bool DataStore::hasSection(const std::string &section) {
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 &section : sectionList) {
sections.emplace_back(section.pItem);
}
return sections;
}
std::vector<std::string>
DataStore::getAllKeys(const std::string &section) const {
std::vector<std::string> keys;
CSimpleIniA::TNamesDepend keyList;
impl_->ini.GetAllKeys(section.c_str(), keyList);
for (const auto &key : keyList) {
keys.emplace_back(key.pItem);
}
return keys;
}
bool DataStore::loadFromSave(const std::string &path) {
if (!saveDataMounted_) {
E2D_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

View File

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

View File

@ -3,50 +3,122 @@
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, "Extra2D Hello World Demo"); E2D_INFO(CAT_APP, "Easy2D Hello World Demo");
E2D_INFO(CAT_APP, "========================"); E2D_INFO(CAT_APP, "========================");
// 获取应用实例并初始化 // 获取应用实例
auto &app = Application::instance(); auto &app = Application::instance();
if (!app.init()) { // 配置应用
E2D_ERROR(CAT_APP, "应用初始化失败!"); AppConfig config;
return -1; config.title = "Easy2D - Hello World";
} config.width = 1280;
config.height = 720;
config.vsync = true;
config.fpsLimit = 60;
// 获取事件服务并订阅键盘事件 // 初始化应用
auto eventService = ServiceLocator::instance().get<IEventService>(); if (!app.init(config)) {
if (eventService) { E2D_ERROR(CAT_APP, "应用初始化失败!");
// 订阅 ESC 键退出 return -1;
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 场景
eventService->on(EventType::GamepadButtonPressed, [&app](Event &e) { app.enterScene(makePtr<HelloWorldScene>());
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;
} }

View File

@ -6,11 +6,6 @@
-- 获取当前脚本所在目录(示例根目录) -- 获取当前脚本所在目录(示例根目录)
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")
@ -18,10 +13,6 @@ 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")