refactor(core): 重构核心模块与类型系统
- 移除旧的字符串处理模块,改用标准库和simdutf - 重构事件系统,使用环形缓冲区和改进接口命名 - 新增服务注册与定位系统 - 引入模块化架构和依赖管理 - 统一类型别名命名规范 - 实现对象池和环形缓冲区等基础数据结构 - 重构计时器服务,优化接口设计 - 新增资产管理系统基础框架 - 改进随机数生成器接口 - 重构应用框架,支持模块化初始化
This commit is contained in:
parent
6fbebafef3
commit
9bf328b1dc
|
|
@ -1,80 +1,113 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/core/string.h>
|
||||
#include <extra2d/core/module.h>
|
||||
#include <extra2d/core/registry.h>
|
||||
#include <extra2d/core/service_locator.h>
|
||||
#include <extra2d/core/types.h>
|
||||
#include <extra2d/window/window.h>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
class Renderer;
|
||||
class Input;
|
||||
class EventQueue;
|
||||
class EventDispatcher;
|
||||
|
||||
struct AppConfig {
|
||||
std::string title = "Extra2D Application";
|
||||
int width = 1280;
|
||||
int height = 720;
|
||||
bool fullscreen = true;
|
||||
bool vsync = true;
|
||||
int fpsLimit = 60;
|
||||
};
|
||||
class GLFWWindow;
|
||||
class WindowModule;
|
||||
|
||||
/**
|
||||
* @brief 应用程序类
|
||||
*/
|
||||
class Application {
|
||||
public:
|
||||
static Application &instance();
|
||||
static Application &get();
|
||||
|
||||
Application(const Application &) = delete;
|
||||
Application &operator=(const Application &) = delete;
|
||||
|
||||
bool init(const AppConfig &config);
|
||||
/**
|
||||
* @brief 应用信息
|
||||
*/
|
||||
std::string appName = "Extra2D App";
|
||||
std::string appVersion = "1.0.0";
|
||||
std::string organization = "";
|
||||
|
||||
/**
|
||||
* @brief 注册模块
|
||||
* @tparam T 模块类型
|
||||
* @tparam Args 构造函数参数
|
||||
* @return 模块指针
|
||||
*/
|
||||
template <typename T, typename... Args> T *use(Args &&...args) {
|
||||
return Registry::instance().use<T>(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取模块
|
||||
* @tparam T 模块类型
|
||||
* @return 模块指针
|
||||
*/
|
||||
template <typename T> T *get() const { return Registry::instance().get<T>(); }
|
||||
|
||||
/**
|
||||
* @brief 初始化
|
||||
* @return 初始化成功返回 true
|
||||
*/
|
||||
bool init();
|
||||
|
||||
/**
|
||||
* @brief 关闭
|
||||
*/
|
||||
void shutdown();
|
||||
|
||||
/**
|
||||
* @brief 运行主循环
|
||||
*/
|
||||
void run();
|
||||
|
||||
/**
|
||||
* @brief 请求退出
|
||||
*/
|
||||
void quit();
|
||||
|
||||
/**
|
||||
* @brief 暂停
|
||||
*/
|
||||
void pause();
|
||||
|
||||
/**
|
||||
* @brief 恢复
|
||||
*/
|
||||
void resume();
|
||||
bool isPaused() const { return paused_; }
|
||||
bool isRunning() const { return running_; }
|
||||
|
||||
Window &window() { return *window_; }
|
||||
Renderer &renderer() { return *renderer_; }
|
||||
Input &input();
|
||||
EventQueue &eventQueue();
|
||||
EventDispatcher &eventDispatcher();
|
||||
bool paused() const { return paused_; }
|
||||
bool running() const { return running_; }
|
||||
|
||||
float deltaTime() const { return deltaTime_; }
|
||||
float totalTime() const { return totalTime_; }
|
||||
int fps() const { return currentFps_; }
|
||||
/**
|
||||
* @brief 获取窗口
|
||||
* @return 窗口指针
|
||||
*/
|
||||
GLFWWindow *window();
|
||||
|
||||
const AppConfig &getConfig() const { return config_; }
|
||||
f32 dt() const { return dt_; }
|
||||
f32 totalTime() const { return totalTime_; }
|
||||
int fps() const { return fps_; }
|
||||
|
||||
private:
|
||||
Application() = default;
|
||||
Application();
|
||||
~Application();
|
||||
|
||||
void mainLoop();
|
||||
void update();
|
||||
void render();
|
||||
|
||||
AppConfig config_;
|
||||
|
||||
UniquePtr<Window> window_;
|
||||
UniquePtr<Renderer> renderer_;
|
||||
UniquePtr<EventQueue> eventQueue_;
|
||||
UniquePtr<EventDispatcher> eventDispatcher_;
|
||||
|
||||
bool initialized_ = false;
|
||||
bool running_ = false;
|
||||
bool paused_ = false;
|
||||
bool shouldQuit_ = false;
|
||||
|
||||
float deltaTime_ = 0.0f;
|
||||
float totalTime_ = 0.0f;
|
||||
double lastFrameTime_ = 0.0;
|
||||
f32 dt_ = 0.0f;
|
||||
f32 totalTime_ = 0.0f;
|
||||
f64 lastFrameTime_ = 0.0;
|
||||
int frameCount_ = 0;
|
||||
float fpsTimer_ = 0.0f;
|
||||
int currentFps_ = 0;
|
||||
f32 fpsTimer_ = 0.0f;
|
||||
int fps_ = 0;
|
||||
};
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
|
|||
|
|
@ -0,0 +1,477 @@
|
|||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
#include <extra2d/asset/asset_types.h>
|
||||
#include <extra2d/core/types.h>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Asset - 资源基类
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 资源基类
|
||||
*
|
||||
* 所有资源类型的基类,继承自 enable_shared_from_this 支持自动引用计数。
|
||||
* 提供资源的基本属性和生命周期管理接口。
|
||||
*/
|
||||
class Asset : public std::enable_shared_from_this<Asset> {
|
||||
public:
|
||||
virtual ~Asset() = default;
|
||||
|
||||
/**
|
||||
* @brief 获取资源类型
|
||||
* @return 资源类型枚举值
|
||||
*/
|
||||
virtual AssetType type() const = 0;
|
||||
|
||||
/**
|
||||
* @brief 检查资源是否已加载
|
||||
* @return 已加载返回 true
|
||||
*/
|
||||
virtual bool loaded() const = 0;
|
||||
|
||||
/**
|
||||
* @brief 获取资源内存占用大小
|
||||
* @return 内存占用字节数
|
||||
*/
|
||||
virtual size_t memSize() const = 0;
|
||||
|
||||
/**
|
||||
* @brief 获取资源ID
|
||||
* @return 资源ID
|
||||
*/
|
||||
const AssetID &id() const { return id_; }
|
||||
|
||||
/**
|
||||
* @brief 获取资源路径
|
||||
* @return 资源路径
|
||||
*/
|
||||
const std::string &path() const { return path_; }
|
||||
|
||||
/**
|
||||
* @brief 获取资源状态
|
||||
* @return 资源状态
|
||||
*/
|
||||
AssetState state() const { return state_; }
|
||||
|
||||
/**
|
||||
* @brief 获取当前引用计数
|
||||
* @return 引用计数(用于调试和监控)
|
||||
*/
|
||||
long refs() const { return shared_from_this().use_count(); }
|
||||
|
||||
protected:
|
||||
AssetID id_;
|
||||
std::string path_;
|
||||
std::atomic<AssetState> state_{AssetState::Unloaded};
|
||||
|
||||
/**
|
||||
* @brief 设置资源状态
|
||||
* @param state 新状态
|
||||
*/
|
||||
void setState(AssetState state) {
|
||||
state_.store(state, std::memory_order_release);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 设置资源ID
|
||||
* @param id 资源ID
|
||||
*/
|
||||
void setId(const AssetID &id) { id_ = id; }
|
||||
|
||||
/**
|
||||
* @brief 设置资源路径
|
||||
* @param path 资源路径
|
||||
*/
|
||||
void setPath(const std::string &path) { path_ = path; }
|
||||
|
||||
friend class AssetCache;
|
||||
friend class AssetService;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// TextureAsset - 纹理资源
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 纹理资源类
|
||||
*
|
||||
* 存储纹理图像数据,支持多种像素格式。
|
||||
*/
|
||||
class TextureAsset : public Asset {
|
||||
public:
|
||||
AssetType type() const override { return AssetType::Texture; }
|
||||
|
||||
bool loaded() const override {
|
||||
return state_.load(std::memory_order_acquire) == AssetState::Loaded &&
|
||||
data_ != nullptr;
|
||||
}
|
||||
|
||||
size_t memSize() const override {
|
||||
return static_cast<size_t>(width_) * height_ * channels_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取纹理宽度
|
||||
* @return 宽度(像素)
|
||||
*/
|
||||
int width() const { return width_; }
|
||||
|
||||
/**
|
||||
* @brief 获取纹理高度
|
||||
* @return 高度(像素)
|
||||
*/
|
||||
int height() const { return height_; }
|
||||
|
||||
/**
|
||||
* @brief 获取通道数
|
||||
* @return 通道数(1-4)
|
||||
*/
|
||||
int channels() const { return channels_; }
|
||||
|
||||
/**
|
||||
* @brief 获取像素数据
|
||||
* @return 像素数据指针
|
||||
*/
|
||||
const u8 *data() const { return data_.get(); }
|
||||
|
||||
/**
|
||||
* @brief 获取像素数据大小
|
||||
* @return 数据大小(字节)
|
||||
*/
|
||||
size_t dataSize() const { return memSize(); }
|
||||
|
||||
/**
|
||||
* @brief 设置纹理数据
|
||||
* @param width 宽度
|
||||
* @param height 高度
|
||||
* @param channels 通道数
|
||||
* @param data 像素数据(转移所有权)
|
||||
*/
|
||||
void setData(int width, int height, int channels, Unique<u8[]> data) {
|
||||
width_ = width;
|
||||
height_ = height;
|
||||
channels_ = channels;
|
||||
data_ = std::move(data);
|
||||
setState(AssetState::Loaded);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 释放纹理数据
|
||||
*/
|
||||
void release() {
|
||||
data_.reset();
|
||||
width_ = 0;
|
||||
height_ = 0;
|
||||
channels_ = 0;
|
||||
setState(AssetState::Unloaded);
|
||||
}
|
||||
|
||||
private:
|
||||
int width_ = 0;
|
||||
int height_ = 0;
|
||||
int channels_ = 0;
|
||||
Unique<u8[]> data_;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// FontAsset - 字体资源
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 字体资源类
|
||||
*
|
||||
* 存储TrueType字体数据,支持字形渲染。
|
||||
* 使用 Pimpl 模式隐藏 stbtt_fontinfo 实现细节。
|
||||
*/
|
||||
class FontAsset : public Asset {
|
||||
public:
|
||||
FontAsset();
|
||||
~FontAsset() override;
|
||||
|
||||
AssetType type() const override { return AssetType::Font; }
|
||||
|
||||
bool loaded() const override;
|
||||
|
||||
size_t memSize() const override { return data_.size(); }
|
||||
|
||||
/**
|
||||
* @brief 获取指定像素高度的缩放因子
|
||||
* @param pixels 像素高度
|
||||
* @return 缩放因子
|
||||
*/
|
||||
float scaleForPixelHeight(float pixels) const;
|
||||
|
||||
/**
|
||||
* @brief 获取字体数据
|
||||
* @return 字体数据指针
|
||||
*/
|
||||
const u8 *data() const { return data_.data(); }
|
||||
|
||||
/**
|
||||
* @brief 获取字体数据大小
|
||||
* @return 数据大小(字节)
|
||||
*/
|
||||
size_t dataSize() const { return data_.size(); }
|
||||
|
||||
/**
|
||||
* @brief 设置字体数据
|
||||
* @param data 字体数据
|
||||
* @return 成功返回 true
|
||||
*/
|
||||
bool setData(std::vector<u8> data);
|
||||
|
||||
/**
|
||||
* @brief 释放字体数据
|
||||
*/
|
||||
void release();
|
||||
|
||||
private:
|
||||
std::vector<u8> data_;
|
||||
class Impl;
|
||||
Unique<Impl> impl_;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// ShaderAsset - 着色器资源
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 着色器资源类
|
||||
*
|
||||
* 存储顶点和片段着色器源代码。
|
||||
*/
|
||||
class ShaderAsset : public Asset {
|
||||
public:
|
||||
AssetType type() const override { return AssetType::Shader; }
|
||||
|
||||
bool loaded() const override {
|
||||
return state_.load(std::memory_order_acquire) == AssetState::Loaded;
|
||||
}
|
||||
|
||||
size_t memSize() const override {
|
||||
return vertexSrc_.size() + fragmentSrc_.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取顶点着色器源码
|
||||
* @return 顶点着色器源码
|
||||
*/
|
||||
const std::string &vertexSource() const { return vertexSrc_; }
|
||||
|
||||
/**
|
||||
* @brief 获取片段着色器源码
|
||||
* @return 片段着色器源码
|
||||
*/
|
||||
const std::string &fragmentSource() const { return fragmentSrc_; }
|
||||
|
||||
/**
|
||||
* @brief 设置着色器源码
|
||||
* @param vertex 顶点着色器源码
|
||||
* @param fragment 片段着色器源码
|
||||
*/
|
||||
void setSource(std::string vertex, std::string fragment) {
|
||||
vertexSrc_ = std::move(vertex);
|
||||
fragmentSrc_ = std::move(fragment);
|
||||
setState(AssetState::Loaded);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 释放着色器源码
|
||||
*/
|
||||
void release() {
|
||||
vertexSrc_.clear();
|
||||
fragmentSrc_.clear();
|
||||
setState(AssetState::Unloaded);
|
||||
}
|
||||
|
||||
private:
|
||||
std::string vertexSrc_;
|
||||
std::string fragmentSrc_;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// AudioAsset - 音频资源
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 音频格式枚举
|
||||
*/
|
||||
enum class AudioFormat : u8 { PCM = 0, MP3 = 1, OGG = 2, WAV = 3 };
|
||||
|
||||
/**
|
||||
* @brief 音频资源类
|
||||
*
|
||||
* 存储音频数据,支持多种格式。
|
||||
*/
|
||||
class AudioAsset : public Asset {
|
||||
public:
|
||||
AssetType type() const override { return AssetType::Audio; }
|
||||
|
||||
bool loaded() const override {
|
||||
return state_.load(std::memory_order_acquire) == AssetState::Loaded &&
|
||||
!data_.empty();
|
||||
}
|
||||
|
||||
size_t memSize() const override { return data_.size(); }
|
||||
|
||||
/**
|
||||
* @brief 获取音频格式
|
||||
* @return 音频格式
|
||||
*/
|
||||
AudioFormat format() const { return format_; }
|
||||
|
||||
/**
|
||||
* @brief 获取声道数
|
||||
* @return 声道数
|
||||
*/
|
||||
int channels() const { return channels_; }
|
||||
|
||||
/**
|
||||
* @brief 获取采样率
|
||||
* @return 采样率
|
||||
*/
|
||||
int sampleRate() const { return sampleRate_; }
|
||||
|
||||
/**
|
||||
* @brief 获取每样本位数
|
||||
* @return 每样本位数
|
||||
*/
|
||||
int bitsPerSample() const { return bitsPerSample_; }
|
||||
|
||||
/**
|
||||
* @brief 获取时长(秒)
|
||||
* @return 时长
|
||||
*/
|
||||
float duration() const { return duration_; }
|
||||
|
||||
/**
|
||||
* @brief 获取音频数据
|
||||
* @return 音频数据指针
|
||||
*/
|
||||
const u8 *data() const { return data_.data(); }
|
||||
|
||||
/**
|
||||
* @brief 获取音频数据大小
|
||||
* @return 数据大小(字节)
|
||||
*/
|
||||
size_t dataSize() const { return data_.size(); }
|
||||
|
||||
/**
|
||||
* @brief 是否为流式音频
|
||||
* @return 流式音频返回 true
|
||||
*/
|
||||
bool streaming() const { return streaming_; }
|
||||
|
||||
/**
|
||||
* @brief 设置音频数据
|
||||
* @param format 音频格式
|
||||
* @param channels 声道数
|
||||
* @param sampleRate 采样率
|
||||
* @param bitsPerSample 每样本位数
|
||||
* @param data 音频数据
|
||||
*/
|
||||
void setData(AudioFormat format, int channels, int sampleRate,
|
||||
int bitsPerSample, std::vector<u8> data) {
|
||||
format_ = format;
|
||||
channels_ = channels;
|
||||
sampleRate_ = sampleRate;
|
||||
bitsPerSample_ = bitsPerSample;
|
||||
data_ = std::move(data);
|
||||
|
||||
if (sampleRate > 0 && channels > 0 && bitsPerSample > 0) {
|
||||
size_t bytesPerSecond =
|
||||
static_cast<size_t>(sampleRate) * channels * (bitsPerSample / 8);
|
||||
if (bytesPerSecond > 0) {
|
||||
duration_ = static_cast<float>(data_.size()) /
|
||||
static_cast<float>(bytesPerSecond);
|
||||
}
|
||||
}
|
||||
|
||||
streaming_ = duration_ > 5.0f;
|
||||
setState(AssetState::Loaded);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 释放音频数据
|
||||
*/
|
||||
void release() {
|
||||
data_.clear();
|
||||
format_ = AudioFormat::PCM;
|
||||
channels_ = 0;
|
||||
sampleRate_ = 0;
|
||||
bitsPerSample_ = 0;
|
||||
duration_ = 0.0f;
|
||||
streaming_ = false;
|
||||
setState(AssetState::Unloaded);
|
||||
}
|
||||
|
||||
private:
|
||||
AudioFormat format_ = AudioFormat::PCM;
|
||||
int channels_ = 0;
|
||||
int sampleRate_ = 0;
|
||||
int bitsPerSample_ = 0;
|
||||
float duration_ = 0.0f;
|
||||
std::vector<u8> data_;
|
||||
bool streaming_ = false;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// DataAsset - 通用数据资源
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 通用数据资源类
|
||||
*
|
||||
* 存储任意二进制数据。
|
||||
*/
|
||||
class DataAsset : public Asset {
|
||||
public:
|
||||
AssetType type() const override { return AssetType::Data; }
|
||||
|
||||
bool loaded() const override {
|
||||
return state_.load(std::memory_order_acquire) == AssetState::Loaded;
|
||||
}
|
||||
|
||||
size_t memSize() const override { return data_.size(); }
|
||||
|
||||
/**
|
||||
* @brief 获取数据
|
||||
* @return 数据指针
|
||||
*/
|
||||
const u8 *data() const { return data_.data(); }
|
||||
|
||||
/**
|
||||
* @brief 获取数据大小
|
||||
* @return 数据大小(字节)
|
||||
*/
|
||||
size_t size() const { return data_.size(); }
|
||||
|
||||
/**
|
||||
* @brief 设置数据
|
||||
* @param data 数据
|
||||
*/
|
||||
void setData(std::vector<u8> data) {
|
||||
data_ = std::move(data);
|
||||
setState(AssetState::Loaded);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 释放数据
|
||||
*/
|
||||
void release() {
|
||||
data_.clear();
|
||||
setState(AssetState::Unloaded);
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<u8> data_;
|
||||
};
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -0,0 +1,263 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/asset/asset.h>
|
||||
#include <extra2d/asset/asset_handle.h>
|
||||
#include <extra2d/asset/asset_types.h>
|
||||
#include <extra2d/core/types.h>
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <deque>
|
||||
#include <list>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <shared_mutex>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// CacheEntry - 缓存条目
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 缓存条目结构
|
||||
*
|
||||
* 存储资源引用和访问信息,用于LRU缓存管理。
|
||||
*/
|
||||
struct CacheEntry {
|
||||
Ref<Asset> asset;
|
||||
std::chrono::steady_clock::time_point lastAccess;
|
||||
size_t accessCount = 0;
|
||||
|
||||
/**
|
||||
* @brief 构造缓存条目
|
||||
* @param a 资源引用
|
||||
*/
|
||||
explicit CacheEntry(Ref<Asset> a)
|
||||
: asset(std::move(a)),
|
||||
lastAccess(std::chrono::steady_clock::now()),
|
||||
accessCount(1) {}
|
||||
|
||||
/**
|
||||
* @brief 更新访问信息
|
||||
*/
|
||||
void touch() {
|
||||
lastAccess = std::chrono::steady_clock::now();
|
||||
++accessCount;
|
||||
}
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// AssetCache - 资源缓存
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 资源缓存类
|
||||
*
|
||||
* 实现享元模式,提供资源共享和缓存管理功能。
|
||||
*
|
||||
* 特性:
|
||||
* - LRU缓存淘汰策略
|
||||
* - 线程安全(读写锁)
|
||||
* - 引用计数自动回收
|
||||
* - 缓存统计和监控
|
||||
* - 内存上限管理
|
||||
*/
|
||||
class AssetCache {
|
||||
public:
|
||||
/**
|
||||
* @brief 构造函数
|
||||
* @param limit 缓存内存上限(字节),0表示无限制
|
||||
*/
|
||||
explicit AssetCache(size_t limit = 0);
|
||||
|
||||
~AssetCache() = default;
|
||||
|
||||
AssetCache(const AssetCache&) = delete;
|
||||
AssetCache& operator=(const AssetCache&) = delete;
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// 资源管理
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 添加资源到缓存
|
||||
* @tparam T 资源类型
|
||||
* @param asset 资源引用
|
||||
* @return 资源句柄
|
||||
*/
|
||||
template<typename T>
|
||||
AssetHandle<T> add(Ref<T> asset) {
|
||||
static_assert(std::is_base_of_v<Asset, T>,
|
||||
"T must derive from Asset");
|
||||
|
||||
if (!asset) {
|
||||
return AssetHandle<T>();
|
||||
}
|
||||
|
||||
std::unique_lock<std::shared_mutex> lock(mutex_);
|
||||
|
||||
AssetID id = asset->id();
|
||||
|
||||
size_t memSize = asset->memSize();
|
||||
bytes_ += memSize;
|
||||
|
||||
auto it = lruList_.insert(lruList_.end(), id);
|
||||
|
||||
entries_[id] = CacheEntryData{
|
||||
ptr::makeUnique<CacheEntry>(std::static_pointer_cast<Asset>(asset)),
|
||||
it
|
||||
};
|
||||
|
||||
++stats_.count;
|
||||
|
||||
if (limit_ > 0 && bytes_ > limit_) {
|
||||
evict();
|
||||
}
|
||||
|
||||
return AssetHandle<T>(id, Weak<T>(asset));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 从缓存获取资源
|
||||
* @tparam T 资源类型
|
||||
* @param id 资源ID
|
||||
* @return 资源句柄
|
||||
*/
|
||||
template<typename T>
|
||||
AssetHandle<T> get(const AssetID& id) {
|
||||
static_assert(std::is_base_of_v<Asset, T>,
|
||||
"T must derive from Asset");
|
||||
|
||||
std::unique_lock<std::shared_mutex> lock(mutex_);
|
||||
|
||||
auto it = entries_.find(id);
|
||||
if (it == entries_.end()) {
|
||||
++stats_.misses;
|
||||
return AssetHandle<T>();
|
||||
}
|
||||
|
||||
it->second.entry->touch();
|
||||
|
||||
lruList_.erase(it->second.lruIterator);
|
||||
it->second.lruIterator = lruList_.insert(lruList_.end(), id);
|
||||
|
||||
++stats_.hits;
|
||||
|
||||
auto typedAsset = std::static_pointer_cast<T>(it->second.entry->asset);
|
||||
return AssetHandle<T>(id, Weak<T>(typedAsset));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 检查缓存是否包含资源
|
||||
* @param id 资源ID
|
||||
* @return 包含返回 true
|
||||
*/
|
||||
bool has(const AssetID& id) const;
|
||||
|
||||
/**
|
||||
* @brief 从缓存移除资源
|
||||
* @param id 资源ID
|
||||
* @return 移除成功返回 true
|
||||
*/
|
||||
bool remove(const AssetID& id);
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// 缓存管理
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 设置缓存内存上限
|
||||
* @param limit 上限(字节),0表示无限制
|
||||
*/
|
||||
void setLimit(size_t limit);
|
||||
|
||||
/**
|
||||
* @brief 获取缓存内存上限
|
||||
* @return 上限(字节)
|
||||
*/
|
||||
size_t limit() const { return limit_; }
|
||||
|
||||
/**
|
||||
* @brief 获取当前缓存内存使用量
|
||||
* @return 使用量(字节)
|
||||
*/
|
||||
size_t bytes() const { return bytes_; }
|
||||
|
||||
/**
|
||||
* @brief 获取缓存条目数量
|
||||
* @return 条目数量
|
||||
*/
|
||||
size_t count() const;
|
||||
|
||||
/**
|
||||
* @brief 获取当前缓存内存使用量(别名)
|
||||
* @return 使用量(字节)
|
||||
*/
|
||||
size_t size() const { return bytes_; }
|
||||
|
||||
/**
|
||||
* @brief 记录缓存命中
|
||||
*/
|
||||
void hit() { ++stats_.hits; }
|
||||
|
||||
/**
|
||||
* @brief 记录缓存未命中
|
||||
*/
|
||||
void miss() { ++stats_.misses; }
|
||||
|
||||
/**
|
||||
* @brief 清理无外部引用的资源
|
||||
* @return 清理的资源数量
|
||||
*/
|
||||
size_t purge();
|
||||
|
||||
/**
|
||||
* @brief 清空所有缓存
|
||||
*/
|
||||
void clear();
|
||||
|
||||
/**
|
||||
* @brief 获取缓存统计信息
|
||||
* @return 统计信息
|
||||
*/
|
||||
CacheStats stats() const;
|
||||
|
||||
/**
|
||||
* @brief 重置统计信息
|
||||
*/
|
||||
void resetStats();
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief 缓存条目数据(包含LRU迭代器)
|
||||
*/
|
||||
struct CacheEntryData {
|
||||
Ref<CacheEntry> entry;
|
||||
std::list<AssetID>::iterator lruIterator;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 淘汰资源(LRU策略)
|
||||
*/
|
||||
void evict();
|
||||
|
||||
/**
|
||||
* @brief 检查资源是否可以被淘汰
|
||||
* @param entry 缓存条目
|
||||
* @return 可淘汰返回 true
|
||||
*/
|
||||
bool canEvict(const CacheEntry& entry) const;
|
||||
|
||||
mutable std::shared_mutex mutex_;
|
||||
std::unordered_map<AssetID, CacheEntryData> entries_;
|
||||
std::list<AssetID> lruList_;
|
||||
|
||||
size_t limit_ = 0;
|
||||
size_t bytes_ = 0;
|
||||
mutable CacheStats stats_;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,304 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/asset/asset_types.h>
|
||||
#include <extra2d/core/types.h>
|
||||
#include <memory>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// AssetHandleBase - 资源句柄非模板基类
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 资源句柄非模板基类
|
||||
*
|
||||
* 用于类型擦除,允许在容器中存储不同类型的句柄。
|
||||
*/
|
||||
class AssetHandleBase {
|
||||
public:
|
||||
AssetHandleBase() = default;
|
||||
|
||||
/**
|
||||
* @brief 从资源ID和弱引用构造
|
||||
* @param id 资源ID
|
||||
* @param ref 资源弱引用
|
||||
*/
|
||||
AssetHandleBase(const AssetID& id, Weak<Asset> ref)
|
||||
: id_(id), cacheRef_(std::move(ref)) {}
|
||||
|
||||
/**
|
||||
* @brief 从强引用构造
|
||||
* @param ptr 资源强引用
|
||||
*/
|
||||
explicit AssetHandleBase(Ref<Asset> ptr)
|
||||
: id_(ptr ? ptr->id() : AssetID()),
|
||||
cacheRef_(ptr) {}
|
||||
|
||||
/**
|
||||
* @brief 检查句柄是否有效
|
||||
* @return 有效返回 true
|
||||
*/
|
||||
bool valid() const {
|
||||
return !cacheRef_.expired();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取资源强引用
|
||||
* @return 资源强引用
|
||||
*/
|
||||
Ref<Asset> get() const {
|
||||
return cacheRef_.lock();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取资源ID
|
||||
* @return 资源ID
|
||||
*/
|
||||
const AssetID& id() const { return id_; }
|
||||
|
||||
/**
|
||||
* @brief 获取资源路径
|
||||
* @return 资源路径
|
||||
*/
|
||||
const std::string& path() const { return id_.path; }
|
||||
|
||||
/**
|
||||
* @brief 隐式转换为bool
|
||||
*/
|
||||
explicit operator bool() const { return valid(); }
|
||||
|
||||
/**
|
||||
* @brief 重置句柄
|
||||
*/
|
||||
void reset() {
|
||||
id_ = AssetID();
|
||||
cacheRef_.reset();
|
||||
}
|
||||
|
||||
protected:
|
||||
AssetID id_;
|
||||
Weak<Asset> cacheRef_;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// AssetHandle - 资源句柄
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 资源句柄模板类
|
||||
*
|
||||
* 使用强类型句柄替代裸指针,提供类型安全和自动生命周期管理。
|
||||
* 内部使用 weak_ptr 弱引用,不阻止资源回收。
|
||||
*
|
||||
* 特性:
|
||||
* - 类型安全:编译期检查资源类型
|
||||
* - 自动生命周期:资源无引用时自动回收
|
||||
* - 弱引用:不阻止缓存清理
|
||||
* - 空安全:使用前检查 valid()
|
||||
*
|
||||
* @tparam T 资源类型,必须继承自 Asset
|
||||
*/
|
||||
template<typename T>
|
||||
class AssetHandle : public AssetHandleBase {
|
||||
static_assert(std::is_base_of_v<Asset, T>,
|
||||
"T must derive from Asset");
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief 默认构造函数
|
||||
*/
|
||||
AssetHandle() = default;
|
||||
|
||||
/**
|
||||
* @brief 从基类句柄构造
|
||||
* @param base 基类句柄
|
||||
*/
|
||||
explicit AssetHandle(const AssetHandleBase& base)
|
||||
: AssetHandleBase(base) {}
|
||||
|
||||
/**
|
||||
* @brief 从资源ID和弱引用构造
|
||||
* @param id 资源ID
|
||||
* @param ref 资源弱引用
|
||||
*/
|
||||
AssetHandle(const AssetID& id, Weak<T> ref)
|
||||
: AssetHandleBase(id, std::move(ref)) {}
|
||||
|
||||
/**
|
||||
* @brief 从强引用构造
|
||||
* @param ptr 资源强引用
|
||||
*/
|
||||
explicit AssetHandle(Ref<T> ptr)
|
||||
: AssetHandleBase(ptr) {}
|
||||
|
||||
/**
|
||||
* @brief 获取资源强引用
|
||||
* @return 资源强引用,如果资源已被回收返回 nullptr
|
||||
*/
|
||||
Ref<T> get() const {
|
||||
return std::static_pointer_cast<T>(cacheRef_.lock());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 解引用操作符
|
||||
* @return 资源指针
|
||||
* @note 使用前应检查 valid()
|
||||
*/
|
||||
T* operator->() const {
|
||||
auto ptr = get();
|
||||
return ptr ? ptr.get() : nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 解引用操作符
|
||||
* @return 资源引用
|
||||
* @note 使用前应检查 valid()
|
||||
*/
|
||||
T& operator*() const {
|
||||
auto ptr = get();
|
||||
return *ptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 相等比较
|
||||
* @param other 其他句柄
|
||||
* @return 相等返回 true
|
||||
*/
|
||||
bool operator==(const AssetHandle& other) const {
|
||||
return id_ == other.id_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 不等比较
|
||||
* @param other 其他句柄
|
||||
* @return 不等返回 true
|
||||
*/
|
||||
bool operator!=(const AssetHandle& other) const {
|
||||
return id_ != other.id_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 小于比较(用于有序容器)
|
||||
* @param other 其他句柄
|
||||
* @return 小于返回 true
|
||||
*/
|
||||
bool operator<(const AssetHandle& other) const {
|
||||
return id_ < other.id_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 重置句柄
|
||||
*/
|
||||
void reset() {
|
||||
id_ = AssetID();
|
||||
cacheRef_.reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 检查资源是否已加载
|
||||
* @return 已加载返回 true
|
||||
*/
|
||||
bool loaded() const {
|
||||
auto ptr = get();
|
||||
return ptr && ptr->loaded();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取资源状态
|
||||
* @return 资源状态
|
||||
*/
|
||||
AssetState state() const {
|
||||
auto ptr = get();
|
||||
return ptr ? ptr->state() : AssetState::Unloaded;
|
||||
}
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// AssetLoadResult - 资源加载结果
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 资源加载结果
|
||||
*
|
||||
* 封装资源加载的结果状态,支持成功、失败、加载中等状态。
|
||||
*
|
||||
* @tparam T 资源类型
|
||||
*/
|
||||
template<typename T>
|
||||
struct AssetLoadResult {
|
||||
AssetHandle<T> handle;
|
||||
AssetState state = AssetState::Unloaded;
|
||||
std::string error;
|
||||
|
||||
/**
|
||||
* @brief 检查是否成功
|
||||
* @return 成功返回 true
|
||||
*/
|
||||
bool success() const {
|
||||
return state == AssetState::Loaded && handle.valid();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 检查是否失败
|
||||
* @return 失败返回 true
|
||||
*/
|
||||
bool failed() const {
|
||||
return state == AssetState::Failed;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 检查是否正在加载
|
||||
* @return 正在加载返回 true
|
||||
*/
|
||||
bool loading() const {
|
||||
return state == AssetState::Loading;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 创建成功结果
|
||||
* @param handle 资源句柄
|
||||
* @return 加载结果
|
||||
*/
|
||||
static AssetLoadResult ok(AssetHandle<T> handle) {
|
||||
return { std::move(handle), AssetState::Loaded, "" };
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 创建失败结果
|
||||
* @param error 错误信息
|
||||
* @return 加载结果
|
||||
*/
|
||||
static AssetLoadResult err(const std::string& error) {
|
||||
return { AssetHandle<T>(), AssetState::Failed, error };
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 创建加载中结果
|
||||
* @param handle 资源句柄(可能为空)
|
||||
* @return 加载结果
|
||||
*/
|
||||
static AssetLoadResult pending(AssetHandle<T> handle = {}) {
|
||||
return { std::move(handle), AssetState::Loading, "" };
|
||||
}
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// AssetLoadCallback - 资源加载回调
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 资源加载回调类型
|
||||
* @tparam T 资源类型
|
||||
*/
|
||||
template<typename T>
|
||||
using AssetLoadCallback = Fn<void(AssetHandle<T>)>;
|
||||
|
||||
/**
|
||||
* @brief 资源加载结果回调类型
|
||||
* @tparam T 资源类型
|
||||
*/
|
||||
template<typename T>
|
||||
using AssetLoadResultCallback = Fn<void(AssetLoadResult<T>)>;
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,312 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/asset/asset.h>
|
||||
#include <extra2d/asset/asset_types.h>
|
||||
#include <extra2d/core/types.h>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// AssetLoaderBase - 资源加载器非模板基类
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 资源加载器非模板基类
|
||||
*
|
||||
* 用于类型擦除,允许在容器中存储不同类型的加载器。
|
||||
*/
|
||||
class AssetLoaderBase {
|
||||
public:
|
||||
virtual ~AssetLoaderBase() = default;
|
||||
|
||||
/**
|
||||
* @brief 从文件加载资源(返回 Asset 基类指针)
|
||||
* @param path 文件路径
|
||||
* @return 资源实例
|
||||
*/
|
||||
virtual Ref<Asset> loadBase(const std::string& path) = 0;
|
||||
|
||||
/**
|
||||
* @brief 从内存数据加载资源
|
||||
* @param data 数据指针
|
||||
* @param size 数据大小
|
||||
* @return 资源实例
|
||||
*/
|
||||
virtual Ref<Asset> loadFromMemoryBase(const u8* data, size_t size) = 0;
|
||||
|
||||
/**
|
||||
* @brief 检查是否能加载指定路径的资源
|
||||
* @param path 文件路径
|
||||
* @return 能加载返回 true
|
||||
*/
|
||||
virtual bool canLoad(const std::string& path) const = 0;
|
||||
|
||||
/**
|
||||
* @brief 获取资源类型
|
||||
* @return 资源类型枚举值
|
||||
*/
|
||||
virtual AssetType type() const = 0;
|
||||
|
||||
/**
|
||||
* @brief 获取支持的文件扩展名列表
|
||||
* @return 扩展名列表
|
||||
*/
|
||||
virtual std::vector<std::string> extensions() const = 0;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// AssetLoader - 资源加载器接口
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 资源加载器接口模板
|
||||
*
|
||||
* 使用策略模式支持不同资源类型的加载。
|
||||
* 每种资源类型可以实现自己的加载器。
|
||||
*
|
||||
* @tparam T 资源类型
|
||||
*/
|
||||
template<typename T>
|
||||
class AssetLoader : public AssetLoaderBase {
|
||||
static_assert(std::is_base_of_v<Asset, T>,
|
||||
"T must derive from Asset");
|
||||
|
||||
public:
|
||||
virtual ~AssetLoader() = default;
|
||||
|
||||
/**
|
||||
* @brief 从文件加载资源
|
||||
* @param path 文件路径
|
||||
* @return 资源实例,失败返回 nullptr
|
||||
*/
|
||||
virtual Ref<T> load(const std::string& path) = 0;
|
||||
|
||||
/**
|
||||
* @brief 从内存数据加载资源
|
||||
* @param data 数据指针
|
||||
* @param size 数据大小
|
||||
* @return 资源实例,失败返回 nullptr
|
||||
*/
|
||||
virtual Ref<T> loadFromMemory(const u8* data, size_t size) = 0;
|
||||
|
||||
Ref<Asset> loadBase(const std::string& path) override {
|
||||
return load(path);
|
||||
}
|
||||
|
||||
Ref<Asset> loadFromMemoryBase(const u8* data, size_t size) override {
|
||||
return loadFromMemory(data, size);
|
||||
}
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// TextureLoader - 纹理加载器
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 纹理加载器
|
||||
*
|
||||
* 使用 stb_image 加载各种格式的图片文件。
|
||||
* 支持 PNG, JPG, BMP, TGA, GIF, PSD, HDR, PIC 等格式。
|
||||
*/
|
||||
class TextureLoader : public AssetLoader<TextureAsset> {
|
||||
public:
|
||||
TextureLoader();
|
||||
~TextureLoader() override;
|
||||
|
||||
Ref<TextureAsset> load(const std::string& path) override;
|
||||
Ref<TextureAsset> loadFromMemory(const u8* data, size_t size) override;
|
||||
bool canLoad(const std::string& path) const override;
|
||||
AssetType type() const override { return AssetType::Texture; }
|
||||
std::vector<std::string> extensions() const override;
|
||||
|
||||
/**
|
||||
* @brief 设置期望的通道数
|
||||
* @param channels 通道数(1-4),0表示自动
|
||||
*/
|
||||
void setDesiredChannels(int channels);
|
||||
|
||||
/**
|
||||
* @brief 获取期望的通道数
|
||||
* @return 通道数
|
||||
*/
|
||||
int desiredChannels() const { return desiredChannels_; }
|
||||
|
||||
private:
|
||||
int desiredChannels_ = 4;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// FontLoader - 字体加载器
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 字体加载器
|
||||
*
|
||||
* 加载 TrueType 字体文件(.ttf, .otf)。
|
||||
*/
|
||||
class FontLoader : public AssetLoader<FontAsset> {
|
||||
public:
|
||||
Ref<FontAsset> load(const std::string& path) override;
|
||||
Ref<FontAsset> loadFromMemory(const u8* data, size_t size) override;
|
||||
bool canLoad(const std::string& path) const override;
|
||||
AssetType type() const override { return AssetType::Font; }
|
||||
std::vector<std::string> extensions() const override;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// ShaderLoader - 着色器加载器
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 着色器加载器
|
||||
*
|
||||
* 加载着色器源文件,支持以下格式:
|
||||
* - .vert/.frag: 分离的顶点/片段着色器
|
||||
* - .glsl: 合并的着色器文件(使用标记分隔)
|
||||
*/
|
||||
class ShaderLoader : public AssetLoader<ShaderAsset> {
|
||||
public:
|
||||
Ref<ShaderAsset> load(const std::string& path) override;
|
||||
Ref<ShaderAsset> loadFromMemory(const u8* data, size_t size) override;
|
||||
bool canLoad(const std::string& path) const override;
|
||||
AssetType type() const override { return AssetType::Shader; }
|
||||
std::vector<std::string> extensions() const override;
|
||||
|
||||
/**
|
||||
* @brief 设置顶点着色器标记
|
||||
* @param marker 标记字符串(默认 "[VERTEX]")
|
||||
*/
|
||||
void setVertexMarker(const std::string& marker) { vertexMarker_ = marker; }
|
||||
|
||||
/**
|
||||
* @brief 设置片段着色器标记
|
||||
* @param marker 标记字符串(默认 "[FRAGMENT]")
|
||||
*/
|
||||
void setFragmentMarker(const std::string& marker) { fragmentMarker_ = marker; }
|
||||
|
||||
private:
|
||||
std::string vertexMarker_ = "[VERTEX]";
|
||||
std::string fragmentMarker_ = "[FRAGMENT]";
|
||||
|
||||
/**
|
||||
* @brief 解析合并的着色器文件
|
||||
* @param content 文件内容
|
||||
* @param vertex 输出顶点着色器源码
|
||||
* @param fragment 输出片段着色器源码
|
||||
* @return 成功返回 true
|
||||
*/
|
||||
bool parseCombined(const std::string& content,
|
||||
std::string& vertex,
|
||||
std::string& fragment);
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// AudioLoader - 音频加载器
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 音频加载器
|
||||
*
|
||||
* 加载音频文件,支持 WAV 格式。
|
||||
* 可扩展支持 MP3, OGG 等格式。
|
||||
*/
|
||||
class AudioLoader : public AssetLoader<AudioAsset> {
|
||||
public:
|
||||
Ref<AudioAsset> load(const std::string& path) override;
|
||||
Ref<AudioAsset> loadFromMemory(const u8* data, size_t size) override;
|
||||
bool canLoad(const std::string& path) const override;
|
||||
AssetType type() const override { return AssetType::Audio; }
|
||||
std::vector<std::string> extensions() const override;
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief 加载 WAV 格式音频
|
||||
* @param data 数据指针
|
||||
* @param size 数据大小
|
||||
* @return 音频资源
|
||||
*/
|
||||
Ref<AudioAsset> loadWav(const u8* data, size_t size);
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// DataLoader - 通用数据加载器
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 通用数据加载器
|
||||
*
|
||||
* 加载任意二进制数据文件。
|
||||
*/
|
||||
class DataLoader : public AssetLoader<DataAsset> {
|
||||
public:
|
||||
Ref<DataAsset> load(const std::string& path) override;
|
||||
Ref<DataAsset> loadFromMemory(const u8* data, size_t size) override;
|
||||
bool canLoad(const std::string& path) const override;
|
||||
AssetType type() const override { return AssetType::Data; }
|
||||
std::vector<std::string> extensions() const override;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// AssetLoaderFactory - 加载器工厂
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 加载器工厂
|
||||
*
|
||||
* 根据资源类型或文件扩展名创建对应的加载器。
|
||||
* 使用模板方法返回具体类型的加载器。
|
||||
*/
|
||||
class AssetLoaderFactory {
|
||||
public:
|
||||
/**
|
||||
* @brief 根据资源类型创建纹理加载器
|
||||
* @return 纹理加载器实例
|
||||
*/
|
||||
static Unique<TextureLoader> createTextureLoader() {
|
||||
return ptr::makeUnique<TextureLoader>();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 根据资源类型创建字体加载器
|
||||
* @return 字体加载器实例
|
||||
*/
|
||||
static Unique<FontLoader> createFontLoader() {
|
||||
return ptr::makeUnique<FontLoader>();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 根据资源类型创建着色器加载器
|
||||
* @return 着色器加载器实例
|
||||
*/
|
||||
static Unique<ShaderLoader> createShaderLoader() {
|
||||
return ptr::makeUnique<ShaderLoader>();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 根据资源类型创建音频加载器
|
||||
* @return 音频加载器实例
|
||||
*/
|
||||
static Unique<AudioLoader> createAudioLoader() {
|
||||
return ptr::makeUnique<AudioLoader>();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 根据资源类型创建数据加载器
|
||||
* @return 数据加载器实例
|
||||
*/
|
||||
static Unique<DataLoader> createDataLoader() {
|
||||
return ptr::makeUnique<DataLoader>();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 根据文件扩展名获取资源类型
|
||||
* @param extension 文件扩展名(包含点,如 ".png")
|
||||
* @return 资源类型,无法识别返回 AssetType::Unknown
|
||||
*/
|
||||
static AssetType getTypeByExtension(const std::string& extension);
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,399 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/asset/asset_types.h>
|
||||
#include <extra2d/asset/data_processor.h>
|
||||
#include <extra2d/core/types.h>
|
||||
#include <fstream>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// AssetPack - 资源包
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 资源包类
|
||||
*
|
||||
* 支持从打包文件中加载资源。
|
||||
* 资源包格式:
|
||||
* - 头部:AssetPackageHeader
|
||||
* - 索引表:条目数量 + AssetPackageEntry 数组
|
||||
* - 数据区:按索引顺序存储的资源数据
|
||||
*/
|
||||
class AssetPack {
|
||||
public:
|
||||
AssetPack() = default;
|
||||
|
||||
/**
|
||||
* @brief 移动构造函数
|
||||
* @param other 其他资源包
|
||||
*/
|
||||
AssetPack(AssetPack&& other) noexcept;
|
||||
|
||||
/**
|
||||
* @brief 移动赋值操作符
|
||||
* @param other 其他资源包
|
||||
* @return 当前资源包引用
|
||||
*/
|
||||
AssetPack& operator=(AssetPack&& other) noexcept;
|
||||
|
||||
/**
|
||||
* @brief 析构函数,自动关闭文件
|
||||
*/
|
||||
~AssetPack();
|
||||
|
||||
/**
|
||||
* @brief 打开资源包
|
||||
* @param path 资源包文件路径
|
||||
* @return 成功返回 true
|
||||
*/
|
||||
bool open(const std::string& path);
|
||||
|
||||
/**
|
||||
* @brief 关闭资源包
|
||||
*/
|
||||
void close();
|
||||
|
||||
/**
|
||||
* @brief 检查资源包是否已打开
|
||||
* @return 已打开返回 true
|
||||
*/
|
||||
bool isOpen() const { return file_.is_open(); }
|
||||
|
||||
/**
|
||||
* @brief 检查资源是否存在
|
||||
* @param id 资源ID
|
||||
* @return 存在返回 true
|
||||
*/
|
||||
bool has(const AssetID& id) const;
|
||||
|
||||
/**
|
||||
* @brief 检查资源是否存在
|
||||
* @param path 资源路径
|
||||
* @return 存在返回 true
|
||||
*/
|
||||
bool has(const std::string& path) const;
|
||||
|
||||
/**
|
||||
* @brief 读取资源数据(自动解压/解密)
|
||||
* @param id 资源ID
|
||||
* @return 资源数据,失败返回空
|
||||
*/
|
||||
std::vector<u8> read(const AssetID& id);
|
||||
|
||||
/**
|
||||
* @brief 读取资源数据(自动解压/解密)
|
||||
* @param path 资源路径
|
||||
* @return 资源数据,失败返回空
|
||||
*/
|
||||
std::vector<u8> read(const std::string& path);
|
||||
|
||||
/**
|
||||
* @brief 读取原始资源数据(不解压/不解密)
|
||||
* @param id 资源ID
|
||||
* @return 原始资源数据
|
||||
*/
|
||||
std::vector<u8> readRaw(const AssetID& id);
|
||||
|
||||
/**
|
||||
* @brief 获取所有资源ID
|
||||
* @return 资源ID列表
|
||||
*/
|
||||
std::vector<AssetID> assets() const;
|
||||
|
||||
/**
|
||||
* @brief 获取资源包路径
|
||||
* @return 资源包路径
|
||||
*/
|
||||
const std::string& path() const { return path_; }
|
||||
|
||||
/**
|
||||
* @brief 获取资源包头部
|
||||
* @return 头部信息
|
||||
*/
|
||||
const AssetPackageHeader& header() const { return header_; }
|
||||
|
||||
/**
|
||||
* @brief 获取资源条目数量
|
||||
* @return 条目数量
|
||||
*/
|
||||
size_t count() const { return entries_.size(); }
|
||||
|
||||
/**
|
||||
* @brief 设置数据处理管道
|
||||
* @param pipe 处理管道
|
||||
*/
|
||||
void setPipe(DataPipe pipe) { pipe_ = std::move(pipe); }
|
||||
|
||||
/**
|
||||
* @brief 获取资源条目信息
|
||||
* @param id 资源ID
|
||||
* @return 条目指针,不存在返回 nullptr
|
||||
*/
|
||||
const AssetPackageEntry* getEntry(const AssetID& id) const;
|
||||
|
||||
private:
|
||||
std::string path_;
|
||||
mutable std::ifstream file_;
|
||||
AssetPackageHeader header_;
|
||||
std::unordered_map<AssetID, AssetPackageEntry> entries_;
|
||||
DataPipe pipe_;
|
||||
|
||||
/**
|
||||
* @brief 读取并解析头部
|
||||
* @return 成功返回 true
|
||||
*/
|
||||
bool readHeader();
|
||||
|
||||
/**
|
||||
* @brief 读取并解析索引表
|
||||
* @return 成功返回 true
|
||||
*/
|
||||
bool readIndex();
|
||||
|
||||
/**
|
||||
* @brief 读取条目数据
|
||||
* @param entry 条目信息
|
||||
* @return 原始数据
|
||||
*/
|
||||
std::vector<u8> readEntryData(const AssetPackageEntry& entry);
|
||||
|
||||
AssetPack(const AssetPack&) = delete;
|
||||
AssetPack& operator=(const AssetPack&) = delete;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// PackManager - 资源包管理器
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 资源包管理器
|
||||
*
|
||||
* 管理多个资源包,支持资源查找和加载。
|
||||
* 支持挂载/卸载资源包。
|
||||
*/
|
||||
class PackManager {
|
||||
public:
|
||||
PackManager() = default;
|
||||
|
||||
/**
|
||||
* @brief 析构函数,自动卸载所有资源包
|
||||
*/
|
||||
~PackManager() = default;
|
||||
|
||||
/**
|
||||
* @brief 挂载资源包
|
||||
* @param path 资源包路径
|
||||
* @return 成功返回 true
|
||||
*/
|
||||
bool mount(const std::string& path);
|
||||
|
||||
/**
|
||||
* @brief 卸载资源包
|
||||
* @param path 资源包路径
|
||||
*/
|
||||
void unmount(const std::string& path);
|
||||
|
||||
/**
|
||||
* @brief 卸载所有资源包
|
||||
*/
|
||||
void unmountAll();
|
||||
|
||||
/**
|
||||
* @brief 查找资源所在的包
|
||||
* @param id 资源ID
|
||||
* @return 资源包指针,未找到返回 nullptr
|
||||
*/
|
||||
AssetPack* find(const AssetID& id);
|
||||
|
||||
/**
|
||||
* @brief 查找资源所在的包
|
||||
* @param path 资源路径
|
||||
* @return 资源包指针,未找到返回 nullptr
|
||||
*/
|
||||
AssetPack* find(const std::string& path);
|
||||
|
||||
/**
|
||||
* @brief 检查资源是否存在
|
||||
* @param id 资源ID
|
||||
* @return 存在返回 true
|
||||
*/
|
||||
bool has(const AssetID& id) const;
|
||||
|
||||
/**
|
||||
* @brief 检查资源是否存在
|
||||
* @param path 资源路径
|
||||
* @return 存在返回 true
|
||||
*/
|
||||
bool has(const std::string& path) const;
|
||||
|
||||
/**
|
||||
* @brief 读取资源数据
|
||||
* @param id 资源ID
|
||||
* @return 资源数据
|
||||
*/
|
||||
std::vector<u8> read(const AssetID& id);
|
||||
|
||||
/**
|
||||
* @brief 读取资源数据
|
||||
* @param path 资源路径
|
||||
* @return 资源数据
|
||||
*/
|
||||
std::vector<u8> read(const std::string& path);
|
||||
|
||||
/**
|
||||
* @brief 设置默认处理管道
|
||||
* @param pipe 处理管道
|
||||
*/
|
||||
void setPipe(DataPipe pipe) { defaultPipe_ = std::move(pipe); }
|
||||
|
||||
/**
|
||||
* @brief 获取默认处理管道
|
||||
* @return 处理管道引用
|
||||
*/
|
||||
const DataPipe& pipe() const { return defaultPipe_; }
|
||||
|
||||
/**
|
||||
* @brief 获取已挂载的资源包数量
|
||||
* @return 资源包数量
|
||||
*/
|
||||
size_t count() const { return packs_.size(); }
|
||||
|
||||
/**
|
||||
* @brief 获取所有资源ID
|
||||
* @return 资源ID列表
|
||||
*/
|
||||
std::vector<AssetID> allAssets() const;
|
||||
|
||||
/**
|
||||
* @brief 获取已挂载的资源包路径列表
|
||||
* @return 路径列表
|
||||
*/
|
||||
std::vector<std::string> mountedPacks() const;
|
||||
|
||||
private:
|
||||
std::vector<Unique<AssetPack>> packs_;
|
||||
DataPipe defaultPipe_;
|
||||
|
||||
PackManager(const PackManager&) = delete;
|
||||
PackManager& operator=(const PackManager&) = delete;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// AssetPackBuilder - 资源包构建器(用于打包工具)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 资源包构建器
|
||||
*
|
||||
* 用于创建资源包文件。
|
||||
* 支持添加资源、压缩、加密等操作。
|
||||
*/
|
||||
class AssetPackBuilder {
|
||||
public:
|
||||
/**
|
||||
* @brief 构造函数
|
||||
* @param compression 压缩算法
|
||||
* @param level 压缩级别
|
||||
*/
|
||||
explicit AssetPackBuilder(Compression compression = Compression::None, int level = 3);
|
||||
|
||||
/**
|
||||
* @brief 添加资源
|
||||
* @param path 资源路径(包内路径)
|
||||
* @param data 资源数据
|
||||
*/
|
||||
void add(const std::string& path, const std::vector<u8>& data);
|
||||
|
||||
/**
|
||||
* @brief 添加资源(移动语义)
|
||||
* @param path 资源路径
|
||||
* @param data 资源数据
|
||||
*/
|
||||
void add(const std::string& path, std::vector<u8>&& data);
|
||||
|
||||
/**
|
||||
* @brief 添加文件
|
||||
* @param filePath 文件路径
|
||||
* @param packPath 包内路径(可选,默认使用文件名)
|
||||
* @return 成功返回 true
|
||||
*/
|
||||
bool addFile(const std::string& filePath, const std::string& packPath = "");
|
||||
|
||||
/**
|
||||
* @brief 添加目录
|
||||
* @param dirPath 目录路径
|
||||
* @param prefix 包内路径前缀(可选)
|
||||
* @return 添加的文件数量
|
||||
*/
|
||||
size_t addDirectory(const std::string& dirPath, const std::string& prefix = "");
|
||||
|
||||
/**
|
||||
* @brief 设置加密密钥
|
||||
* @param key 加密密钥
|
||||
* @param type 加密类型
|
||||
*/
|
||||
void setEncryption(const std::string& key, Decryptor::Type type = Decryptor::Type::XOR);
|
||||
|
||||
/**
|
||||
* @brief 构建资源包
|
||||
* @param outputPath 输出文件路径
|
||||
* @return 成功返回 true
|
||||
*/
|
||||
bool build(const std::string& outputPath);
|
||||
|
||||
/**
|
||||
* @brief 清空所有资源
|
||||
*/
|
||||
void clear();
|
||||
|
||||
/**
|
||||
* @brief 获取资源数量
|
||||
* @return 资源数量
|
||||
*/
|
||||
size_t count() const { return entries_.size(); }
|
||||
|
||||
/**
|
||||
* @brief 获取总原始大小
|
||||
* @return 总原始大小
|
||||
*/
|
||||
size_t totalOriginalSize() const { return totalOriginalSize_; }
|
||||
|
||||
/**
|
||||
* @brief 获取总压缩大小
|
||||
* @return 总压缩大小
|
||||
*/
|
||||
size_t totalCompressedSize() const { return totalCompressedSize_; }
|
||||
|
||||
private:
|
||||
struct BuilderEntry {
|
||||
AssetID id;
|
||||
std::vector<u8> data;
|
||||
std::vector<u8> compressedData;
|
||||
u64 offset;
|
||||
u32 compression;
|
||||
u32 flags;
|
||||
};
|
||||
|
||||
Compression compression_;
|
||||
int level_;
|
||||
std::string encryptKey_;
|
||||
Decryptor::Type encryptType_ = Decryptor::Type::None;
|
||||
std::vector<BuilderEntry> entries_;
|
||||
size_t totalOriginalSize_ = 0;
|
||||
size_t totalCompressedSize_ = 0;
|
||||
|
||||
/**
|
||||
* @brief 处理数据(压缩/加密)
|
||||
* @param data 原始数据
|
||||
* @return 处理后的数据
|
||||
*/
|
||||
std::vector<u8> processData(const std::vector<u8>& data);
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,270 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/core/types.h>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// AssetID - 强类型资源标识符
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 强类型资源ID
|
||||
*
|
||||
* 使用类型安全的ID替代裸字符串,支持:
|
||||
* - 哈希计算(用于快速比较和查找)
|
||||
* - 原始路径存储(用于调试和日志)
|
||||
* - 隐式转换和比较操作
|
||||
*/
|
||||
struct AssetID {
|
||||
u64 hash = 0;
|
||||
std::string path;
|
||||
|
||||
AssetID() = default;
|
||||
|
||||
/**
|
||||
* @brief 从路径构造资源ID
|
||||
* @param p 资源路径
|
||||
*/
|
||||
explicit AssetID(const std::string& p)
|
||||
: hash(hashPath(p)), path(p) {}
|
||||
|
||||
/**
|
||||
* @brief 从路径构造资源ID(移动语义)
|
||||
* @param p 资源路径
|
||||
*/
|
||||
explicit AssetID(std::string&& p)
|
||||
: hash(hashPath(p)), path(std::move(p)) {}
|
||||
|
||||
/**
|
||||
* @brief 计算路径哈希值
|
||||
* @param p 路径字符串
|
||||
* @return 64位哈希值
|
||||
*/
|
||||
static u64 hashPath(const std::string& p) {
|
||||
u64 result = 14695981039346656037ULL;
|
||||
for (char c : p) {
|
||||
result ^= static_cast<u64>(static_cast<u8>(c));
|
||||
result *= 1099511628211ULL;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 检查ID是否有效
|
||||
* @return 有效返回 true
|
||||
*/
|
||||
bool valid() const { return hash != 0 || !path.empty(); }
|
||||
|
||||
/**
|
||||
* @brief 布尔转换操作符
|
||||
*/
|
||||
explicit operator bool() const { return valid(); }
|
||||
|
||||
/**
|
||||
* @brief 相等比较
|
||||
*/
|
||||
bool operator==(const AssetID& other) const { return hash == other.hash; }
|
||||
|
||||
/**
|
||||
* @brief 不等比较
|
||||
*/
|
||||
bool operator!=(const AssetID& other) const { return hash != other.hash; }
|
||||
|
||||
/**
|
||||
* @brief 小于比较(用于有序容器)
|
||||
*/
|
||||
bool operator<(const AssetID& other) const { return hash < other.hash; }
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// AssetType - 资源类型枚举
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 资源类型枚举
|
||||
* 定义支持的资源类型,用于类型安全的资源加载和管理
|
||||
*/
|
||||
enum class AssetType : u8 {
|
||||
Unknown = 0,
|
||||
Texture = 1,
|
||||
Font = 2,
|
||||
Shader = 3,
|
||||
Audio = 4,
|
||||
Data = 5,
|
||||
Custom = 255
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 获取资源类型名称
|
||||
* @param type 资源类型
|
||||
* @return 类型名称字符串
|
||||
*/
|
||||
inline const char* assetTypeName(AssetType type) {
|
||||
switch (type) {
|
||||
case AssetType::Texture: return "Texture";
|
||||
case AssetType::Font: return "Font";
|
||||
case AssetType::Shader: return "Shader";
|
||||
case AssetType::Audio: return "Audio";
|
||||
case AssetType::Data: return "Data";
|
||||
case AssetType::Custom: return "Custom";
|
||||
default: return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// AssetState - 资源状态枚举
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 资源状态枚举
|
||||
* 定义资源的生命周期状态
|
||||
*/
|
||||
enum class AssetState : u8 {
|
||||
Unloaded = 0,
|
||||
Loading = 1,
|
||||
Loaded = 2,
|
||||
Failed = 3,
|
||||
Unloading = 4
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 获取资源状态名称
|
||||
* @param state 资源状态
|
||||
* @return 状态名称字符串
|
||||
*/
|
||||
inline const char* assetStateName(AssetState state) {
|
||||
switch (state) {
|
||||
case AssetState::Unloaded: return "Unloaded";
|
||||
case AssetState::Loading: return "Loading";
|
||||
case AssetState::Loaded: return "Loaded";
|
||||
case AssetState::Failed: return "Failed";
|
||||
case AssetState::Unloading: return "Unloading";
|
||||
default: return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Compression - 压缩算法枚举
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 压缩算法枚举
|
||||
* 定义支持的压缩算法类型
|
||||
*/
|
||||
enum class Compression : u8 {
|
||||
None = 0,
|
||||
Zstd = 1,
|
||||
LZ4 = 2,
|
||||
Zlib = 3
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 获取压缩算法名称
|
||||
* @param comp 压缩算法
|
||||
* @return 算法名称字符串
|
||||
*/
|
||||
inline const char* compressionName(Compression comp) {
|
||||
switch (comp) {
|
||||
case Compression::Zstd: return "Zstd";
|
||||
case Compression::LZ4: return "LZ4";
|
||||
case Compression::Zlib: return "Zlib";
|
||||
default: return "None";
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// CacheStats - 缓存统计结构
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 缓存统计信息
|
||||
* 用于监控资源缓存的使用情况和性能
|
||||
*/
|
||||
struct CacheStats {
|
||||
size_t bytes = 0;
|
||||
size_t limit = 0;
|
||||
size_t count = 0;
|
||||
size_t hits = 0;
|
||||
size_t misses = 0;
|
||||
|
||||
/**
|
||||
* @brief 计算缓存命中率
|
||||
* @return 命中率(0.0 - 1.0)
|
||||
*/
|
||||
float hitRate() const {
|
||||
size_t total = hits + misses;
|
||||
return total > 0 ? static_cast<float>(hits) / static_cast<float>(total) : 0.0f;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 计算缓存使用率
|
||||
* @return 使用率(0.0 - 1.0)
|
||||
*/
|
||||
float usage() const {
|
||||
return limit > 0 ? static_cast<float>(bytes) / static_cast<float>(limit) : 0.0f;
|
||||
}
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// AssetPackageHeader - 资源包头部信息
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 资源包头部信息
|
||||
* 用于识别压缩和加密类型
|
||||
*/
|
||||
struct AssetPackageHeader {
|
||||
u32 magic = 0;
|
||||
u32 version = 0;
|
||||
u32 compressionType = 0;
|
||||
u32 encryptionType = 0;
|
||||
u64 originalSize = 0;
|
||||
u64 compressedSize = 0;
|
||||
u8 checksum[32] = {};
|
||||
|
||||
static constexpr u32 MAGIC = 0x4B325045; // 'E2PK'
|
||||
|
||||
/**
|
||||
* @brief 检查头部是否有效
|
||||
* @return 有效返回 true
|
||||
*/
|
||||
bool valid() const { return magic == MAGIC; }
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// AssetPackageEntry - 资源包索引项
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 资源包索引项
|
||||
* 描述资源在包中的位置和属性
|
||||
*/
|
||||
struct AssetPackageEntry {
|
||||
AssetID id;
|
||||
u64 offset = 0;
|
||||
u64 size = 0;
|
||||
u64 originalSize = 0;
|
||||
u32 compression = 0;
|
||||
u32 flags = 0;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// std::hash 特化 - 支持在 unordered_map/unordered_set 中使用 AssetID
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
namespace std {
|
||||
|
||||
template<>
|
||||
struct hash<extra2d::AssetID> {
|
||||
size_t operator()(const extra2d::AssetID& id) const noexcept {
|
||||
return static_cast<size_t>(id.hash);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,397 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/asset/asset_types.h>
|
||||
#include <extra2d/core/types.h>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// DataProcessor - 数据处理器接口(装饰器模式)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 数据处理器接口
|
||||
*
|
||||
* 使用装饰器模式支持链式处理数据流。
|
||||
* 支持压缩、解压、加密、解密等操作的组合。
|
||||
*/
|
||||
class DataProcessor {
|
||||
public:
|
||||
virtual ~DataProcessor() = default;
|
||||
|
||||
/**
|
||||
* @brief 处理输入数据
|
||||
* @param input 输入数据
|
||||
* @return 处理后的数据
|
||||
*/
|
||||
virtual std::vector<u8> process(const std::vector<u8>& input) = 0;
|
||||
|
||||
/**
|
||||
* @brief 设置下一个处理器
|
||||
* @param next 下一个处理器
|
||||
*/
|
||||
void setNext(Unique<DataProcessor> next) { next_ = std::move(next); }
|
||||
|
||||
/**
|
||||
* @brief 获取下一个处理器
|
||||
* @return 下一个处理器的指针
|
||||
*/
|
||||
DataProcessor* next() const { return next_.get(); }
|
||||
|
||||
protected:
|
||||
/**
|
||||
* @brief 调用下一个处理器处理数据
|
||||
* @param input 输入数据
|
||||
* @return 处理后的数据,如果没有下一个处理器则返回原数据
|
||||
*/
|
||||
std::vector<u8> processNext(const std::vector<u8>& input) {
|
||||
return next_ ? next_->process(input) : input;
|
||||
}
|
||||
|
||||
Unique<DataProcessor> next_;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Decryptor - 解密器
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 解密处理器
|
||||
*
|
||||
* 使用 XOR 或 AES 算法解密数据。
|
||||
* 支持简单的 XOR 加密和 AES-256 加密。
|
||||
*/
|
||||
class Decryptor : public DataProcessor {
|
||||
public:
|
||||
/**
|
||||
* @brief 加密类型枚举
|
||||
*/
|
||||
enum class Type : u8 {
|
||||
None = 0,
|
||||
XOR = 1,
|
||||
AES256 = 2
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 构造解密器
|
||||
* @param key 解密密钥
|
||||
* @param type 加密类型
|
||||
*/
|
||||
explicit Decryptor(const std::string& key, Type type = Type::XOR);
|
||||
|
||||
/**
|
||||
* @brief 处理(解密)输入数据
|
||||
* @param input 加密的输入数据
|
||||
* @return 解密后的数据
|
||||
*/
|
||||
std::vector<u8> process(const std::vector<u8>& input) override;
|
||||
|
||||
/**
|
||||
* @brief 获取加密类型
|
||||
* @return 加密类型
|
||||
*/
|
||||
Type type() const { return type_; }
|
||||
|
||||
private:
|
||||
std::string key_;
|
||||
Type type_;
|
||||
|
||||
/**
|
||||
* @brief XOR 解密
|
||||
* @param input 输入数据
|
||||
* @return 解密后的数据
|
||||
*/
|
||||
std::vector<u8> decryptXOR(const std::vector<u8>& input);
|
||||
|
||||
/**
|
||||
* @brief AES-256 解密
|
||||
* @param input 输入数据
|
||||
* @return 解密后的数据
|
||||
*/
|
||||
std::vector<u8> decryptAES256(const std::vector<u8>& input);
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Decompressor - 解压器
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 解压处理器
|
||||
*
|
||||
* 支持多种压缩算法的解压操作。
|
||||
* 目前支持 Zstd、LZ4 和 Zlib。
|
||||
*/
|
||||
class Decompressor : public DataProcessor {
|
||||
public:
|
||||
/**
|
||||
* @brief 构造解压器
|
||||
* @param algo 压缩算法
|
||||
*/
|
||||
explicit Decompressor(Compression algo = Compression::Zstd);
|
||||
|
||||
/**
|
||||
* @brief 处理(解压)输入数据
|
||||
* @param input 压缩的输入数据
|
||||
* @return 解压后的数据
|
||||
*/
|
||||
std::vector<u8> process(const std::vector<u8>& input) override;
|
||||
|
||||
/**
|
||||
* @brief 获取压缩算法
|
||||
* @return 压缩算法
|
||||
*/
|
||||
Compression algorithm() const { return algo_; }
|
||||
|
||||
private:
|
||||
Compression algo_;
|
||||
|
||||
/**
|
||||
* @brief Zstd 解压
|
||||
* @param input 压缩数据
|
||||
* @return 解压后的数据
|
||||
*/
|
||||
std::vector<u8> decompressZstd(const std::vector<u8>& input);
|
||||
|
||||
/**
|
||||
* @brief LZ4 解压
|
||||
* @param input 压缩数据
|
||||
* @return 解压后的数据
|
||||
*/
|
||||
std::vector<u8> decompressLZ4(const std::vector<u8>& input);
|
||||
|
||||
/**
|
||||
* @brief Zlib 解压
|
||||
* @param input 压缩数据
|
||||
* @return 解压后的数据
|
||||
*/
|
||||
std::vector<u8> decompressZlib(const std::vector<u8>& input);
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Encryptor - 加密器
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 加密处理器
|
||||
*
|
||||
* 使用 XOR 或 AES 算法加密数据。
|
||||
*/
|
||||
class Encryptor : public DataProcessor {
|
||||
public:
|
||||
/**
|
||||
* @brief 构造加密器
|
||||
* @param key 加密密钥
|
||||
* @param type 加密类型
|
||||
*/
|
||||
explicit Encryptor(const std::string& key, Decryptor::Type type = Decryptor::Type::XOR);
|
||||
|
||||
/**
|
||||
* @brief 处理(加密)输入数据
|
||||
* @param input 原始输入数据
|
||||
* @return 加密后的数据
|
||||
*/
|
||||
std::vector<u8> process(const std::vector<u8>& input) override;
|
||||
|
||||
/**
|
||||
* @brief 获取加密类型
|
||||
* @return 加密类型
|
||||
*/
|
||||
Decryptor::Type type() const { return type_; }
|
||||
|
||||
private:
|
||||
std::string key_;
|
||||
Decryptor::Type type_;
|
||||
|
||||
/**
|
||||
* @brief XOR 加密
|
||||
* @param input 输入数据
|
||||
* @return 加密后的数据
|
||||
*/
|
||||
std::vector<u8> encryptXOR(const std::vector<u8>& input);
|
||||
|
||||
/**
|
||||
* @brief AES-256 加密
|
||||
* @param input 输入数据
|
||||
* @return 加密后的数据
|
||||
*/
|
||||
std::vector<u8> encryptAES256(const std::vector<u8>& input);
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Compressor - 压缩器
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 压缩处理器
|
||||
*
|
||||
* 支持多种压缩算法的压缩操作。
|
||||
*/
|
||||
class Compressor : public DataProcessor {
|
||||
public:
|
||||
/**
|
||||
* @brief 构造压缩器
|
||||
* @param algo 压缩算法
|
||||
* @param level 压缩级别(1-22,仅对某些算法有效)
|
||||
*/
|
||||
explicit Compressor(Compression algo = Compression::Zstd, int level = 3);
|
||||
|
||||
/**
|
||||
* @brief 处理(压缩)输入数据
|
||||
* @param input 原始输入数据
|
||||
* @return 压缩后的数据
|
||||
*/
|
||||
std::vector<u8> process(const std::vector<u8>& input) override;
|
||||
|
||||
/**
|
||||
* @brief 获取压缩算法
|
||||
* @return 压缩算法
|
||||
*/
|
||||
Compression algorithm() const { return algo_; }
|
||||
|
||||
private:
|
||||
Compression algo_;
|
||||
int level_;
|
||||
|
||||
/**
|
||||
* @brief Zstd 压缩
|
||||
* @param input 原始数据
|
||||
* @return 压缩后的数据
|
||||
*/
|
||||
std::vector<u8> compressZstd(const std::vector<u8>& input);
|
||||
|
||||
/**
|
||||
* @brief LZ4 压缩
|
||||
* @param input 原始数据
|
||||
* @return 压缩后的数据
|
||||
*/
|
||||
std::vector<u8> compressLZ4(const std::vector<u8>& input);
|
||||
|
||||
/**
|
||||
* @brief Zlib 压缩
|
||||
* @param input 原始数据
|
||||
* @return 压缩后的数据
|
||||
*/
|
||||
std::vector<u8> compressZlib(const std::vector<u8>& input);
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// DataPipe - 数据处理管道
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 数据处理管道
|
||||
*
|
||||
* 使用流式 API 构建数据处理链。
|
||||
* 支持链式调用添加多个处理器。
|
||||
*
|
||||
* @example
|
||||
* DataPipe pipe;
|
||||
* pipe.decrypt("secret-key").decompress(Compression::Zstd);
|
||||
* auto result = pipe.process(data);
|
||||
*/
|
||||
class DataPipe {
|
||||
public:
|
||||
DataPipe() = default;
|
||||
|
||||
/**
|
||||
* @brief 移动构造函数
|
||||
* @param other 其他管道
|
||||
*/
|
||||
DataPipe(DataPipe&& other) noexcept = default;
|
||||
|
||||
/**
|
||||
* @brief 移动赋值操作符
|
||||
* @param other 其他管道
|
||||
* @return 当前管道引用
|
||||
*/
|
||||
DataPipe& operator=(DataPipe&& other) noexcept = default;
|
||||
|
||||
/**
|
||||
* @brief 添加解密处理器
|
||||
* @param key 解密密钥
|
||||
* @param type 加密类型
|
||||
* @return 当前管道引用(支持链式调用)
|
||||
*/
|
||||
DataPipe& decrypt(const std::string& key, Decryptor::Type type = Decryptor::Type::XOR);
|
||||
|
||||
/**
|
||||
* @brief 添加解压处理器
|
||||
* @param algo 压缩算法
|
||||
* @return 当前管道引用(支持链式调用)
|
||||
*/
|
||||
DataPipe& decompress(Compression algo);
|
||||
|
||||
/**
|
||||
* @brief 添加加密处理器
|
||||
* @param key 加密密钥
|
||||
* @param type 加密类型
|
||||
* @return 当前管道引用(支持链式调用)
|
||||
*/
|
||||
DataPipe& encrypt(const std::string& key, Decryptor::Type type = Decryptor::Type::XOR);
|
||||
|
||||
/**
|
||||
* @brief 添加压缩处理器
|
||||
* @param algo 压缩算法
|
||||
* @param level 压缩级别
|
||||
* @return 当前管道引用(支持链式调用)
|
||||
*/
|
||||
DataPipe& compress(Compression algo, int level = 3);
|
||||
|
||||
/**
|
||||
* @brief 添加自定义处理器
|
||||
* @param processor 处理器
|
||||
* @return 当前管道引用(支持链式调用)
|
||||
*/
|
||||
DataPipe& add(Unique<DataProcessor> processor);
|
||||
|
||||
/**
|
||||
* @brief 处理数据
|
||||
* @param input 输入数据
|
||||
* @return 处理后的数据
|
||||
*/
|
||||
std::vector<u8> process(const std::vector<u8>& input);
|
||||
|
||||
/**
|
||||
* @brief 清空所有处理器
|
||||
*/
|
||||
void clear();
|
||||
|
||||
/**
|
||||
* @brief 检查管道是否为空
|
||||
* @return 为空返回 true
|
||||
*/
|
||||
bool empty() const { return processors_.empty(); }
|
||||
|
||||
/**
|
||||
* @brief 获取处理器数量
|
||||
* @return 处理器数量
|
||||
*/
|
||||
size_t size() const { return processors_.size(); }
|
||||
|
||||
private:
|
||||
std::vector<Unique<DataProcessor>> processors_;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 工具函数
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 计算数据的 SHA-256 校验和
|
||||
* @param data 输入数据
|
||||
* @return 32字节的校验和
|
||||
*/
|
||||
std::vector<u8> computeChecksum(const std::vector<u8>& data);
|
||||
|
||||
/**
|
||||
* @brief 验证数据的 SHA-256 校验和
|
||||
* @param data 输入数据
|
||||
* @param checksum 预期的校验和
|
||||
* @return 匹配返回 true
|
||||
*/
|
||||
bool verifyChecksum(const std::vector<u8>& data, const std::vector<u8>& checksum);
|
||||
|
||||
}
|
||||
|
|
@ -155,17 +155,4 @@ inline constexpr Color Coral{1.0f, 0.498f, 0.314f, 1.0f};
|
|||
inline constexpr Color Transparent{0.0f, 0.0f, 0.0f, 0.0f};
|
||||
} // namespace Colors
|
||||
|
||||
// 为了向后兼容,在 Color 结构体内提供静态引用
|
||||
struct ColorConstants {
|
||||
static const Color &White;
|
||||
static const Color &Black;
|
||||
static const Color &Red;
|
||||
static const Color &Green;
|
||||
static const Color &Blue;
|
||||
static const Color &Yellow;
|
||||
static const Color &Cyan;
|
||||
static const Color &Magenta;
|
||||
static const Color &Transparent;
|
||||
};
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
|
|||
|
|
@ -330,6 +330,221 @@ inline float degrees(float radians) { return radians * RAD_TO_DEG; }
|
|||
|
||||
inline float radians(float degrees) { return degrees * DEG_TO_RAD; }
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 角度工具函数
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 规范化角度到 [0, 360) 范围
|
||||
* @param degrees 输入角度(度数)
|
||||
* @return 规范化后的角度,范围 [0, 360)
|
||||
*/
|
||||
inline float normalizeAngle360(float degrees) {
|
||||
degrees = std::fmod(degrees, 360.0f);
|
||||
if (degrees < 0.0f) {
|
||||
degrees += 360.0f;
|
||||
}
|
||||
return degrees;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 规范化角度到 [-180, 180) 范围
|
||||
* @param degrees 输入角度(度数)
|
||||
* @return 规范化后的角度,范围 [-180, 180)
|
||||
*/
|
||||
inline float normalizeAngle180(float degrees) {
|
||||
degrees = std::fmod(degrees + 180.0f, 360.0f);
|
||||
if (degrees < 0.0f) {
|
||||
degrees += 360.0f;
|
||||
}
|
||||
return degrees - 180.0f;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 计算两个角度之间的最短差值
|
||||
* @param from 起始角度(度数)
|
||||
* @param to 目标角度(度数)
|
||||
* @return 从 from 到 to 的最短角度差,范围 [-180, 180]
|
||||
*/
|
||||
inline float angleDifference(float from, float to) {
|
||||
float diff = normalizeAngle360(to - from);
|
||||
if (diff > 180.0f) {
|
||||
diff -= 360.0f;
|
||||
}
|
||||
return diff;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 线性插值角度
|
||||
* @param from 起始角度(度数)
|
||||
* @param to 目标角度(度数)
|
||||
* @param t 插值因子 [0, 1]
|
||||
* @return 插值后的角度
|
||||
*/
|
||||
inline float lerpAngle(float from, float to, float t) {
|
||||
return from + angleDifference(from, to) * t;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 向量工具函数
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 计算方向向量(从 from 指向 to 的单位向量)
|
||||
* @param from 起始点
|
||||
* @param to 目标点
|
||||
* @return 归一化的方向向量
|
||||
*/
|
||||
inline Vec2 direction(const Vec2 &from, const Vec2 &to) {
|
||||
return (to - from).normalized();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 计算两点之间的角度
|
||||
* @param from 起始点
|
||||
* @param to 目标点
|
||||
* @return 角度(度数),范围 [-180, 180]
|
||||
*/
|
||||
inline float angleBetween(const Vec2 &from, const Vec2 &to) {
|
||||
Vec2 dir = to - from;
|
||||
return std::atan2(dir.y, dir.x) * RAD_TO_DEG;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 根据角度创建方向向量
|
||||
* @param degrees 角度(度数),0度指向右方,逆时针为正
|
||||
* @return 单位方向向量
|
||||
*/
|
||||
inline Vec2 angleToVector(float degrees) {
|
||||
float rad = degrees * DEG_TO_RAD;
|
||||
return {std::cos(rad), std::sin(rad)};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 将向量旋转指定角度
|
||||
* @param v 原始向量
|
||||
* @param degrees 旋转角度(度数),正值为逆时针旋转
|
||||
* @return 旋转后的向量
|
||||
*/
|
||||
inline Vec2 rotateVector(const Vec2 &v, float degrees) {
|
||||
float rad = degrees * DEG_TO_RAD;
|
||||
float cosA = std::cos(rad);
|
||||
float sinA = std::sin(rad);
|
||||
return {v.x * cosA - v.y * sinA, v.x * sinA + v.y * cosA};
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 坐标系转换工具
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief Y轴向上坐标转Y轴向下坐标
|
||||
* @param pos Y轴向上坐标系中的位置
|
||||
* @param height 画布/屏幕高度
|
||||
* @return Y轴向下坐标系中的位置
|
||||
*/
|
||||
inline Vec2 flipY(const Vec2 &pos, float height) {
|
||||
return {pos.x, height - pos.y};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Y轴向下坐标转Y轴向上坐标
|
||||
* @param pos Y轴向下坐标系中的位置
|
||||
* @param height 画布/屏幕高度
|
||||
* @return Y轴向上坐标系中的位置
|
||||
*/
|
||||
inline Vec2 unflipY(const Vec2 &pos, float height) {
|
||||
return {pos.x, height - pos.y};
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 矩阵工具函数
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 从变换矩阵提取位置
|
||||
* @param matrix 4x4变换矩阵
|
||||
* @return 提取的位置向量
|
||||
*/
|
||||
inline Vec2 extractPosition(const glm::mat4 &matrix) {
|
||||
return {matrix[3][0], matrix[3][1]};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 从变换矩阵提取缩放
|
||||
* @param matrix 4x4变换矩阵
|
||||
* @return 提取的缩放向量
|
||||
*/
|
||||
inline Vec2 extractScale(const glm::mat4 &matrix) {
|
||||
float scaleX = std::sqrt(matrix[0][0] * matrix[0][0] + matrix[0][1] * matrix[0][1]);
|
||||
float scaleY = std::sqrt(matrix[1][0] * matrix[1][0] + matrix[1][1] * matrix[1][1]);
|
||||
return {scaleX, scaleY};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 从变换矩阵提取旋转角度
|
||||
* @param matrix 4x4变换矩阵
|
||||
* @return 提取的旋转角度(度数)
|
||||
*/
|
||||
inline float extractRotation(const glm::mat4 &matrix) {
|
||||
return std::atan2(matrix[0][1], matrix[0][0]) * RAD_TO_DEG;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 碰撞检测工具
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 判断点是否在矩形内
|
||||
* @param point 要检测的点
|
||||
* @param rect 矩形区域
|
||||
* @return 如果点在矩形内返回 true,否则返回 false
|
||||
*/
|
||||
inline bool pointInRect(const Vec2 &point, const Rect &rect) {
|
||||
return point.x >= rect.left() && point.x <= rect.right() &&
|
||||
point.y >= rect.top() && point.y <= rect.bottom();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 判断点是否在圆内
|
||||
* @param point 要检测的点
|
||||
* @param center 圆心
|
||||
* @param radius 圆的半径
|
||||
* @return 如果点在圆内返回 true,否则返回 false
|
||||
*/
|
||||
inline bool pointInCircle(const Vec2 &point, const Vec2 ¢er, float radius) {
|
||||
float dx = point.x - center.x;
|
||||
float dy = point.y - center.y;
|
||||
return (dx * dx + dy * dy) <= (radius * radius);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 判断两个矩形是否相交
|
||||
* @param a 第一个矩形
|
||||
* @param b 第二个矩形
|
||||
* @return 如果矩形相交返回 true,否则返回 false
|
||||
*/
|
||||
inline bool rectsIntersect(const Rect &a, const Rect &b) {
|
||||
return a.intersects(b);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 判断两个圆是否相交
|
||||
* @param center1 第一个圆的圆心
|
||||
* @param radius1 第一个圆的半径
|
||||
* @param center2 第二个圆的圆心
|
||||
* @param radius2 第二个圆的半径
|
||||
* @return 如果圆相交返回 true,否则返回 false
|
||||
*/
|
||||
inline bool circlesIntersect(const Vec2 ¢er1, float radius1,
|
||||
const Vec2 ¢er2, float radius2) {
|
||||
float dx = center2.x - center1.x;
|
||||
float dy = center2.y - center1.y;
|
||||
float distSq = dx * dx + dy * dy;
|
||||
float radiusSum = radius1 + radius2;
|
||||
return distSq <= (radiusSum * radiusSum);
|
||||
}
|
||||
|
||||
} // namespace math
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
|
|||
|
|
@ -0,0 +1,82 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/core/types.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <typeindex>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
class Application;
|
||||
|
||||
/**
|
||||
* @brief 模块基类
|
||||
* 所有模块必须继承此类
|
||||
*/
|
||||
class Module {
|
||||
public:
|
||||
virtual ~Module() = default;
|
||||
|
||||
/**
|
||||
* @brief 初始化模块
|
||||
* @return 初始化成功返回 true
|
||||
*/
|
||||
virtual bool init() = 0;
|
||||
|
||||
/**
|
||||
* @brief 关闭模块
|
||||
*/
|
||||
virtual void shutdown() = 0;
|
||||
|
||||
/**
|
||||
* @brief 检查模块是否已初始化
|
||||
* @return 已初始化返回 true
|
||||
*/
|
||||
virtual bool ok() const = 0;
|
||||
|
||||
/**
|
||||
* @brief 获取模块名称
|
||||
* @return 模块名称
|
||||
*/
|
||||
virtual const char* name() const = 0;
|
||||
|
||||
/**
|
||||
* @brief 获取模块优先级(数值越小越优先)
|
||||
* @return 优先级值
|
||||
*/
|
||||
virtual int priority() const { return 100; }
|
||||
|
||||
/**
|
||||
* @brief 获取模块依赖列表
|
||||
* @return 依赖模块类型列表
|
||||
*/
|
||||
virtual std::vector<std::type_index> deps() const { return {}; }
|
||||
|
||||
/**
|
||||
* @brief 检查模块是否支持并行初始化
|
||||
* @return 支持并行初始化返回 true
|
||||
*/
|
||||
virtual bool parallel() const { return true; }
|
||||
|
||||
/**
|
||||
* @brief 设置所属Application
|
||||
* @param app Application指针
|
||||
*/
|
||||
void setApp(class Application* app) { app_ = app; }
|
||||
|
||||
/**
|
||||
* @brief 获取Application
|
||||
* @return Application指针
|
||||
*/
|
||||
class Application* app() const { return app_; }
|
||||
|
||||
protected:
|
||||
class Application* app_ = nullptr;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 模块工厂函数类型
|
||||
*/
|
||||
using ModuleFactory = std::function<Unique<Module>()>;
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <extra2d/core/types.h>
|
||||
#include <queue>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
/**
|
||||
* @brief 固定大小对象池
|
||||
* @tparam T 对象类型
|
||||
* @tparam Size 池大小
|
||||
*/
|
||||
template <typename T, size_t Size> class ObjectPool {
|
||||
public:
|
||||
ObjectPool() {
|
||||
for (size_t i = 0; i < Size; ++i) {
|
||||
available_.push(&pool_[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取对象
|
||||
* @return 对象指针,池耗尽返回nullptr
|
||||
*/
|
||||
T *acquire() {
|
||||
if (available_.empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
T *obj = available_.front();
|
||||
available_.pop();
|
||||
return obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 释放对象回池
|
||||
* @param obj 对象指针
|
||||
*/
|
||||
void release(T *obj) {
|
||||
if (obj >= pool_ && obj < pool_ + Size) {
|
||||
obj->~T();
|
||||
new (obj) T();
|
||||
available_.push(obj);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取可用对象数量
|
||||
*/
|
||||
size_t available() const { return available_.size(); }
|
||||
|
||||
/**
|
||||
* @brief 获取池总大小
|
||||
*/
|
||||
static constexpr size_t capacity() { return Size; }
|
||||
|
||||
private:
|
||||
alignas(alignof(T)) std::array<u8, sizeof(T) * Size> storage_;
|
||||
T *pool_ = reinterpret_cast<T *>(storage_.data());
|
||||
std::queue<T *> available_;
|
||||
};
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -0,0 +1,170 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/core/module.h>
|
||||
#include <extra2d/core/types.h>
|
||||
#include <array>
|
||||
#include <typeindex>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <future>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
class Application;
|
||||
|
||||
/**
|
||||
* @brief 编译期类型ID生成器
|
||||
*/
|
||||
using TypeId = size_t;
|
||||
|
||||
namespace detail {
|
||||
inline TypeId nextTypeId() {
|
||||
static TypeId id = 0;
|
||||
return ++id;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
inline TypeId getTypeId() {
|
||||
static TypeId id = nextTypeId();
|
||||
return id;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 模块注册表
|
||||
* 管理模块的注册、拓扑排序和生命周期
|
||||
*/
|
||||
class Registry {
|
||||
public:
|
||||
static constexpr size_t MAX_MODULES = 64;
|
||||
|
||||
static Registry& instance();
|
||||
|
||||
Registry(const Registry&) = delete;
|
||||
Registry& operator=(const Registry&) = delete;
|
||||
|
||||
/**
|
||||
* @brief 注册模块
|
||||
* @tparam T 模块类型
|
||||
* @tparam Args 构造函数参数类型
|
||||
* @param args 构造函数参数
|
||||
* @return 模块指针
|
||||
*/
|
||||
template<typename T, typename... Args>
|
||||
T* use(Args&&... args) {
|
||||
static_assert(std::is_base_of_v<Module, T>, "T must derive from Module");
|
||||
|
||||
TypeId typeId = detail::getTypeId<T>();
|
||||
|
||||
// 数组查找,O(n) 但 n 很小,缓存友好
|
||||
for (size_t i = 0; i < moduleCount_; ++i) {
|
||||
if (modules_[i].id == typeId) {
|
||||
return static_cast<T*>(modules_[i].module.get());
|
||||
}
|
||||
}
|
||||
|
||||
// 添加新模块
|
||||
if (moduleCount_ >= MAX_MODULES) {
|
||||
return nullptr; // 模块数量超过上限
|
||||
}
|
||||
|
||||
auto module = ptr::makeUnique<T>(std::forward<Args>(args)...);
|
||||
T* ptr = module.get();
|
||||
module->setApp(app_);
|
||||
|
||||
modules_[moduleCount_].id = typeId;
|
||||
modules_[moduleCount_].module = std::move(module);
|
||||
modules_[moduleCount_].valid = true;
|
||||
++moduleCount_;
|
||||
|
||||
return ptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取模块
|
||||
* @tparam T 模块类型
|
||||
* @return 模块指针,不存在返回 nullptr
|
||||
*/
|
||||
template<typename T>
|
||||
T* get() const {
|
||||
TypeId typeId = detail::getTypeId<T>();
|
||||
|
||||
for (size_t i = 0; i < moduleCount_; ++i) {
|
||||
if (modules_[i].id == typeId && modules_[i].valid) {
|
||||
return static_cast<T*>(modules_[i].module.get());
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取模块(基类版本)
|
||||
* @param typeIdx 类型索引
|
||||
* @return 模块指针
|
||||
*/
|
||||
Module* get(std::type_index typeIdx) const {
|
||||
// 这里仍然使用type_index作为后备方案
|
||||
for (size_t i = 0; i < moduleCount_; ++i) {
|
||||
if (modules_[i].valid &&
|
||||
std::type_index(typeid(*modules_[i].module)) == typeIdx) {
|
||||
return modules_[i].module.get();
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 设置Application
|
||||
*/
|
||||
void setApp(Application* app) { app_ = app; }
|
||||
|
||||
/**
|
||||
* @brief 初始化所有模块(按优先级拓扑排序,支持并行初始化)
|
||||
* @return 初始化成功返回 true
|
||||
*/
|
||||
bool init();
|
||||
|
||||
/**
|
||||
* @brief 关闭所有模块
|
||||
*/
|
||||
void shutdown();
|
||||
|
||||
/**
|
||||
* @brief 清空所有模块
|
||||
*/
|
||||
void clear();
|
||||
|
||||
/**
|
||||
* @brief 获取模块数量
|
||||
*/
|
||||
size_t size() const { return moduleCount_; }
|
||||
|
||||
private:
|
||||
Registry() = default;
|
||||
~Registry() = default;
|
||||
|
||||
struct ModuleEntry {
|
||||
TypeId id = 0;
|
||||
Unique<Module> module;
|
||||
bool valid = false;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 拓扑排序模块
|
||||
* @return 排序后的模块列表
|
||||
*/
|
||||
std::vector<Module*> sort();
|
||||
|
||||
/**
|
||||
* @brief 按层级对模块进行分组
|
||||
* 同一层级的模块没有相互依赖,可以并行初始化
|
||||
* @return 按层级分组的模块列表
|
||||
*/
|
||||
std::vector<std::vector<Module*>> group();
|
||||
|
||||
std::array<ModuleEntry, MAX_MODULES> modules_;
|
||||
size_t moduleCount_ = 0;
|
||||
Application* app_ = nullptr;
|
||||
};
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -0,0 +1,398 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/core/types.h>
|
||||
#include <string>
|
||||
#include <variant>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
/**
|
||||
* @brief 错误码枚举
|
||||
*/
|
||||
enum class ErrorCode {
|
||||
None = 0,
|
||||
Unknown = 1,
|
||||
InvalidArgument = 2,
|
||||
OutOfMemory = 3,
|
||||
FileNotFound = 4,
|
||||
PermissionDenied = 5,
|
||||
NotImplemented = 6,
|
||||
AlreadyExists = 7,
|
||||
NotInitialized = 8,
|
||||
AlreadyInitialized = 9,
|
||||
OperationFailed = 10,
|
||||
Timeout = 11,
|
||||
Cancelled = 12,
|
||||
InvalidState = 13,
|
||||
ResourceExhausted = 14,
|
||||
Unavailable = 15,
|
||||
DataLoss = 16,
|
||||
Unauthenticated = 17,
|
||||
PermissionDenied2 = 18,
|
||||
ResourceNotFound = 19,
|
||||
Aborted = 20,
|
||||
OutOfRange = 21,
|
||||
Unimplemented = 22,
|
||||
Internal = 23,
|
||||
DataCorrupted = 24,
|
||||
RequestTooLarge = 25,
|
||||
ResourceBusy = 26,
|
||||
QuotaExceeded = 27,
|
||||
DeadlineExceeded = 28,
|
||||
LoadBalancing = 29,
|
||||
NetworkError = 30,
|
||||
ProtocolError = 31,
|
||||
ServiceUnavailable = 32,
|
||||
GatewayError = 33,
|
||||
RateLimited = 34,
|
||||
BadRequest = 35,
|
||||
Unauthorized = 36,
|
||||
Forbidden = 37,
|
||||
NotFound = 38,
|
||||
MethodNotAllowed = 39,
|
||||
Conflict = 40,
|
||||
Gone = 41,
|
||||
LengthRequired = 42,
|
||||
PreconditionFailed = 43,
|
||||
PayloadTooLarge = 44,
|
||||
UriTooLong = 45,
|
||||
UnsupportedMediaType = 46,
|
||||
RangeNotSatisfiable = 47,
|
||||
ExpectationFailed = 48,
|
||||
ImATeapot = 49,
|
||||
MisdirectedRequest = 50,
|
||||
UnprocessableEntity = 51,
|
||||
Locked = 52,
|
||||
FailedDependency = 53,
|
||||
TooEarly = 54,
|
||||
UpgradeRequired = 55,
|
||||
PreconditionRequired = 56,
|
||||
TooManyRequests = 57,
|
||||
RequestHeaderFieldsTooLarge = 58,
|
||||
UnavailableForLegalReasons = 59
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 错误信息结构
|
||||
*/
|
||||
struct Error {
|
||||
ErrorCode code = ErrorCode::None;
|
||||
std::string message;
|
||||
std::string file;
|
||||
int line = 0;
|
||||
|
||||
Error() = default;
|
||||
Error(ErrorCode c, const std::string& msg) : code(c), message(msg) {}
|
||||
Error(ErrorCode c, const std::string& msg, const std::string& f, int l)
|
||||
: code(c), message(msg), file(f), line(l) {}
|
||||
|
||||
bool ok() const { return code == ErrorCode::None; }
|
||||
|
||||
static Error none() { return Error(); }
|
||||
static Error unknown(const std::string& msg) { return Error(ErrorCode::Unknown, msg); }
|
||||
static Error invalidArgument(const std::string& msg) { return Error(ErrorCode::InvalidArgument, msg); }
|
||||
static Error outOfMemory(const std::string& msg) { return Error(ErrorCode::OutOfMemory, msg); }
|
||||
static Error fileNotFound(const std::string& msg) { return Error(ErrorCode::FileNotFound, msg); }
|
||||
static Error permissionDenied(const std::string& msg) { return Error(ErrorCode::PermissionDenied, msg); }
|
||||
static Error notImplemented(const std::string& msg) { return Error(ErrorCode::NotImplemented, msg); }
|
||||
static Error alreadyExists(const std::string& msg) { return Error(ErrorCode::AlreadyExists, msg); }
|
||||
static Error notInitialized(const std::string& msg) { return Error(ErrorCode::NotInitialized, msg); }
|
||||
static Error alreadyInitialized(const std::string& msg) { return Error(ErrorCode::AlreadyInitialized, msg); }
|
||||
static Error operationFailed(const std::string& msg) { return Error(ErrorCode::OperationFailed, msg); }
|
||||
static Error timeout(const std::string& msg) { return Error(ErrorCode::Timeout, msg); }
|
||||
static Error cancelled(const std::string& msg) { return Error(ErrorCode::Cancelled, msg); }
|
||||
static Error invalidState(const std::string& msg) { return Error(ErrorCode::InvalidState, msg); }
|
||||
static Error resourceExhausted(const std::string& msg) { return Error(ErrorCode::ResourceExhausted, msg); }
|
||||
static Error unavailable(const std::string& msg) { return Error(ErrorCode::Unavailable, msg); }
|
||||
static Error dataLoss(const std::string& msg) { return Error(ErrorCode::DataLoss, msg); }
|
||||
static Error unauthenticated(const std::string& msg) { return Error(ErrorCode::Unauthenticated, msg); }
|
||||
static Error permissionDenied2(const std::string& msg) { return Error(ErrorCode::PermissionDenied2, msg); }
|
||||
static Error resourceNotFound(const std::string& msg) { return Error(ErrorCode::ResourceNotFound, msg); }
|
||||
static Error aborted(const std::string& msg) { return Error(ErrorCode::Aborted, msg); }
|
||||
static Error outOfRange(const std::string& msg) { return Error(ErrorCode::OutOfRange, msg); }
|
||||
static Error unimplemented(const std::string& msg) { return Error(ErrorCode::Unimplemented, msg); }
|
||||
static Error internal(const std::string& msg) { return Error(ErrorCode::Internal, msg); }
|
||||
static Error dataCorrupted(const std::string& msg) { return Error(ErrorCode::DataCorrupted, msg); }
|
||||
static Error requestTooLarge(const std::string& msg) { return Error(ErrorCode::RequestTooLarge, msg); }
|
||||
static Error resourceBusy(const std::string& msg) { return Error(ErrorCode::ResourceBusy, msg); }
|
||||
static Error quotaExceeded(const std::string& msg) { return Error(ErrorCode::QuotaExceeded, msg); }
|
||||
static Error deadlineExceeded(const std::string& msg) { return Error(ErrorCode::DeadlineExceeded, msg); }
|
||||
static Error loadBalancing(const std::string& msg) { return Error(ErrorCode::LoadBalancing, msg); }
|
||||
static Error networkError(const std::string& msg) { return Error(ErrorCode::NetworkError, msg); }
|
||||
static Error protocolError(const std::string& msg) { return Error(ErrorCode::ProtocolError, msg); }
|
||||
static Error serviceUnavailable(const std::string& msg) { return Error(ErrorCode::ServiceUnavailable, msg); }
|
||||
static Error gatewayError(const std::string& msg) { return Error(ErrorCode::GatewayError, msg); }
|
||||
static Error rateLimited(const std::string& msg) { return Error(ErrorCode::RateLimited, msg); }
|
||||
static Error badRequest(const std::string& msg) { return Error(ErrorCode::BadRequest, msg); }
|
||||
static Error unauthorized(const std::string& msg) { return Error(ErrorCode::Unauthorized, msg); }
|
||||
static Error forbidden(const std::string& msg) { return Error(ErrorCode::Forbidden, msg); }
|
||||
static Error notFound(const std::string& msg) { return Error(ErrorCode::NotFound, msg); }
|
||||
static Error methodNotAllowed(const std::string& msg) { return Error(ErrorCode::MethodNotAllowed, msg); }
|
||||
static Error conflict(const std::string& msg) { return Error(ErrorCode::Conflict, msg); }
|
||||
static Error gone(const std::string& msg) { return Error(ErrorCode::Gone, msg); }
|
||||
static Error lengthRequired(const std::string& msg) { return Error(ErrorCode::LengthRequired, msg); }
|
||||
static Error preconditionFailed(const std::string& msg) { return Error(ErrorCode::PreconditionFailed, msg); }
|
||||
static Error payloadTooLarge(const std::string& msg) { return Error(ErrorCode::PayloadTooLarge, msg); }
|
||||
static Error uriTooLong(const std::string& msg) { return Error(ErrorCode::UriTooLong, msg); }
|
||||
static Error unsupportedMediaType(const std::string& msg) { return Error(ErrorCode::UnsupportedMediaType, msg); }
|
||||
static Error rangeNotSatisfiable(const std::string& msg) { return Error(ErrorCode::RangeNotSatisfiable, msg); }
|
||||
static Error expectationFailed(const std::string& msg) { return Error(ErrorCode::ExpectationFailed, msg); }
|
||||
static Error imATeapot(const std::string& msg) { return Error(ErrorCode::ImATeapot, msg); }
|
||||
static Error misdirectedRequest(const std::string& msg) { return Error(ErrorCode::MisdirectedRequest, msg); }
|
||||
static Error unprocessableEntity(const std::string& msg) { return Error(ErrorCode::UnprocessableEntity, msg); }
|
||||
static Error locked(const std::string& msg) { return Error(ErrorCode::Locked, msg); }
|
||||
static Error failedDependency(const std::string& msg) { return Error(ErrorCode::FailedDependency, msg); }
|
||||
static Error tooEarly(const std::string& msg) { return Error(ErrorCode::TooEarly, msg); }
|
||||
static Error upgradeRequired(const std::string& msg) { return Error(ErrorCode::UpgradeRequired, msg); }
|
||||
static Error preconditionRequired(const std::string& msg) { return Error(ErrorCode::PreconditionRequired, msg); }
|
||||
static Error tooManyRequests(const std::string& msg) { return Error(ErrorCode::TooManyRequests, msg); }
|
||||
static Error requestHeaderFieldsTooLarge(const std::string& msg) { return Error(ErrorCode::RequestHeaderFieldsTooLarge, msg); }
|
||||
static Error unavailableForLegalReasons(const std::string& msg) { return Error(ErrorCode::UnavailableForLegalReasons, msg); }
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Result类型,用于错误处理
|
||||
* @tparam T 成功时的值类型
|
||||
* @tparam E 错误类型,默认为Error
|
||||
*/
|
||||
template<typename T, typename E = Error>
|
||||
class Result {
|
||||
public:
|
||||
Result() : hasValue_(false) {
|
||||
new (&storage_.error) E();
|
||||
}
|
||||
|
||||
~Result() {
|
||||
if (hasValue_) {
|
||||
storage_.value.~T();
|
||||
} else {
|
||||
storage_.error.~E();
|
||||
}
|
||||
}
|
||||
|
||||
Result(const Result& other) : hasValue_(other.hasValue_) {
|
||||
if (hasValue_) {
|
||||
new (&storage_.value) T(other.storage_.value);
|
||||
} else {
|
||||
new (&storage_.error) E(other.storage_.error);
|
||||
}
|
||||
}
|
||||
|
||||
Result(Result&& other) noexcept : hasValue_(other.hasValue_) {
|
||||
if (hasValue_) {
|
||||
new (&storage_.value) T(std::move(other.storage_.value));
|
||||
} else {
|
||||
new (&storage_.error) E(std::move(other.storage_.error));
|
||||
}
|
||||
}
|
||||
|
||||
Result& operator=(const Result& other) {
|
||||
if (this != &other) {
|
||||
this->~Result();
|
||||
hasValue_ = other.hasValue_;
|
||||
if (hasValue_) {
|
||||
new (&storage_.value) T(other.storage_.value);
|
||||
} else {
|
||||
new (&storage_.error) E(other.storage_.error);
|
||||
}
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
Result& operator=(Result&& other) noexcept {
|
||||
if (this != &other) {
|
||||
this->~Result();
|
||||
hasValue_ = other.hasValue_;
|
||||
if (hasValue_) {
|
||||
new (&storage_.value) T(std::move(other.storage_.value));
|
||||
} else {
|
||||
new (&storage_.error) E(std::move(other.storage_.error));
|
||||
}
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
static Result<T, E> ok(T value) {
|
||||
Result<T, E> result;
|
||||
result.hasValue_ = true;
|
||||
new (&result.storage_.value) T(std::move(value));
|
||||
return result;
|
||||
}
|
||||
|
||||
static Result<T, E> err(E error) {
|
||||
Result<T, E> result;
|
||||
result.hasValue_ = false;
|
||||
new (&result.storage_.error) E(std::move(error));
|
||||
return result;
|
||||
}
|
||||
|
||||
bool ok() const { return hasValue_; }
|
||||
bool isOk() const { return hasValue_; }
|
||||
bool isErr() const { return !hasValue_; }
|
||||
|
||||
T& value() & {
|
||||
return storage_.value;
|
||||
}
|
||||
|
||||
const T& value() const & {
|
||||
return storage_.value;
|
||||
}
|
||||
|
||||
T&& value() && {
|
||||
return std::move(storage_.value);
|
||||
}
|
||||
|
||||
E& error() & {
|
||||
return storage_.error;
|
||||
}
|
||||
|
||||
const E& error() const & {
|
||||
return storage_.error;
|
||||
}
|
||||
|
||||
E&& error() && {
|
||||
return std::move(storage_.error);
|
||||
}
|
||||
|
||||
T valueOr(T defaultValue) const {
|
||||
return hasValue_ ? storage_.value : std::move(defaultValue);
|
||||
}
|
||||
|
||||
template<typename F>
|
||||
Result<T, E> map(F&& f) {
|
||||
if (hasValue_) {
|
||||
return Result<T, E>::ok(f(storage_.value));
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<typename F>
|
||||
Result<T, E> mapErr(F&& f) {
|
||||
if (!hasValue_) {
|
||||
return Result<T, E>::err(f(storage_.error));
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<typename F>
|
||||
auto andThen(F&& f) -> decltype(f(std::declval<T>())) {
|
||||
if (hasValue_) {
|
||||
return f(storage_.value);
|
||||
}
|
||||
return Result<typename decltype(f(std::declval<T>()))::ValueType, E>::err(storage_.error);
|
||||
}
|
||||
|
||||
template<typename F>
|
||||
Result<T, E> orElse(F&& f) {
|
||||
if (!hasValue_) {
|
||||
return f(storage_.error);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
union Storage {
|
||||
T value;
|
||||
E error;
|
||||
|
||||
Storage() {}
|
||||
~Storage() {}
|
||||
} storage_;
|
||||
|
||||
bool hasValue_;
|
||||
};
|
||||
|
||||
// 特化void类型
|
||||
template<typename E>
|
||||
class Result<void, E> {
|
||||
public:
|
||||
Result() : hasValue_(true) {}
|
||||
|
||||
~Result() {
|
||||
if (!hasValue_) {
|
||||
storage_.error.~E();
|
||||
}
|
||||
}
|
||||
|
||||
Result(const Result& other) : hasValue_(other.hasValue_) {
|
||||
if (!hasValue_) {
|
||||
new (&storage_.error) E(other.storage_.error);
|
||||
}
|
||||
}
|
||||
|
||||
Result(Result&& other) noexcept : hasValue_(other.hasValue_) {
|
||||
if (!hasValue_) {
|
||||
new (&storage_.error) E(std::move(other.storage_.error));
|
||||
}
|
||||
}
|
||||
|
||||
Result& operator=(const Result& other) {
|
||||
if (this != &other) {
|
||||
this->~Result();
|
||||
hasValue_ = other.hasValue_;
|
||||
if (!hasValue_) {
|
||||
new (&storage_.error) E(other.storage_.error);
|
||||
}
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
Result& operator=(Result&& other) noexcept {
|
||||
if (this != &other) {
|
||||
this->~Result();
|
||||
hasValue_ = other.hasValue_;
|
||||
if (!hasValue_) {
|
||||
new (&storage_.error) E(std::move(other.storage_.error));
|
||||
}
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
static Result<void, E> ok() {
|
||||
return Result<void, E>();
|
||||
}
|
||||
|
||||
static Result<void, E> err(E error) {
|
||||
Result<void, E> result;
|
||||
result.hasValue_ = false;
|
||||
new (&result.storage_.error) E(std::move(error));
|
||||
return result;
|
||||
}
|
||||
|
||||
bool ok() const { return hasValue_; }
|
||||
bool isOk() const { return hasValue_; }
|
||||
bool isErr() const { return !hasValue_; }
|
||||
|
||||
E& error() & {
|
||||
return storage_.error;
|
||||
}
|
||||
|
||||
const E& error() const & {
|
||||
return storage_.error;
|
||||
}
|
||||
|
||||
E&& error() && {
|
||||
return std::move(storage_.error);
|
||||
}
|
||||
|
||||
private:
|
||||
union Storage {
|
||||
E error;
|
||||
|
||||
Storage() {}
|
||||
~Storage() {}
|
||||
} storage_;
|
||||
|
||||
bool hasValue_;
|
||||
};
|
||||
|
||||
// 便捷宏
|
||||
#define E2D_TRY(result) \
|
||||
do { \
|
||||
auto _res = (result); \
|
||||
if (!_res.ok()) { \
|
||||
return Result<decltype(_res)::ValueType, decltype(_res)::ErrorType>::err(_res.error()); \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -0,0 +1,103 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/core/types.h>
|
||||
#include <atomic>
|
||||
#include <array>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
/**
|
||||
* @brief 无锁环形缓冲区(单生产者单消费者)
|
||||
* @tparam T 元素类型
|
||||
* @tparam Size 缓冲区大小(必须是2的幂)
|
||||
*/
|
||||
template <typename T, size_t Size>
|
||||
class RingBuffer {
|
||||
static_assert((Size & (Size - 1)) == 0, "Size must be a power of 2");
|
||||
|
||||
public:
|
||||
RingBuffer() = default;
|
||||
|
||||
/**
|
||||
* @brief 入队
|
||||
* @param item 元素
|
||||
* @return 成功返回true,缓冲区满返回false
|
||||
*/
|
||||
bool push(const T& item) {
|
||||
const size_t currentHead = head_.load(std::memory_order_relaxed);
|
||||
const size_t currentTail = tail_.load(std::memory_order_acquire);
|
||||
|
||||
if ((currentHead - currentTail) >= Size) {
|
||||
return false; // 缓冲区满
|
||||
}
|
||||
|
||||
buffer_[currentHead & mask_] = item;
|
||||
head_.store(currentHead + 1, std::memory_order_release);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 入队(移动语义)
|
||||
* @param item 元素
|
||||
* @return 成功返回true,缓冲区满返回false
|
||||
*/
|
||||
bool push(T&& item) {
|
||||
const size_t currentHead = head_.load(std::memory_order_relaxed);
|
||||
const size_t currentTail = tail_.load(std::memory_order_acquire);
|
||||
|
||||
if ((currentHead - currentTail) >= Size) {
|
||||
return false; // 缓冲区满
|
||||
}
|
||||
|
||||
buffer_[currentHead & mask_] = std::move(item);
|
||||
head_.store(currentHead + 1, std::memory_order_release);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 出队
|
||||
* @param item 输出元素
|
||||
* @return 成功返回true,缓冲区空返回false
|
||||
*/
|
||||
bool pop(T& item) {
|
||||
const size_t currentTail = tail_.load(std::memory_order_relaxed);
|
||||
const size_t currentHead = head_.load(std::memory_order_acquire);
|
||||
|
||||
if (currentTail == currentHead) {
|
||||
return false; // 缓冲区空
|
||||
}
|
||||
|
||||
item = std::move(buffer_[currentTail & mask_]);
|
||||
tail_.store(currentTail + 1, std::memory_order_release);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 检查是否为空
|
||||
*/
|
||||
bool empty() const {
|
||||
return head_.load(std::memory_order_acquire) ==
|
||||
tail_.load(std::memory_order_acquire);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取当前大小
|
||||
*/
|
||||
size_t size() const {
|
||||
return head_.load(std::memory_order_acquire) -
|
||||
tail_.load(std::memory_order_acquire);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取容量
|
||||
*/
|
||||
static constexpr size_t capacity() { return Size; }
|
||||
|
||||
private:
|
||||
static constexpr size_t mask_ = Size - 1;
|
||||
alignas(64) std::array<T, Size> buffer_;
|
||||
alignas(64) std::atomic<size_t> head_{0};
|
||||
alignas(64) std::atomic<size_t> tail_{0};
|
||||
};
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -0,0 +1,144 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/core/types.h>
|
||||
#include <string>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
/**
|
||||
* @brief 服务优先级枚举
|
||||
* 定义服务的初始化顺序,数值越小越先初始化
|
||||
*/
|
||||
enum class ServicePriority : i32 {
|
||||
Core = 0,
|
||||
Event = 100,
|
||||
Timer = 200,
|
||||
Scene = 300,
|
||||
Camera = 400,
|
||||
Resource = 500,
|
||||
Audio = 600,
|
||||
User = 1000
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 服务状态枚举
|
||||
*/
|
||||
enum class ServiceState {
|
||||
Uninitialized,
|
||||
Initializing,
|
||||
Running,
|
||||
Paused,
|
||||
Stopping,
|
||||
Stopped
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 服务信息结构体
|
||||
*/
|
||||
struct ServiceInfo {
|
||||
std::string name;
|
||||
ServicePriority priority = ServicePriority::User;
|
||||
ServiceState state = ServiceState::Uninitialized;
|
||||
bool enabled = true;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 服务接口基类
|
||||
* 所有服务必须实现此接口,支持依赖注入和生命周期管理
|
||||
*/
|
||||
class IService {
|
||||
friend class ServiceLocator;
|
||||
|
||||
public:
|
||||
virtual ~IService() = default;
|
||||
|
||||
/**
|
||||
* @brief 获取服务信息
|
||||
* @return 服务信息结构体
|
||||
*/
|
||||
virtual ServiceInfo info() const = 0;
|
||||
|
||||
/**
|
||||
* @brief 初始化服务
|
||||
* @return 初始化成功返回 true
|
||||
*/
|
||||
virtual bool init() = 0;
|
||||
|
||||
/**
|
||||
* @brief 关闭服务
|
||||
*/
|
||||
virtual void shutdown() = 0;
|
||||
|
||||
/**
|
||||
* @brief 暂停服务
|
||||
*/
|
||||
virtual void pause() {
|
||||
info_.state = ServiceState::Paused;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 恢复服务
|
||||
*/
|
||||
virtual void resume() {
|
||||
if (info_.state == ServiceState::Paused) {
|
||||
info_.state = ServiceState::Running;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 更新服务
|
||||
* @param dt 帧间隔时间
|
||||
*/
|
||||
virtual void update(f32 dt) { }
|
||||
|
||||
/**
|
||||
* @brief 检查服务是否已初始化
|
||||
* @return 已初始化返回 true
|
||||
*/
|
||||
virtual bool initialized() const {
|
||||
return info_.state == ServiceState::Running ||
|
||||
info_.state == ServiceState::Paused;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取服务状态
|
||||
* @return 当前服务状态
|
||||
*/
|
||||
ServiceState state() const { return info_.state; }
|
||||
|
||||
/**
|
||||
* @brief 获取服务名称
|
||||
* @return 服务名称
|
||||
*/
|
||||
const std::string& name() const { return info_.name; }
|
||||
|
||||
protected:
|
||||
ServiceInfo info_;
|
||||
|
||||
/**
|
||||
* @brief 设置服务状态
|
||||
* @param state 新状态
|
||||
*/
|
||||
void setState(ServiceState state) { info_.state = state; }
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 类型ID生成器
|
||||
* 用于为每种服务类型生成唯一ID
|
||||
*/
|
||||
using ServiceTypeId = size_t;
|
||||
|
||||
namespace detail {
|
||||
inline ServiceTypeId nextServiceTypeId() {
|
||||
static ServiceTypeId id = 0;
|
||||
return ++id;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
ServiceTypeId getServiceTypeId() {
|
||||
static ServiceTypeId id = nextServiceTypeId();
|
||||
return id;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,307 @@
|
|||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <extra2d/core/service_interface.h>
|
||||
#include <extra2d/core/types.h>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <shared_mutex>
|
||||
#include <typeindex>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
/**
|
||||
* @brief 服务工厂函数类型
|
||||
*/
|
||||
template <typename T> using ServiceFactory = Fn<Ref<T>()>;
|
||||
|
||||
/**
|
||||
* @brief 服务定位器
|
||||
* 实现依赖注入和服务发现模式,解耦模块间依赖
|
||||
*
|
||||
* 特性:
|
||||
* - 类型安全的服务注册和获取
|
||||
* - 支持服务工厂延迟创建
|
||||
* - 支持服务依赖声明
|
||||
* - 线程安全(读写锁)
|
||||
* - 支持 Mock 测试
|
||||
*/
|
||||
class ServiceLocator {
|
||||
public:
|
||||
/**
|
||||
* @brief 获取单例实例
|
||||
* @return 服务定位器实例引用
|
||||
*/
|
||||
static ServiceLocator &instance();
|
||||
|
||||
ServiceLocator(const ServiceLocator &) = delete;
|
||||
ServiceLocator &operator=(const ServiceLocator &) = delete;
|
||||
|
||||
/**
|
||||
* @brief 注册服务实例
|
||||
* @tparam T 服务接口类型
|
||||
* @param svc 服务实例
|
||||
*/
|
||||
template <typename T> void add(Ref<T> svc) {
|
||||
static_assert(std::is_base_of_v<IService, T>,
|
||||
"T must derive from IService");
|
||||
|
||||
std::unique_lock<std::shared_mutex> lock(mutex_);
|
||||
auto typeId = std::type_index(typeid(T));
|
||||
services_[typeId] = std::static_pointer_cast<IService>(svc);
|
||||
orderedServices_.push_back(svc);
|
||||
sort();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 注册服务工厂
|
||||
* @tparam T 服务接口类型
|
||||
* @param fn 服务工厂函数
|
||||
*/
|
||||
template <typename T> void setFactory(ServiceFactory<T> fn) {
|
||||
static_assert(std::is_base_of_v<IService, T>,
|
||||
"T must derive from IService");
|
||||
|
||||
std::unique_lock<std::shared_mutex> lock(mutex_);
|
||||
auto typeId = std::type_index(typeid(T));
|
||||
factories_[typeId] = [fn]() -> Ref<IService> {
|
||||
return std::static_pointer_cast<IService>(fn());
|
||||
};
|
||||
|
||||
// 立即创建服务实例并添加到有序列表
|
||||
auto svc = factories_[typeId]();
|
||||
services_[typeId] = svc;
|
||||
orderedServices_.push_back(svc);
|
||||
sort();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取服务实例
|
||||
* @tparam T 服务接口类型
|
||||
* @return 服务实例,不存在返回 nullptr
|
||||
*/
|
||||
template <typename T> Ref<T> get() const {
|
||||
static_assert(std::is_base_of_v<IService, T>,
|
||||
"T must derive from IService");
|
||||
|
||||
auto typeId = std::type_index(typeid(T));
|
||||
|
||||
// 读锁查询
|
||||
std::shared_lock<std::shared_mutex> lock(mutex_);
|
||||
|
||||
auto it = services_.find(typeId);
|
||||
if (it != services_.end()) {
|
||||
return std::static_pointer_cast<T>(it->second);
|
||||
}
|
||||
|
||||
auto factoryIt = factories_.find(typeId);
|
||||
if (factoryIt != factories_.end()) {
|
||||
auto svc = factoryIt->second();
|
||||
services_[typeId] = svc;
|
||||
return std::static_pointer_cast<T>(svc);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 尝试获取服务实例(不创建)
|
||||
* @tparam T 服务接口类型
|
||||
* @return 服务实例,不存在返回 nullptr
|
||||
*/
|
||||
template <typename T> Ref<T> tryGet() const {
|
||||
static_assert(std::is_base_of_v<IService, T>,
|
||||
"T must derive from IService");
|
||||
|
||||
auto typeId = std::type_index(typeid(T));
|
||||
|
||||
// 读锁查询
|
||||
std::shared_lock<std::shared_mutex> lock(mutex_);
|
||||
auto it = services_.find(typeId);
|
||||
if (it != services_.end()) {
|
||||
return std::static_pointer_cast<T>(it->second);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 检查服务是否已注册
|
||||
* @tparam T 服务接口类型
|
||||
* @return 已注册返回 true
|
||||
*/
|
||||
template <typename T> bool has() const {
|
||||
std::shared_lock<std::shared_mutex> lock(mutex_);
|
||||
auto typeId = std::type_index(typeid(T));
|
||||
return services_.find(typeId) != services_.end() ||
|
||||
factories_.find(typeId) != factories_.end();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 注销服务
|
||||
* @tparam T 服务接口类型
|
||||
*/
|
||||
template <typename T> void remove() {
|
||||
std::unique_lock<std::shared_mutex> lock(mutex_);
|
||||
auto typeId = std::type_index(typeid(T));
|
||||
|
||||
auto it = services_.find(typeId);
|
||||
if (it != services_.end()) {
|
||||
auto svc = it->second;
|
||||
services_.erase(it);
|
||||
|
||||
auto orderIt =
|
||||
std::find(orderedServices_.begin(), orderedServices_.end(), svc);
|
||||
if (orderIt != orderedServices_.end()) {
|
||||
orderedServices_.erase(orderIt);
|
||||
}
|
||||
}
|
||||
|
||||
factories_.erase(typeId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 初始化所有已注册的服务
|
||||
* @return 所有服务初始化成功返回 true
|
||||
*/
|
||||
bool init();
|
||||
|
||||
/**
|
||||
* @brief 关闭所有服务
|
||||
*/
|
||||
void shutdown();
|
||||
|
||||
/**
|
||||
* @brief 更新所有服务
|
||||
* @param dt 帧间隔时间
|
||||
*/
|
||||
void update(f32 dt);
|
||||
|
||||
/**
|
||||
* @brief 暂停所有服务
|
||||
*/
|
||||
void pause();
|
||||
|
||||
/**
|
||||
* @brief 恢复所有服务
|
||||
*/
|
||||
void resume();
|
||||
|
||||
/**
|
||||
* @brief 获取所有服务(按优先级排序)
|
||||
* @return 服务列表
|
||||
*/
|
||||
std::vector<Ref<IService>> all() const;
|
||||
|
||||
/**
|
||||
* @brief 清空所有服务和工厂
|
||||
*/
|
||||
void clear();
|
||||
|
||||
/**
|
||||
* @brief 获取已注册服务数量
|
||||
* @return 服务数量
|
||||
*/
|
||||
size_t size() const { return services_.size(); }
|
||||
|
||||
private:
|
||||
ServiceLocator() = default;
|
||||
~ServiceLocator() = default;
|
||||
|
||||
/**
|
||||
* @brief 按优先级排序服务
|
||||
*/
|
||||
void sort();
|
||||
|
||||
mutable std::unordered_map<std::type_index, Ref<IService>> services_;
|
||||
std::unordered_map<std::type_index, std::function<Ref<IService>()>>
|
||||
factories_;
|
||||
std::vector<Ref<IService>> orderedServices_;
|
||||
mutable std::shared_mutex mutex_;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 服务注册器
|
||||
* 用于静态注册服务
|
||||
*/
|
||||
template <typename Interface, typename Implementation> class ServiceRegistrar {
|
||||
public:
|
||||
explicit ServiceRegistrar(ServiceFactory<Interface> fn = nullptr) {
|
||||
if (fn) {
|
||||
ServiceLocator::instance().setFactory<Interface>(fn);
|
||||
} else {
|
||||
ServiceLocator::instance().setFactory<Interface>(
|
||||
[]() -> Ref<Interface> {
|
||||
return ptr::make<Implementation>();
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 服务注册元数据模板
|
||||
* 使用模板元编程实现编译期服务注册
|
||||
* 通过静态成员变量的初始化触发注册
|
||||
*/
|
||||
template <typename Interface, typename Implementation> struct ServiceAutoReg {
|
||||
/**
|
||||
* @brief 注册标记,访问此变量时触发服务注册
|
||||
*/
|
||||
static const bool registered;
|
||||
|
||||
/**
|
||||
* @brief 执行实际的服务注册
|
||||
* @return true 表示注册成功
|
||||
*/
|
||||
static bool doRegister() {
|
||||
::extra2d::ServiceLocator::instance().setFactory<Interface>(
|
||||
[]() -> ::extra2d::Ref<Interface> {
|
||||
return ::extra2d::ptr::make<Implementation>();
|
||||
});
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
// 静态成员定义,在此处触发注册
|
||||
template <typename Interface, typename Implementation>
|
||||
const bool ServiceAutoReg<Interface, Implementation>::registered =
|
||||
ServiceAutoReg<Interface, Implementation>::doRegister();
|
||||
|
||||
/**
|
||||
* @brief 服务注册元数据(带自定义工厂)
|
||||
*/
|
||||
template <typename Interface> struct ServiceAutoRegFactory {
|
||||
template <typename Factory> struct Impl {
|
||||
static const bool registered;
|
||||
|
||||
static bool doRegister(Factory fn) {
|
||||
::extra2d::ServiceLocator::instance().setFactory<Interface>(fn);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
template <typename Interface>
|
||||
template <typename Factory>
|
||||
const bool ServiceAutoRegFactory<Interface>::Impl<Factory>::registered =
|
||||
ServiceAutoRegFactory<Interface>::Impl<Factory>::doRegister(Factory{});
|
||||
|
||||
/**
|
||||
* @brief 自动注册服务宏(元数据驱动)
|
||||
* 在服务实现类中使用,通过模板元编程实现自动注册
|
||||
* 比静态对象更可靠,不易被编译器优化
|
||||
*/
|
||||
#define E2D_AUTO_REGISTER_SERVICE(Interface, Implementation) \
|
||||
static inline const bool E2D_CONCAT(_service_reg_, __LINE__) = \
|
||||
ServiceAutoReg<Interface, Implementation>::registered
|
||||
|
||||
/**
|
||||
* @brief 带自定义工厂的自动注册服务宏(元数据驱动)
|
||||
*/
|
||||
#define E2D_AUTO_REGISTER_SERVICE_FACTORY(Interface, Factory) \
|
||||
static inline const bool E2D_CONCAT(_service_factory_reg_, __LINE__) = \
|
||||
ServiceAutoRegFactory<Interface>::Impl<Factory>::registered
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -0,0 +1,137 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/core/service_interface.h>
|
||||
#include <extra2d/core/service_locator.h>
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
/**
|
||||
* @brief 服务注册信息
|
||||
*/
|
||||
struct ServiceRegistration {
|
||||
std::string name;
|
||||
ServicePriority priority;
|
||||
std::function<Ref<IService>()> factory;
|
||||
bool enabled = true;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 服务注册表
|
||||
* 管理服务的注册信息,支持延迟创建和配置
|
||||
*/
|
||||
class ServiceRegistry {
|
||||
public:
|
||||
/**
|
||||
* @brief 获取单例实例
|
||||
* @return 服务注册表实例引用
|
||||
*/
|
||||
static ServiceRegistry& instance();
|
||||
|
||||
ServiceRegistry(const ServiceRegistry&) = delete;
|
||||
ServiceRegistry& operator=(const ServiceRegistry&) = delete;
|
||||
|
||||
/**
|
||||
* @brief 注册服务
|
||||
* @tparam T 服务接口类型
|
||||
* @tparam Impl 服务实现类型
|
||||
* @param name 服务名称
|
||||
* @param priority 服务优先级
|
||||
*/
|
||||
template<typename T, typename Impl>
|
||||
void add(const std::string& name, ServicePriority priority) {
|
||||
static_assert(std::is_base_of_v<IService, T>,
|
||||
"T must derive from IService");
|
||||
static_assert(std::is_base_of_v<T, Impl>,
|
||||
"Impl must derive from T");
|
||||
|
||||
ServiceRegistration reg;
|
||||
reg.name = name;
|
||||
reg.priority = priority;
|
||||
reg.factory = []() -> Ref<IService> {
|
||||
return std::static_pointer_cast<IService>(ptr::make<Impl>());
|
||||
};
|
||||
registrations_.push_back(reg);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 注册服务(带工厂函数)
|
||||
* @tparam T 服务接口类型
|
||||
* @param name 服务名称
|
||||
* @param priority 服务优先级
|
||||
* @param factory 工厂函数
|
||||
*/
|
||||
template<typename T>
|
||||
void addWithFactory(
|
||||
const std::string& name,
|
||||
ServicePriority priority,
|
||||
std::function<Ref<T>()> factory) {
|
||||
static_assert(std::is_base_of_v<IService, T>,
|
||||
"T must derive from IService");
|
||||
|
||||
ServiceRegistration reg;
|
||||
reg.name = name;
|
||||
reg.priority = priority;
|
||||
reg.factory = [factory]() -> Ref<IService> {
|
||||
return std::static_pointer_cast<IService>(factory());
|
||||
};
|
||||
registrations_.push_back(reg);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 启用/禁用服务
|
||||
* @param name 服务名称
|
||||
* @param enabled 是否启用
|
||||
*/
|
||||
void setEnabled(const std::string& name, bool enabled);
|
||||
|
||||
/**
|
||||
* @brief 创建所有已注册的服务
|
||||
* 并注册到 ServiceLocator
|
||||
*/
|
||||
void createAll();
|
||||
|
||||
/**
|
||||
* @brief 获取所有注册信息
|
||||
* @return 注册信息列表
|
||||
*/
|
||||
const std::vector<ServiceRegistration>& all() const {
|
||||
return registrations_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 清空所有注册
|
||||
*/
|
||||
void clear() {
|
||||
registrations_.clear();
|
||||
}
|
||||
|
||||
private:
|
||||
ServiceRegistry() = default;
|
||||
~ServiceRegistry() = default;
|
||||
|
||||
std::vector<ServiceRegistration> registrations_;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 自动服务注册器
|
||||
* 在全局作用域使用,自动注册服务
|
||||
*/
|
||||
template<typename Interface, typename Implementation>
|
||||
class AutoServiceRegistrar {
|
||||
public:
|
||||
AutoServiceRegistrar(const std::string& name, ServicePriority priority) {
|
||||
ServiceRegistry::instance().add<Interface, Implementation>(
|
||||
name, priority);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#define E2D_REGISTER_SERVICE_AUTO(Interface, Implementation, Name, Priority) \
|
||||
namespace { \
|
||||
static ::extra2d::AutoServiceRegistrar<Interface, Implementation> \
|
||||
E2D_CONCAT(auto_service_registrar_, __LINE__)(Name, Priority); \
|
||||
}
|
||||
|
|
@ -1,209 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
// ============================================================================
|
||||
// 字符串编码转换工具函数
|
||||
// 统一使用 std::string (UTF-8) 作为项目标准字符串类型
|
||||
// ============================================================================
|
||||
|
||||
// UTF-8 ↔ UTF-16 转换
|
||||
std::u16string utf8ToUtf16(const std::string& utf8);
|
||||
std::string utf16ToUtf8(const std::u16string& utf16);
|
||||
|
||||
// UTF-8 ↔ UTF-32 转换
|
||||
std::u32string utf8ToUtf32(const std::string& utf8);
|
||||
std::string utf32ToUtf8(const std::u32string& utf32);
|
||||
|
||||
// UTF-8 ↔ Wide String 转换
|
||||
std::wstring utf8ToWide(const std::string& utf8);
|
||||
std::string wideToUtf8(const std::wstring& wide);
|
||||
|
||||
// UTF-8 ↔ GBK/GB2312 转换(Windows 中文系统常用)
|
||||
std::string utf8ToGbk(const std::string& utf8);
|
||||
std::string gbkToUtf8(const std::string& gbk);
|
||||
|
||||
// ============================================================================
|
||||
// 内联实现
|
||||
// ============================================================================
|
||||
|
||||
inline std::u16string utf8ToUtf16(const std::string& utf8) {
|
||||
if (utf8.empty()) return std::u16string();
|
||||
|
||||
// UTF-8 → UTF-32 → UTF-16 (with surrogate pairs)
|
||||
std::u32string u32 = utf8ToUtf32(utf8);
|
||||
std::u16string result;
|
||||
result.reserve(u32.size());
|
||||
|
||||
for (char32_t ch : u32) {
|
||||
if (ch <= 0xFFFF) {
|
||||
result.push_back(static_cast<char16_t>(ch));
|
||||
} else if (ch <= 0x10FFFF) {
|
||||
// Surrogate pair
|
||||
ch -= 0x10000;
|
||||
result.push_back(static_cast<char16_t>(0xD800 | (ch >> 10)));
|
||||
result.push_back(static_cast<char16_t>(0xDC00 | (ch & 0x3FF)));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
inline std::string utf16ToUtf8(const std::u16string& utf16) {
|
||||
if (utf16.empty()) return std::string();
|
||||
|
||||
// UTF-16 → UTF-32 → UTF-8
|
||||
std::u32string u32;
|
||||
u32.reserve(utf16.size());
|
||||
|
||||
for (size_t i = 0; i < utf16.size(); ++i) {
|
||||
char16_t cu = utf16[i];
|
||||
char32_t ch;
|
||||
if (cu >= 0xD800 && cu <= 0xDBFF && i + 1 < utf16.size()) {
|
||||
// High surrogate
|
||||
char16_t cl = utf16[i + 1];
|
||||
if (cl >= 0xDC00 && cl <= 0xDFFF) {
|
||||
ch = 0x10000 + ((static_cast<char32_t>(cu - 0xD800) << 10) |
|
||||
(cl - 0xDC00));
|
||||
++i;
|
||||
} else {
|
||||
ch = cu; // Invalid, pass through
|
||||
}
|
||||
} else {
|
||||
ch = cu;
|
||||
}
|
||||
u32.push_back(ch);
|
||||
}
|
||||
|
||||
return utf32ToUtf8(u32);
|
||||
}
|
||||
|
||||
inline std::u32string utf8ToUtf32(const std::string& utf8) {
|
||||
std::u32string result;
|
||||
result.reserve(utf8.size());
|
||||
|
||||
const char* ptr = utf8.c_str();
|
||||
const char* end = ptr + utf8.size();
|
||||
|
||||
while (ptr < end) {
|
||||
char32_t ch = 0;
|
||||
unsigned char byte = static_cast<unsigned char>(*ptr);
|
||||
|
||||
if ((byte & 0x80) == 0) {
|
||||
// 1-byte sequence
|
||||
ch = byte;
|
||||
ptr += 1;
|
||||
} else if ((byte & 0xE0) == 0xC0) {
|
||||
// 2-byte sequence
|
||||
ch = (byte & 0x1F) << 6;
|
||||
ch |= (static_cast<unsigned char>(ptr[1]) & 0x3F);
|
||||
ptr += 2;
|
||||
} else if ((byte & 0xF0) == 0xE0) {
|
||||
// 3-byte sequence
|
||||
ch = (byte & 0x0F) << 12;
|
||||
ch |= (static_cast<unsigned char>(ptr[1]) & 0x3F) << 6;
|
||||
ch |= (static_cast<unsigned char>(ptr[2]) & 0x3F);
|
||||
ptr += 3;
|
||||
} else if ((byte & 0xF8) == 0xF0) {
|
||||
// 4-byte sequence
|
||||
ch = (byte & 0x07) << 18;
|
||||
ch |= (static_cast<unsigned char>(ptr[1]) & 0x3F) << 12;
|
||||
ch |= (static_cast<unsigned char>(ptr[2]) & 0x3F) << 6;
|
||||
ch |= (static_cast<unsigned char>(ptr[3]) & 0x3F);
|
||||
ptr += 4;
|
||||
} else {
|
||||
// Invalid UTF-8, skip
|
||||
ptr += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
result.push_back(ch);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
inline std::string utf32ToUtf8(const std::u32string& utf32) {
|
||||
std::string result;
|
||||
|
||||
for (char32_t ch : utf32) {
|
||||
if (ch <= 0x7F) {
|
||||
// 1-byte
|
||||
result.push_back(static_cast<char>(ch));
|
||||
} else if (ch <= 0x7FF) {
|
||||
// 2-byte
|
||||
result.push_back(static_cast<char>(0xC0 | ((ch >> 6) & 0x1F)));
|
||||
result.push_back(static_cast<char>(0x80 | (ch & 0x3F)));
|
||||
} else if (ch <= 0xFFFF) {
|
||||
// 3-byte
|
||||
result.push_back(static_cast<char>(0xE0 | ((ch >> 12) & 0x0F)));
|
||||
result.push_back(static_cast<char>(0x80 | ((ch >> 6) & 0x3F)));
|
||||
result.push_back(static_cast<char>(0x80 | (ch & 0x3F)));
|
||||
} else if (ch <= 0x10FFFF) {
|
||||
// 4-byte
|
||||
result.push_back(static_cast<char>(0xF0 | ((ch >> 18) & 0x07)));
|
||||
result.push_back(static_cast<char>(0x80 | ((ch >> 12) & 0x3F)));
|
||||
result.push_back(static_cast<char>(0x80 | ((ch >> 6) & 0x3F)));
|
||||
result.push_back(static_cast<char>(0x80 | (ch & 0x3F)));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
inline std::wstring utf8ToWide(const std::string& utf8) {
|
||||
if (utf8.empty()) return std::wstring();
|
||||
|
||||
if constexpr (sizeof(wchar_t) == 4) {
|
||||
// wchar_t is 32-bit (Linux/Switch): same as UTF-32
|
||||
std::u32string u32 = utf8ToUtf32(utf8);
|
||||
return std::wstring(u32.begin(), u32.end());
|
||||
} else {
|
||||
// wchar_t is 16-bit (Windows): same as UTF-16
|
||||
std::u16string u16 = utf8ToUtf16(utf8);
|
||||
return std::wstring(u16.begin(), u16.end());
|
||||
}
|
||||
}
|
||||
|
||||
inline std::string wideToUtf8(const std::wstring& wide) {
|
||||
if (wide.empty()) return std::string();
|
||||
|
||||
if constexpr (sizeof(wchar_t) == 4) {
|
||||
std::u32string u32(wide.begin(), wide.end());
|
||||
return utf32ToUtf8(u32);
|
||||
} else {
|
||||
std::u16string u16(wide.begin(), wide.end());
|
||||
return utf16ToUtf8(u16);
|
||||
}
|
||||
}
|
||||
|
||||
// GBK/GB2312 转换(Windows 平台实现)
|
||||
// 注意:Windows 实现在 .cpp 文件中,避免头文件包含 windows.h 导致冲突
|
||||
#ifdef _WIN32
|
||||
// 前向声明,实现在 .cpp 文件中
|
||||
std::string utf8ToGbkImpl(const std::string& utf8);
|
||||
std::string gbkToUtf8Impl(const std::string& gbk);
|
||||
|
||||
inline std::string utf8ToGbk(const std::string& utf8) {
|
||||
return utf8ToGbkImpl(utf8);
|
||||
}
|
||||
|
||||
inline std::string gbkToUtf8(const std::string& gbk) {
|
||||
return gbkToUtf8Impl(gbk);
|
||||
}
|
||||
#else
|
||||
// 非 Windows 平台,GBK 转换使用 iconv 或返回原字符串
|
||||
inline std::string utf8ToGbk(const std::string& utf8) {
|
||||
// TODO: 使用 iconv 实现
|
||||
return utf8;
|
||||
}
|
||||
|
||||
inline std::string gbkToUtf8(const std::string& gbk) {
|
||||
// TODO: 使用 iconv 实现
|
||||
return gbk;
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -7,41 +7,55 @@
|
|||
|
||||
namespace extra2d {
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 宏定义
|
||||
// ---------------------------------------------------------------------------
|
||||
#define E2D_CONCAT_IMPL(a, b) a##b
|
||||
#define E2D_CONCAT(a, b) E2D_CONCAT_IMPL(a, b)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 智能指针别名
|
||||
// ---------------------------------------------------------------------------
|
||||
template <typename T> using Ptr = std::shared_ptr<T>;
|
||||
template <typename T> using Ref = std::shared_ptr<T>;
|
||||
template <typename T> using Unique = std::unique_ptr<T>;
|
||||
template <typename T> using Weak = std::weak_ptr<T>;
|
||||
|
||||
template <typename T> using UniquePtr = std::unique_ptr<T>;
|
||||
|
||||
template <typename T> using WeakPtr = std::weak_ptr<T>;
|
||||
|
||||
/// 创建 shared_ptr 的便捷函数
|
||||
template <typename T, typename... Args> inline Ptr<T> makePtr(Args &&...args) {
|
||||
/// 智能指针工厂函数命名空间
|
||||
namespace ptr {
|
||||
/// 创建 Ref 的便捷函数
|
||||
template <typename T, typename... Args> inline Ref<T> make(Args &&...args) {
|
||||
return std::make_shared<T>(std::forward<Args>(args)...);
|
||||
}
|
||||
}
|
||||
|
||||
/// 创建 unique_ptr 的便捷函数
|
||||
template <typename T, typename... Args>
|
||||
inline UniquePtr<T> makeUnique(Args &&...args) {
|
||||
/// 创建 Unique 的便捷函数
|
||||
template <typename T, typename... Args>
|
||||
inline Unique<T> makeUnique(Args &&...args) {
|
||||
return std::make_unique<T>(std::forward<Args>(args)...);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 函数别名
|
||||
// ---------------------------------------------------------------------------
|
||||
template <typename Sig> using Function = std::function<Sig>;
|
||||
template <typename Sig> using Fn = std::function<Sig>;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 基础类型别名
|
||||
// ---------------------------------------------------------------------------
|
||||
using int8 = std::int8_t;
|
||||
using int16 = std::int16_t;
|
||||
using int32 = std::int32_t;
|
||||
using int64 = std::int64_t;
|
||||
using uint8 = std::uint8_t;
|
||||
using uint16 = std::uint16_t;
|
||||
using uint32 = std::uint32_t;
|
||||
using uint64 = std::uint64_t;
|
||||
using i8 = int8_t;
|
||||
using i16 = int16_t;
|
||||
using i32 = int32_t;
|
||||
using i64 = int64_t;
|
||||
using u8 = uint8_t;
|
||||
using u16 = uint16_t;
|
||||
using u32 = uint32_t;
|
||||
using u64 = uint64_t;
|
||||
using f32 = float;
|
||||
using f64 = double;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// ID 类型别名
|
||||
// ---------------------------------------------------------------------------
|
||||
using ID = u32;
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
|
|||
|
|
@ -59,70 +59,70 @@ enum class EventType {
|
|||
// 键盘事件数据
|
||||
// ============================================================================
|
||||
struct KeyEvent {
|
||||
int keyCode;
|
||||
int scancode;
|
||||
int mods; // 修饰键 (Shift, Ctrl, Alt, etc.)
|
||||
i32 key;
|
||||
i32 scancode;
|
||||
i32 mods; // 修饰键 (Shift, Ctrl, Alt, etc.)
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// 鼠标事件数据
|
||||
// ============================================================================
|
||||
struct MouseButtonEvent {
|
||||
int button;
|
||||
int mods;
|
||||
Vec2 position;
|
||||
i32 button;
|
||||
i32 mods;
|
||||
Vec2 pos;
|
||||
};
|
||||
|
||||
struct MouseMoveEvent {
|
||||
Vec2 position;
|
||||
Vec2 pos;
|
||||
Vec2 delta;
|
||||
};
|
||||
|
||||
struct MouseScrollEvent {
|
||||
Vec2 offset;
|
||||
Vec2 position;
|
||||
Vec2 pos;
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// 窗口事件数据
|
||||
// ============================================================================
|
||||
struct WindowResizeEvent {
|
||||
int width;
|
||||
int height;
|
||||
i32 w;
|
||||
i32 h;
|
||||
};
|
||||
|
||||
struct WindowMoveEvent {
|
||||
int x;
|
||||
int y;
|
||||
i32 x;
|
||||
i32 y;
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// 游戏手柄事件数据
|
||||
// ============================================================================
|
||||
struct GamepadButtonEvent {
|
||||
int gamepadId;
|
||||
int button;
|
||||
i32 gamepadId;
|
||||
i32 button;
|
||||
};
|
||||
|
||||
struct GamepadAxisEvent {
|
||||
int gamepadId;
|
||||
int axis;
|
||||
float value;
|
||||
i32 gamepadId;
|
||||
i32 axis;
|
||||
f32 value;
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// 触摸事件数据
|
||||
// ============================================================================
|
||||
struct TouchEvent {
|
||||
int touchId;
|
||||
Vec2 position;
|
||||
i32 touchId;
|
||||
Vec2 pos;
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// 自定义事件数据
|
||||
// ============================================================================
|
||||
struct CustomEvent {
|
||||
uint32_t id;
|
||||
u32 id;
|
||||
void *data;
|
||||
};
|
||||
|
||||
|
|
@ -131,7 +131,7 @@ struct CustomEvent {
|
|||
// ============================================================================
|
||||
struct Event {
|
||||
EventType type = EventType::None;
|
||||
double timestamp = 0.0;
|
||||
f64 timestamp = 0.0;
|
||||
bool handled = false;
|
||||
|
||||
// 事件数据联合体
|
||||
|
|
@ -141,32 +141,32 @@ struct Event {
|
|||
data;
|
||||
|
||||
// 便捷访问方法
|
||||
bool isWindowEvent() const {
|
||||
bool window() const {
|
||||
return type == EventType::WindowClose || type == EventType::WindowResize ||
|
||||
type == EventType::WindowFocus ||
|
||||
type == EventType::WindowLostFocus || type == EventType::WindowMoved;
|
||||
}
|
||||
|
||||
bool isKeyboardEvent() const {
|
||||
bool keyboard() const {
|
||||
return type == EventType::KeyPressed || type == EventType::KeyReleased ||
|
||||
type == EventType::KeyRepeat;
|
||||
}
|
||||
|
||||
bool isMouseEvent() const {
|
||||
bool mouse() const {
|
||||
return type == EventType::MouseButtonPressed ||
|
||||
type == EventType::MouseButtonReleased ||
|
||||
type == EventType::MouseMoved || type == EventType::MouseScrolled;
|
||||
}
|
||||
|
||||
// 静态工厂方法
|
||||
static Event createWindowResize(int width, int height);
|
||||
static Event createWindowClose();
|
||||
static Event createKeyPress(int keyCode, int scancode, int mods);
|
||||
static Event createKeyRelease(int keyCode, int scancode, int mods);
|
||||
static Event createMouseButtonPress(int button, int mods, const Vec2 &pos);
|
||||
static Event createMouseButtonRelease(int button, int mods, const Vec2 &pos);
|
||||
static Event createMouseMove(const Vec2 &pos, const Vec2 &delta);
|
||||
static Event createMouseScroll(const Vec2 &offset, const Vec2 &pos);
|
||||
static Event windowResize(i32 w, i32 h);
|
||||
static Event windowClose();
|
||||
static Event keyPress(i32 key, i32 scancode, i32 mods);
|
||||
static Event keyRelease(i32 key, i32 scancode, i32 mods);
|
||||
static Event mousePress(i32 btn, i32 mods, Vec2 pos);
|
||||
static Event mouseRelease(i32 btn, i32 mods, Vec2 pos);
|
||||
static Event mouseMove(Vec2 pos, Vec2 delta);
|
||||
static Event mouseScroll(Vec2 offset, Vec2 pos);
|
||||
};
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
|
|||
|
|
@ -11,46 +11,46 @@ namespace extra2d {
|
|||
// ============================================================================
|
||||
// 事件监听器 ID
|
||||
// ============================================================================
|
||||
using ListenerId = uint64_t;
|
||||
using ListenerID = u64;
|
||||
|
||||
// ============================================================================
|
||||
// 事件分发器
|
||||
// ============================================================================
|
||||
class EventDispatcher {
|
||||
public:
|
||||
using EventCallback = std::function<void(Event &)>;
|
||||
using EventFn = Fn<void(Event &)>;
|
||||
|
||||
EventDispatcher();
|
||||
~EventDispatcher() = default;
|
||||
|
||||
// 添加监听器
|
||||
ListenerId addListener(EventType type, EventCallback callback);
|
||||
ListenerID on(EventType type, EventFn fn);
|
||||
|
||||
// 移除监听器
|
||||
void removeListener(ListenerId id);
|
||||
void removeAllListeners(EventType type);
|
||||
void removeAllListeners();
|
||||
void off(ListenerID id);
|
||||
void offAll(EventType type);
|
||||
void offAll();
|
||||
|
||||
// 分发事件
|
||||
void dispatch(Event &event);
|
||||
void dispatch(const Event &event);
|
||||
|
||||
// 处理事件队列
|
||||
void processQueue(class EventQueue &queue);
|
||||
void process(class EventQueue &queue);
|
||||
|
||||
// 统计
|
||||
size_t getListenerCount(EventType type) const;
|
||||
size_t getTotalListenerCount() const;
|
||||
size_t listenerCount(EventType type) const;
|
||||
size_t totalListeners() const;
|
||||
|
||||
private:
|
||||
struct Listener {
|
||||
ListenerId id;
|
||||
ListenerID id;
|
||||
EventType type;
|
||||
EventCallback callback;
|
||||
EventFn fn;
|
||||
};
|
||||
|
||||
std::unordered_map<EventType, std::vector<Listener>> listeners_;
|
||||
ListenerId nextId_;
|
||||
ListenerID nextId_;
|
||||
};
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/core/ring_buffer.h>
|
||||
#include <extra2d/core/types.h>
|
||||
#include <extra2d/event/event.h>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
|
|
@ -12,12 +12,14 @@ namespace extra2d {
|
|||
// ============================================================================
|
||||
class EventQueue {
|
||||
public:
|
||||
static constexpr size_t DEFAULT_CAPACITY = 1024;
|
||||
|
||||
EventQueue();
|
||||
~EventQueue() = default;
|
||||
|
||||
// 添加事件到队列
|
||||
void push(const Event &event);
|
||||
void push(Event &&event);
|
||||
bool push(const Event &event);
|
||||
bool push(Event &&event);
|
||||
|
||||
// 从队列取出事件
|
||||
bool poll(Event &event);
|
||||
|
|
@ -31,10 +33,11 @@ public:
|
|||
// 队列状态
|
||||
bool empty() const;
|
||||
size_t size() const;
|
||||
size_t capacity() const { return buffer_.capacity(); }
|
||||
|
||||
private:
|
||||
std::queue<Event> queue_;
|
||||
mutable std::mutex mutex_;
|
||||
RingBuffer<Event, DEFAULT_CAPACITY> buffer_;
|
||||
mutable std::mutex mutex_; // 用于peek和clear的互斥
|
||||
};
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
|
|||
|
|
@ -1,56 +1,43 @@
|
|||
#pragma once
|
||||
|
||||
// Easy2D v3.0 - 统一入口头文件
|
||||
// Extra2D - 统一入口头文件
|
||||
// 包含所有公共 API
|
||||
|
||||
// Core
|
||||
#include <extra2d/core/types.h>
|
||||
#include <extra2d/core/string.h>
|
||||
#include <extra2d/core/color.h>
|
||||
#include <extra2d/core/math_types.h>
|
||||
#include <extra2d/core/module.h>
|
||||
#include <extra2d/core/registry.h>
|
||||
#include <extra2d/core/types.h>
|
||||
|
||||
// Window - SDL2 + OpenGL
|
||||
#include <extra2d/window/window.h>
|
||||
|
||||
// Graphics
|
||||
#include <extra2d/graphics/renderer.h>
|
||||
#include <extra2d/graphics/texture.h>
|
||||
#include <extra2d/graphics/font.h>
|
||||
#include <extra2d/graphics/camera.h>
|
||||
#include <extra2d/graphics/render_target.h>
|
||||
#include <extra2d/graphics/vram_manager.h>
|
||||
|
||||
// Scene
|
||||
#include <extra2d/scene/node.h>
|
||||
#include <extra2d/scene/scene.h>
|
||||
#include <extra2d/scene/sprite.h>
|
||||
#include <extra2d/scene/shape_node.h>
|
||||
#include <extra2d/scene/scene_manager.h>
|
||||
// Platform
|
||||
#include <extra2d/platform/glfw/glfw_window.h>
|
||||
#include <extra2d/platform/keys.h>
|
||||
#include <extra2d/platform/window_module.h>
|
||||
|
||||
// Event
|
||||
#include <extra2d/event/event.h>
|
||||
#include <extra2d/event/event_queue.h>
|
||||
#include <extra2d/event/event_dispatcher.h>
|
||||
#include <extra2d/event/input_codes.h>
|
||||
|
||||
// Audio
|
||||
#include <extra2d/audio/audio_engine.h>
|
||||
#include <extra2d/audio/sound.h>
|
||||
|
||||
// Resource
|
||||
#include <extra2d/resource/resource_manager.h>
|
||||
#include <extra2d/event/event_queue.h>
|
||||
|
||||
// Utils
|
||||
#include <extra2d/utils/logger.h>
|
||||
#include <extra2d/utils/timer.h>
|
||||
#include <extra2d/utils/data.h>
|
||||
#include <extra2d/utils/random.h>
|
||||
#include <extra2d/utils/timer.h>
|
||||
|
||||
// Spatial
|
||||
#include <extra2d/spatial/spatial_index.h>
|
||||
#include <extra2d/spatial/quadtree.h>
|
||||
#include <extra2d/spatial/spatial_hash.h>
|
||||
#include <extra2d/spatial/spatial_manager.h>
|
||||
// Services
|
||||
#include <extra2d/services/asset_service.h>
|
||||
#include <extra2d/services/event_service.h>
|
||||
#include <extra2d/services/logger_service.h>
|
||||
#include <extra2d/services/timer_service.h>
|
||||
|
||||
// Asset
|
||||
#include <extra2d/asset/asset.h>
|
||||
#include <extra2d/asset/asset_cache.h>
|
||||
#include <extra2d/asset/asset_handle.h>
|
||||
#include <extra2d/asset/asset_loader.h>
|
||||
#include <extra2d/asset/asset_pack.h>
|
||||
#include <extra2d/asset/asset_types.h>
|
||||
#include <extra2d/asset/data_processor.h>
|
||||
|
||||
// Application
|
||||
#include <extra2d/app/application.h>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,334 @@
|
|||
#pragma once
|
||||
|
||||
#include <condition_variable>
|
||||
#include <extra2d/asset/asset.h>
|
||||
#include <extra2d/asset/asset_cache.h>
|
||||
#include <extra2d/asset/asset_handle.h>
|
||||
#include <extra2d/asset/asset_loader.h>
|
||||
#include <extra2d/asset/asset_pack.h>
|
||||
#include <extra2d/asset/asset_types.h>
|
||||
#include <extra2d/asset/data_processor.h>
|
||||
#include <extra2d/core/service_interface.h>
|
||||
#include <extra2d/core/service_locator.h>
|
||||
#include <extra2d/core/types.h>
|
||||
#include <functional>
|
||||
#include <future>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
#include <shared_mutex>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <typeindex>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// IAssetService - 资源服务接口
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 资源服务接口
|
||||
*
|
||||
* 提供资源加载、缓存、异步加载等功能。
|
||||
* 使用模板方法支持类型安全的资源加载。
|
||||
*/
|
||||
class IAssetService : public IService {
|
||||
public:
|
||||
virtual ~IAssetService() = default;
|
||||
|
||||
/**
|
||||
* @brief 同步加载资源
|
||||
* @tparam T 资源类型
|
||||
* @param path 资源路径
|
||||
* @return 资源句柄
|
||||
*/
|
||||
template <typename T> AssetHandle<T> load(const std::string &path) {
|
||||
static_assert(std::is_base_of_v<Asset, T>, "T must derive from Asset");
|
||||
return AssetHandle<T>(loadImpl(AssetID(path), typeid(T)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 异步加载资源
|
||||
* @tparam T 资源类型
|
||||
* @param path 资源路径
|
||||
* @param callback 加载完成回调
|
||||
*/
|
||||
template <typename T>
|
||||
void loadAsync(const std::string &path, AssetLoadCallback<T> callback) {
|
||||
static_assert(std::is_base_of_v<Asset, T>, "T must derive from Asset");
|
||||
loadAsyncImpl(AssetID(path), typeid(T),
|
||||
[cb = std::move(callback)](AssetHandleBase handle) {
|
||||
cb(AssetHandle<T>(handle));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取已缓存的资源
|
||||
* @tparam T 资源类型
|
||||
* @param path 资源路径
|
||||
* @return 资源句柄,不存在返回空句柄
|
||||
*/
|
||||
template <typename T> AssetHandle<T> get(const std::string &path) {
|
||||
static_assert(std::is_base_of_v<Asset, T>, "T must derive from Asset");
|
||||
return AssetHandle<T>(getImpl(AssetID(path), typeid(T)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 预加载资源(后台加载,不返回句柄)
|
||||
* @tparam T 资源类型
|
||||
* @param path 资源路径
|
||||
*/
|
||||
template <typename T> void preload(const std::string &path) {
|
||||
static_assert(std::is_base_of_v<Asset, T>, "T must derive from Asset");
|
||||
preloadImpl(AssetID(path), typeid(T));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 检查资源是否已加载
|
||||
* @param path 资源路径
|
||||
* @return 已加载返回 true
|
||||
*/
|
||||
virtual bool isLoaded(const std::string &path) const = 0;
|
||||
|
||||
/**
|
||||
* @brief 检查资源是否正在加载
|
||||
* @param path 资源路径
|
||||
* @return 正在加载返回 true
|
||||
*/
|
||||
virtual bool isLoading(const std::string &path) const = 0;
|
||||
|
||||
/**
|
||||
* @brief 卸载资源
|
||||
* @param path 资源路径
|
||||
*/
|
||||
virtual void unload(const std::string &path) = 0;
|
||||
|
||||
/**
|
||||
* @brief 设置缓存上限
|
||||
* @param maxBytes 最大字节数
|
||||
*/
|
||||
virtual void setLimit(size_t maxBytes) = 0;
|
||||
|
||||
/**
|
||||
* @brief 获取当前缓存大小
|
||||
* @return 缓存字节数
|
||||
*/
|
||||
virtual size_t size() const = 0;
|
||||
|
||||
/**
|
||||
* @brief 清理无引用资源
|
||||
*/
|
||||
virtual void purge() = 0;
|
||||
|
||||
/**
|
||||
* @brief 清空所有缓存
|
||||
*/
|
||||
virtual void clear() = 0;
|
||||
|
||||
/**
|
||||
* @brief 获取缓存统计信息
|
||||
* @return 缓存统计
|
||||
*/
|
||||
virtual CacheStats stats() const = 0;
|
||||
|
||||
/**
|
||||
* @brief 注册加载器
|
||||
* @tparam T 资源类型
|
||||
* @param loader 加载器实例
|
||||
*/
|
||||
template <typename T> void registerLoader(Unique<AssetLoader<T>> loader) {
|
||||
static_assert(std::is_base_of_v<Asset, T>, "T must derive from Asset");
|
||||
registerLoaderImpl(typeid(T), std::move(loader));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 挂载资源包
|
||||
* @param path 资源包路径
|
||||
* @return 成功返回 true
|
||||
*/
|
||||
virtual bool mount(const std::string &path) = 0;
|
||||
|
||||
/**
|
||||
* @brief 卸载资源包
|
||||
* @param path 资源包路径
|
||||
*/
|
||||
virtual void unmount(const std::string &path) = 0;
|
||||
|
||||
/**
|
||||
* @brief 设置数据处理管道
|
||||
* @param pipe 处理管道
|
||||
*/
|
||||
virtual void setPipe(DataPipe pipe) = 0;
|
||||
|
||||
/**
|
||||
* @brief 设置资源根目录
|
||||
* @param path 根目录路径
|
||||
*/
|
||||
virtual void setRoot(const std::string &path) = 0;
|
||||
|
||||
/**
|
||||
* @brief 获取资源根目录
|
||||
* @return 根目录路径
|
||||
*/
|
||||
virtual std::string root() const = 0;
|
||||
|
||||
/**
|
||||
* @brief 处理异步加载完成回调(在主线程调用)
|
||||
*/
|
||||
virtual void process() = 0;
|
||||
|
||||
protected:
|
||||
/**
|
||||
* @brief 同步加载实现
|
||||
*/
|
||||
virtual AssetHandleBase loadImpl(const AssetID &id, std::type_index type) = 0;
|
||||
|
||||
/**
|
||||
* @brief 异步加载实现
|
||||
*/
|
||||
virtual void loadAsyncImpl(const AssetID &id, std::type_index type,
|
||||
std::function<void(AssetHandleBase)> callback) = 0;
|
||||
|
||||
/**
|
||||
* @brief 获取资源实现
|
||||
*/
|
||||
virtual AssetHandleBase getImpl(const AssetID &id, std::type_index type) = 0;
|
||||
|
||||
/**
|
||||
* @brief 预加载实现
|
||||
*/
|
||||
virtual void preloadImpl(const AssetID &id, std::type_index type) = 0;
|
||||
|
||||
/**
|
||||
* @brief 注册加载器实现
|
||||
*/
|
||||
virtual void registerLoaderImpl(std::type_index type,
|
||||
Unique<AssetLoaderBase> loader) = 0;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// AssetService - 资源服务实现
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 资源服务实现
|
||||
*
|
||||
* 实现资源加载、缓存、异步加载等功能。
|
||||
* 使用线程池处理异步加载任务。
|
||||
*/
|
||||
class AssetService : public IAssetService {
|
||||
public:
|
||||
AssetService();
|
||||
~AssetService() override;
|
||||
|
||||
ServiceInfo info() const override {
|
||||
ServiceInfo i;
|
||||
i.name = "AssetService";
|
||||
i.priority = ServicePriority::Resource;
|
||||
i.enabled = true;
|
||||
return i;
|
||||
}
|
||||
|
||||
bool init() override;
|
||||
void shutdown() override;
|
||||
|
||||
bool isLoaded(const std::string &path) const override;
|
||||
bool isLoading(const std::string &path) const override;
|
||||
void unload(const std::string &path) override;
|
||||
|
||||
void setLimit(size_t maxBytes) override;
|
||||
size_t size() const override;
|
||||
void purge() override;
|
||||
void clear() override;
|
||||
CacheStats stats() const override;
|
||||
|
||||
bool mount(const std::string &path) override;
|
||||
void unmount(const std::string &path) override;
|
||||
void setPipe(DataPipe pipe) override;
|
||||
void setRoot(const std::string &path) override;
|
||||
std::string root() const override;
|
||||
|
||||
void process() override;
|
||||
|
||||
protected:
|
||||
AssetHandleBase loadImpl(const AssetID &id, std::type_index type) override;
|
||||
void loadAsyncImpl(const AssetID &id, std::type_index type,
|
||||
std::function<void(AssetHandleBase)> callback) override;
|
||||
AssetHandleBase getImpl(const AssetID &id, std::type_index type) override;
|
||||
void preloadImpl(const AssetID &id, std::type_index type) override;
|
||||
void registerLoaderImpl(std::type_index type,
|
||||
Unique<AssetLoaderBase> loader) override;
|
||||
|
||||
private:
|
||||
struct LoadTask {
|
||||
AssetID id;
|
||||
std::type_index type = typeid(void);
|
||||
std::function<void(AssetHandleBase)> callback;
|
||||
};
|
||||
|
||||
struct LoadedAsset {
|
||||
Ref<Asset> asset;
|
||||
std::type_index type = typeid(void);
|
||||
};
|
||||
|
||||
std::string root_;
|
||||
Unique<AssetCache> cache_;
|
||||
PackManager packManager_;
|
||||
DataPipe pipe_;
|
||||
|
||||
mutable std::shared_mutex mutex_;
|
||||
std::unordered_map<AssetID, LoadedAsset> assets_;
|
||||
std::unordered_map<AssetID, AssetState> states_;
|
||||
std::unordered_map<std::type_index, Unique<AssetLoaderBase>> loaders_;
|
||||
|
||||
std::thread workerThread_;
|
||||
std::queue<LoadTask> taskQueue_;
|
||||
std::mutex taskMutex_;
|
||||
std::condition_variable taskCv_;
|
||||
std::atomic<bool> running_{false};
|
||||
|
||||
std::queue<std::function<void()>> callbackQueue_;
|
||||
std::mutex callbackMutex_;
|
||||
|
||||
/**
|
||||
* @brief 工作线程函数
|
||||
*/
|
||||
void workerFunc();
|
||||
|
||||
/**
|
||||
* @brief 从文件系统加载资源
|
||||
* @param id 资源ID
|
||||
* @param type 资源类型
|
||||
* @return 加载的资源
|
||||
*/
|
||||
Ref<Asset> loadFromFile(const AssetID &id, std::type_index type);
|
||||
|
||||
/**
|
||||
* @brief 从资源包加载资源
|
||||
* @param id 资源ID
|
||||
* @param type 资源类型
|
||||
* @return 加载的资源
|
||||
*/
|
||||
Ref<Asset> loadFromPack(const AssetID &id, std::type_index type);
|
||||
|
||||
/**
|
||||
* @brief 获取加载器
|
||||
* @param type 资源类型
|
||||
* @return 加载器指针
|
||||
*/
|
||||
AssetLoaderBase *getLoader(std::type_index type);
|
||||
|
||||
/**
|
||||
* @brief 根据路径推断资源类型
|
||||
* @param path 资源路径
|
||||
* @return 资源类型索引
|
||||
*/
|
||||
std::type_index inferType(const std::string &path);
|
||||
|
||||
E2D_AUTO_REGISTER_SERVICE(IAssetService, AssetService);
|
||||
};
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/core/service_interface.h>
|
||||
#include <extra2d/core/service_locator.h>
|
||||
#include <extra2d/event/event_dispatcher.h>
|
||||
#include <extra2d/event/event_queue.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
/**
|
||||
* @brief 事件服务接口
|
||||
*/
|
||||
class IEventService : public IService {
|
||||
public:
|
||||
virtual ~IEventService() = default;
|
||||
|
||||
virtual void push(const Event& event) = 0;
|
||||
virtual void push(Event&& event) = 0;
|
||||
virtual bool poll(Event& event) = 0;
|
||||
|
||||
virtual ListenerID on(EventType type, EventDispatcher::EventFn fn) = 0;
|
||||
virtual void off(ListenerID id) = 0;
|
||||
virtual void offAll(EventType type) = 0;
|
||||
virtual void offAll() = 0;
|
||||
|
||||
virtual void dispatch(Event& event) = 0;
|
||||
virtual void process() = 0;
|
||||
|
||||
virtual size_t listenerCount(EventType type) const = 0;
|
||||
virtual size_t totalListeners() const = 0;
|
||||
virtual size_t queueSize() const = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 事件服务实现
|
||||
*/
|
||||
class EventService : public IEventService {
|
||||
public:
|
||||
EventService();
|
||||
~EventService() override = default;
|
||||
|
||||
ServiceInfo info() const override;
|
||||
|
||||
bool init() override;
|
||||
void shutdown() override;
|
||||
void update(f32 dt) override;
|
||||
|
||||
void push(const Event& event) override;
|
||||
void push(Event&& event) override;
|
||||
bool poll(Event& event) override;
|
||||
|
||||
ListenerID on(EventType type, EventDispatcher::EventFn fn) override;
|
||||
void off(ListenerID id) override;
|
||||
void offAll(EventType type) override;
|
||||
void offAll() override;
|
||||
|
||||
void dispatch(Event& event) override;
|
||||
void process() override;
|
||||
|
||||
size_t listenerCount(EventType type) const override;
|
||||
size_t totalListeners() const override;
|
||||
size_t queueSize() const override;
|
||||
|
||||
EventQueue& queue() { return queue_; }
|
||||
const EventQueue& queue() const { return queue_; }
|
||||
EventDispatcher& dispatcher() { return dispatcher_; }
|
||||
const EventDispatcher& dispatcher() const { return dispatcher_; }
|
||||
|
||||
private:
|
||||
EventQueue queue_;
|
||||
EventDispatcher dispatcher_;
|
||||
|
||||
// 服务注册元数据
|
||||
E2D_AUTO_REGISTER_SERVICE(IEventService, EventService);
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,310 @@
|
|||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <extra2d/core/service_interface.h>
|
||||
#include <extra2d/core/service_locator.h>
|
||||
#include <extra2d/core/types.h>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
/**
|
||||
* @brief 日志颜色结构
|
||||
*/
|
||||
struct LogColor {
|
||||
u8 r, g, b;
|
||||
|
||||
constexpr LogColor() : r(255), g(255), b(255) {}
|
||||
constexpr LogColor(u8 r, u8 g, u8 b) : r(r), g(g), b(b) {}
|
||||
|
||||
static constexpr LogColor White() { return LogColor(255, 255, 255); }
|
||||
static constexpr LogColor Gray() { return LogColor(128, 128, 128); }
|
||||
static constexpr LogColor Red() { return LogColor(255, 85, 85); }
|
||||
static constexpr LogColor Green() { return LogColor(85, 255, 85); }
|
||||
static constexpr LogColor Yellow() { return LogColor(255, 255, 85); }
|
||||
static constexpr LogColor Blue() { return LogColor(85, 85, 255); }
|
||||
static constexpr LogColor Magenta() { return LogColor(255, 85, 255); }
|
||||
static constexpr LogColor Cyan() { return LogColor(85, 255, 255); }
|
||||
static constexpr LogColor Orange() { return LogColor(255, 165, 0); }
|
||||
|
||||
static constexpr LogColor Slate() { return LogColor(100, 116, 139); }
|
||||
static constexpr LogColor SlateLight() { return LogColor(148, 163, 184); }
|
||||
static constexpr LogColor Sky() { return LogColor(14, 165, 233); }
|
||||
static constexpr LogColor SkyLight() { return LogColor(125, 211, 252); }
|
||||
static constexpr LogColor Emerald() { return LogColor(16, 185, 129); }
|
||||
static constexpr LogColor EmeraldLight() { return LogColor(110, 231, 183); }
|
||||
static constexpr LogColor Amber() { return LogColor(245, 158, 11); }
|
||||
static constexpr LogColor AmberLight() { return LogColor(252, 211, 77); }
|
||||
static constexpr LogColor Rose() { return LogColor(244, 63, 94); }
|
||||
static constexpr LogColor RoseLight() { return LogColor(253, 164, 175); }
|
||||
static constexpr LogColor Violet() { return LogColor(139, 92, 246); }
|
||||
static constexpr LogColor VioletLight() { return LogColor(196, 181, 253); }
|
||||
static constexpr LogColor Indigo() { return LogColor(99, 102, 241); }
|
||||
static constexpr LogColor IndigoLight() { return LogColor(165, 180, 252); }
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 日志级别枚举
|
||||
*/
|
||||
enum class LogLevel {
|
||||
Trace = 0,
|
||||
Debug = 1,
|
||||
Info = 2,
|
||||
Registry = 3,
|
||||
Warn = 4,
|
||||
Error = 5,
|
||||
Fatal = 6,
|
||||
Off = 7
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 日志服务接口
|
||||
*/
|
||||
class ILogger : public IService {
|
||||
public:
|
||||
virtual ~ILogger() = default;
|
||||
|
||||
/**
|
||||
* @brief 设置日志级别
|
||||
*/
|
||||
virtual void level(LogLevel lvl) = 0;
|
||||
|
||||
/**
|
||||
* @brief 获取日志级别
|
||||
*/
|
||||
virtual LogLevel level() const = 0;
|
||||
|
||||
/**
|
||||
* @brief 检查日志级别是否启用
|
||||
*/
|
||||
virtual bool enabled(LogLevel lvl) const = 0;
|
||||
|
||||
/**
|
||||
* @brief 记录日志(格式化)
|
||||
*/
|
||||
virtual void log(LogLevel lvl, const char *fmt, ...) = 0;
|
||||
|
||||
/**
|
||||
* @brief 记录日志(字符串)
|
||||
*/
|
||||
virtual void log(LogLevel lvl, const std::string &msg) = 0;
|
||||
|
||||
/**
|
||||
* @brief Trace级别日志
|
||||
*/
|
||||
virtual void trace(const char *fmt, ...) = 0;
|
||||
|
||||
/**
|
||||
* @brief Debug级别日志
|
||||
*/
|
||||
virtual void debug(const char *fmt, ...) = 0;
|
||||
|
||||
/**
|
||||
* @brief Info级别日志
|
||||
*/
|
||||
virtual void info(const char *fmt, ...) = 0;
|
||||
|
||||
/**
|
||||
* @brief Registry级别日志(用于模块/服务注册显示)
|
||||
*/
|
||||
virtual void registry(const char *fmt, ...) = 0;
|
||||
|
||||
/**
|
||||
* @brief Warn级别日志
|
||||
*/
|
||||
virtual void warn(const char *fmt, ...) = 0;
|
||||
|
||||
/**
|
||||
* @brief Error级别日志
|
||||
*/
|
||||
virtual void error(const char *fmt, ...) = 0;
|
||||
|
||||
/**
|
||||
* @brief Fatal级别日志
|
||||
*/
|
||||
virtual void fatal(const char *fmt, ...) = 0;
|
||||
|
||||
/**
|
||||
* @brief 设置日志级别颜色
|
||||
* @param lvl 日志级别
|
||||
* @param c 颜色
|
||||
*/
|
||||
virtual void levelColor(LogLevel lvl, const LogColor &c) = 0;
|
||||
|
||||
/**
|
||||
* @brief 获取日志级别颜色
|
||||
* @param lvl 日志级别
|
||||
* @return 颜色
|
||||
*/
|
||||
virtual LogColor levelColor(LogLevel lvl) const = 0;
|
||||
|
||||
/**
|
||||
* @brief 启用/禁用颜色输出
|
||||
* @param on 是否启用
|
||||
*/
|
||||
virtual void colors(bool on) = 0;
|
||||
|
||||
/**
|
||||
* @brief 是否启用颜色输出
|
||||
*/
|
||||
virtual bool colors() const = 0;
|
||||
|
||||
ServiceInfo info() const override {
|
||||
ServiceInfo i;
|
||||
i.name = "Logger";
|
||||
i.priority = ServicePriority::Core;
|
||||
i.enabled = true;
|
||||
return i;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 控制台日志服务实现
|
||||
*/
|
||||
class ConsoleLogger : public ILogger {
|
||||
public:
|
||||
ConsoleLogger();
|
||||
~ConsoleLogger() override;
|
||||
|
||||
bool init() override;
|
||||
void shutdown() override;
|
||||
|
||||
void level(LogLevel lvl) override;
|
||||
LogLevel level() const override;
|
||||
bool enabled(LogLevel lvl) const override;
|
||||
|
||||
void log(LogLevel lvl, const char *fmt, ...) override;
|
||||
void log(LogLevel lvl, const std::string &msg) override;
|
||||
|
||||
void trace(const char *fmt, ...) override;
|
||||
void debug(const char *fmt, ...) override;
|
||||
void info(const char *fmt, ...) override;
|
||||
void registry(const char *fmt, ...) override;
|
||||
void warn(const char *fmt, ...) override;
|
||||
void error(const char *fmt, ...) override;
|
||||
void fatal(const char *fmt, ...) override;
|
||||
|
||||
void levelColor(LogLevel lvl, const LogColor &c) override;
|
||||
LogColor levelColor(LogLevel lvl) const override;
|
||||
void colors(bool on) override;
|
||||
bool colors() const override;
|
||||
|
||||
private:
|
||||
void output(LogLevel lvl, const char *msg);
|
||||
const char *levelString(LogLevel lvl);
|
||||
std::string ansiColor(LogLevel lvl);
|
||||
|
||||
LogLevel level_;
|
||||
bool colors_;
|
||||
LogColor levelColors_[7];
|
||||
class Impl;
|
||||
Unique<Impl> impl_;
|
||||
|
||||
// 服务注册元数据
|
||||
E2D_AUTO_REGISTER_SERVICE(ILogger, ConsoleLogger);
|
||||
};
|
||||
|
||||
} // namespace extra2d
|
||||
|
||||
// 格式化辅助函数 - 将参数转换为字符串
|
||||
namespace extra2d {
|
||||
namespace detail {
|
||||
template <typename T> std::string to_string(T &&value) {
|
||||
using Decayed = std::decay_t<T>;
|
||||
if constexpr (std::is_same_v<Decayed, std::string>) {
|
||||
return value;
|
||||
} else if constexpr (std::is_same_v<Decayed, const char *>) {
|
||||
return value ? value : "(null)";
|
||||
} else if constexpr (std::is_arithmetic_v<Decayed>) {
|
||||
if constexpr (std::is_same_v<Decayed, bool>) {
|
||||
return value ? "true" : "false";
|
||||
} else if constexpr (std::is_floating_point_v<Decayed>) {
|
||||
return std::to_string(value);
|
||||
} else {
|
||||
return std::to_string(value);
|
||||
}
|
||||
} else {
|
||||
return "<?>";
|
||||
}
|
||||
}
|
||||
|
||||
inline void format_impl(std::string &result, const char *fmt) { result += fmt; }
|
||||
|
||||
template <typename T, typename... Args>
|
||||
void format_impl(std::string &result, const char *fmt, T &&value,
|
||||
Args &&...args) {
|
||||
const char *p = fmt;
|
||||
while (*p) {
|
||||
if (*p == '{' && *(p + 1) == '}') {
|
||||
result += to_string(std::forward<T>(value));
|
||||
format_impl(result, p + 2, std::forward<Args>(args)...);
|
||||
return;
|
||||
}
|
||||
result += *p++;
|
||||
}
|
||||
result += " ";
|
||||
result += to_string(std::forward<T>(value));
|
||||
format_impl(result, p, std::forward<Args>(args)...);
|
||||
}
|
||||
} // namespace detail
|
||||
|
||||
template <typename... Args>
|
||||
std::string format_str(const char *fmt, Args &&...args) {
|
||||
if constexpr (sizeof...(args) == 0) {
|
||||
return std::string(fmt);
|
||||
} else {
|
||||
std::string result;
|
||||
detail::format_impl(result, fmt, std::forward<Args>(args)...);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
} // namespace extra2d
|
||||
|
||||
// 便捷宏 - 自动获取日志服务
|
||||
#define E2D_LOG(lvl, ...) \
|
||||
do { \
|
||||
if (auto logService = ::extra2d::ServiceLocator::instance() \
|
||||
.tryGet<::extra2d::ILogger>()) { \
|
||||
if (logService->enabled(lvl)) { \
|
||||
logService->log(lvl, ::extra2d::format_str(__VA_ARGS__)); \
|
||||
} \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define E2D_TRACE(...) E2D_LOG(::extra2d::LogLevel::Trace, __VA_ARGS__)
|
||||
#define E2D_DEBUG(...) E2D_LOG(::extra2d::LogLevel::Debug, __VA_ARGS__)
|
||||
#define E2D_INFO(...) E2D_LOG(::extra2d::LogLevel::Info, __VA_ARGS__)
|
||||
#define E2D_REGISTRY(...) E2D_LOG(::extra2d::LogLevel::Registry, __VA_ARGS__)
|
||||
#define E2D_WARN(...) E2D_LOG(::extra2d::LogLevel::Warn, __VA_ARGS__)
|
||||
#define E2D_ERROR(...) E2D_LOG(::extra2d::LogLevel::Error, __VA_ARGS__)
|
||||
#define E2D_FATAL(...) E2D_LOG(::extra2d::LogLevel::Fatal, __VA_ARGS__)
|
||||
|
||||
// 带颜色参数的日志宏
|
||||
#define E2D_LOG_COLOR(lvl, c, ...) \
|
||||
do { \
|
||||
if (auto logService = ::extra2d::ServiceLocator::instance() \
|
||||
.tryGet<::extra2d::ILogger>()) { \
|
||||
if (logService->enabled(lvl)) { \
|
||||
auto prevColor = logService->levelColor(lvl); \
|
||||
logService->levelColor(lvl, c); \
|
||||
logService->log(lvl, ::extra2d::format_str(__VA_ARGS__)); \
|
||||
logService->levelColor(lvl, prevColor); \
|
||||
} \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define E2D_TRACE_COLOR(c, ...) \
|
||||
E2D_LOG_COLOR(::extra2d::LogLevel::Trace, c, __VA_ARGS__)
|
||||
#define E2D_DEBUG_COLOR(c, ...) \
|
||||
E2D_LOG_COLOR(::extra2d::LogLevel::Debug, c, __VA_ARGS__)
|
||||
#define E2D_INFO_COLOR(c, ...) \
|
||||
E2D_LOG_COLOR(::extra2d::LogLevel::Info, c, __VA_ARGS__)
|
||||
#define E2D_REGISTRY_COLOR(c, ...) \
|
||||
E2D_LOG_COLOR(::extra2d::LogLevel::Registry, c, __VA_ARGS__)
|
||||
#define E2D_WARN_COLOR(c, ...) \
|
||||
E2D_LOG_COLOR(::extra2d::LogLevel::Warn, c, __VA_ARGS__)
|
||||
#define E2D_ERROR_COLOR(c, ...) \
|
||||
E2D_LOG_COLOR(::extra2d::LogLevel::Error, c, __VA_ARGS__)
|
||||
#define E2D_FATAL_COLOR(c, ...) \
|
||||
E2D_LOG_COLOR(::extra2d::LogLevel::Fatal, c, __VA_ARGS__)
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/core/service_interface.h>
|
||||
#include <extra2d/core/service_locator.h>
|
||||
#include <extra2d/utils/timer.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
/**
|
||||
* @brief 计时器服务接口
|
||||
*/
|
||||
class ITimerService : public IService {
|
||||
public:
|
||||
virtual ~ITimerService() = default;
|
||||
|
||||
virtual u32 add(f32 delay, Timer::Fn fn) = 0;
|
||||
virtual u32 addRepeat(f32 interval, Timer::Fn fn) = 0;
|
||||
virtual void cancel(u32 timerId) = 0;
|
||||
virtual void pauseTimer(u32 timerId) = 0;
|
||||
virtual void resumeTimer(u32 timerId) = 0;
|
||||
virtual void clear() = 0;
|
||||
virtual size_t count() const = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 计时器服务实现
|
||||
*/
|
||||
class TimerService : public ITimerService {
|
||||
public:
|
||||
TimerService();
|
||||
~TimerService() override = default;
|
||||
|
||||
ServiceInfo info() const override;
|
||||
|
||||
bool init() override;
|
||||
void shutdown() override;
|
||||
void update(f32 dt) override;
|
||||
|
||||
u32 add(f32 delay, Timer::Fn fn) override;
|
||||
u32 addRepeat(f32 interval, Timer::Fn fn) override;
|
||||
void cancel(u32 timerId) override;
|
||||
void pauseTimer(u32 timerId) override;
|
||||
void resumeTimer(u32 timerId) override;
|
||||
void clear() override;
|
||||
size_t count() const override;
|
||||
|
||||
TimerManager& mgr() { return mgr_; }
|
||||
const TimerManager& mgr() const { return mgr_; }
|
||||
|
||||
private:
|
||||
TimerManager mgr_;
|
||||
|
||||
// 服务注册元数据
|
||||
E2D_AUTO_REGISTER_SERVICE(ITimerService, TimerService);
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -11,37 +11,37 @@ namespace extra2d {
|
|||
class Random {
|
||||
public:
|
||||
/// 获取单例实例
|
||||
static Random &getInstance();
|
||||
static Random &get();
|
||||
|
||||
/// 设置随机种子
|
||||
void setSeed(uint32 seed);
|
||||
void seed(u32 s);
|
||||
|
||||
/// 使用当前时间作为种子
|
||||
void randomize();
|
||||
|
||||
/// 获取 [0, 1) 范围内的随机浮点数
|
||||
float getFloat();
|
||||
f32 randomF32();
|
||||
|
||||
/// 获取 [min, max] 范围内的随机浮点数
|
||||
float getFloat(float min, float max);
|
||||
f32 randomF32(f32 min, f32 max);
|
||||
|
||||
/// 获取 [0, max] 范围内的随机整数
|
||||
int getInt(int max);
|
||||
i32 randomI32(i32 max);
|
||||
|
||||
/// 获取 [min, max] 范围内的随机整数
|
||||
int getInt(int min, int max);
|
||||
i32 randomI32(i32 min, i32 max);
|
||||
|
||||
/// 获取随机布尔值
|
||||
bool getBool();
|
||||
bool boolean();
|
||||
|
||||
/// 获取随机布尔值(带概率)
|
||||
bool getBool(float probability);
|
||||
bool boolean(f32 probability);
|
||||
|
||||
/// 获取指定范围内的随机角度(弧度)
|
||||
float getAngle();
|
||||
f32 angle();
|
||||
|
||||
/// 获取 [-1, 1] 范围内的随机数(用于方向)
|
||||
float getSigned();
|
||||
f32 signedF32();
|
||||
|
||||
private:
|
||||
Random();
|
||||
|
|
@ -59,27 +59,27 @@ private:
|
|||
// ============================================================================
|
||||
|
||||
/// 获取 [0, 1) 范围内的随机浮点数
|
||||
inline float randomFloat() { return Random::getInstance().getFloat(); }
|
||||
inline f32 randF32() { return Random::get().randomF32(); }
|
||||
|
||||
/// 获取 [min, max] 范围内的随机浮点数
|
||||
inline float randomFloat(float min, float max) {
|
||||
return Random::getInstance().getFloat(min, max);
|
||||
inline f32 randF32(f32 min, f32 max) {
|
||||
return Random::get().randomF32(min, max);
|
||||
}
|
||||
|
||||
/// 获取 [0, max] 范围内的随机整数
|
||||
inline int randomInt(int max) { return Random::getInstance().getInt(max); }
|
||||
inline i32 randI32(i32 max) { return Random::get().randomI32(max); }
|
||||
|
||||
/// 获取 [min, max] 范围内的随机整数
|
||||
inline int randomInt(int min, int max) {
|
||||
return Random::getInstance().getInt(min, max);
|
||||
inline i32 randI32(i32 min, i32 max) {
|
||||
return Random::get().randomI32(min, max);
|
||||
}
|
||||
|
||||
/// 获取随机布尔值
|
||||
inline bool randomBool() { return Random::getInstance().getBool(); }
|
||||
inline bool randBool() { return Random::get().boolean(); }
|
||||
|
||||
/// 获取随机布尔值(带概率)
|
||||
inline bool randomBool(float probability) {
|
||||
return Random::getInstance().getBool(probability);
|
||||
inline bool randBool(f32 probability) {
|
||||
return Random::get().boolean(probability);
|
||||
}
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
|
|||
|
|
@ -16,12 +16,12 @@ public:
|
|||
using Clock = std::chrono::steady_clock;
|
||||
using TimePoint = Clock::time_point;
|
||||
using Duration = Clock::duration;
|
||||
using Callback = Function<void()>;
|
||||
using Fn = std::function<void()>;
|
||||
|
||||
Timer(float interval, bool repeat, Callback callback);
|
||||
Timer(f32 interval, bool repeat, Fn fn);
|
||||
|
||||
/// 更新计时器,返回 true 如果触发了回调
|
||||
bool update(float deltaTime);
|
||||
bool update(f32 dt);
|
||||
|
||||
/// 重置计时器
|
||||
void reset();
|
||||
|
|
@ -36,27 +36,27 @@ public:
|
|||
void cancel();
|
||||
|
||||
/// 是否有效
|
||||
bool isValid() const { return valid_; }
|
||||
bool valid() const { return valid_; }
|
||||
|
||||
/// 是否暂停
|
||||
bool isPaused() const { return paused_; }
|
||||
bool paused() const { return paused_; }
|
||||
|
||||
/// 获取剩余时间(秒)
|
||||
float getRemaining() const;
|
||||
f32 remaining() const;
|
||||
|
||||
/// 获取唯一ID
|
||||
uint32 getId() const { return id_; }
|
||||
u32 id() const { return id_; }
|
||||
|
||||
private:
|
||||
uint32 id_;
|
||||
float interval_;
|
||||
float elapsed_;
|
||||
u32 id_;
|
||||
f32 interval_;
|
||||
f32 elapsed_;
|
||||
bool repeat_;
|
||||
bool paused_;
|
||||
bool valid_;
|
||||
Callback callback_;
|
||||
Fn fn_;
|
||||
|
||||
static uint32 nextId_;
|
||||
static u32 nextId_;
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
|
|
@ -68,32 +68,32 @@ public:
|
|||
~TimerManager() = default;
|
||||
|
||||
/// 创建单次计时器,返回计时器ID
|
||||
uint32 addTimer(float delay, Timer::Callback callback);
|
||||
u32 add(f32 delay, Timer::Fn fn);
|
||||
|
||||
/// 创建重复计时器,返回计时器ID
|
||||
uint32 addRepeatingTimer(float interval, Timer::Callback callback);
|
||||
u32 addRepeat(f32 interval, Timer::Fn fn);
|
||||
|
||||
/// 取消指定ID的计时器
|
||||
void cancelTimer(uint32 timerId);
|
||||
void cancel(u32 timerId);
|
||||
|
||||
/// 暂停指定ID的计时器
|
||||
void pauseTimer(uint32 timerId);
|
||||
void pause(u32 timerId);
|
||||
|
||||
/// 恢复指定ID的计时器
|
||||
void resumeTimer(uint32 timerId);
|
||||
void resume(u32 timerId);
|
||||
|
||||
/// 更新所有计时器(每帧调用)
|
||||
void update(float deltaTime);
|
||||
void update(f32 dt);
|
||||
|
||||
/// 清除所有计时器
|
||||
void clear();
|
||||
|
||||
/// 获取计时器数量
|
||||
size_t getTimerCount() const { return timers_.size(); }
|
||||
size_t count() const { return timers_.size(); }
|
||||
|
||||
private:
|
||||
std::map<uint32, std::unique_ptr<Timer>> timers_;
|
||||
std::vector<uint32> timersToRemove_;
|
||||
std::map<u32, std::unique_ptr<Timer>> timers_;
|
||||
std::vector<u32> timersToRemove_;
|
||||
};
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/core/types.h>
|
||||
#include <extra2d/core/string.h>
|
||||
#include <extra2d/core/math_types.h>
|
||||
#include <string>
|
||||
#include <functional>
|
||||
|
||||
#include <SDL.h>
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,68 +1,56 @@
|
|||
#include <extra2d/app/application.h>
|
||||
#include <extra2d/audio/audio_engine.h>
|
||||
#include <extra2d/event/event_dispatcher.h>
|
||||
#include <extra2d/event/event_queue.h>
|
||||
#include <extra2d/graphics/renderer.h>
|
||||
#include <extra2d/platform/input.h>
|
||||
#include <extra2d/utils/logger.h>
|
||||
#include <extra2d/window/window.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
#include <extra2d/core/registry.h>
|
||||
#include <extra2d/platform/glfw/glfw_window.h>
|
||||
#include <extra2d/platform/window_module.h>
|
||||
#include <extra2d/services/event_service.h>
|
||||
#include <extra2d/services/logger_service.h>
|
||||
#include <extra2d/services/timer_service.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
static double getTimeSeconds() {
|
||||
static f64 getTimeSeconds() {
|
||||
#ifdef __SWITCH__
|
||||
struct timespec ts;
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
return static_cast<f64>(ts.tv_sec) +
|
||||
static_cast<f64>(ts.tv_nsec) / 1000000000.0;
|
||||
#else
|
||||
using namespace std::chrono;
|
||||
auto now = steady_clock::now();
|
||||
auto duration = now.time_since_epoch();
|
||||
return duration_cast<std::chrono::duration<double>>(duration).count();
|
||||
return duration_cast<std::chrono::duration<f64>>(duration).count();
|
||||
#endif
|
||||
}
|
||||
|
||||
Application &Application::instance() {
|
||||
Application &Application::get() {
|
||||
static Application instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
Application::~Application() { shutdown(); }
|
||||
Application::Application() { Registry::instance().setApp(this); }
|
||||
|
||||
bool Application::init(const AppConfig &config) {
|
||||
Application::~Application() {
|
||||
if (initialized_) {
|
||||
shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
bool Application::init() {
|
||||
if (initialized_) {
|
||||
E2D_LOG_WARN("Application already initialized");
|
||||
return true;
|
||||
}
|
||||
|
||||
config_ = config;
|
||||
|
||||
window_ = makeUnique<Window>();
|
||||
WindowConfig winConfig;
|
||||
winConfig.title = config.title;
|
||||
winConfig.width = config.width;
|
||||
winConfig.height = config.height;
|
||||
winConfig.fullscreen = config.fullscreen;
|
||||
winConfig.vsync = config.vsync;
|
||||
|
||||
if (!window_->create(winConfig)) {
|
||||
E2D_LOG_ERROR("Failed to create window");
|
||||
// 初始化所有模块(拓扑排序)
|
||||
// 服务通过 E2D_AUTO_REGISTER_SERVICE 宏自动注册
|
||||
if (!Registry::instance().init()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
renderer_ = makeUnique<Renderer>();
|
||||
if (!renderer_->init(window_.get())) {
|
||||
E2D_LOG_ERROR("Failed to initialize renderer");
|
||||
window_->destroy();
|
||||
return false;
|
||||
}
|
||||
|
||||
eventQueue_ = makeUnique<EventQueue>();
|
||||
eventDispatcher_ = makeUnique<EventDispatcher>();
|
||||
|
||||
AudioEngine::getInstance().initialize();
|
||||
// 初始化所有服务
|
||||
ServiceLocator::instance().init();
|
||||
|
||||
initialized_ = true;
|
||||
running_ = true;
|
||||
|
||||
E2D_LOG_INFO("Application initialized successfully");
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -70,80 +58,73 @@ void Application::shutdown() {
|
|||
if (!initialized_)
|
||||
return;
|
||||
|
||||
E2D_LOG_INFO("Shutting down application...");
|
||||
|
||||
AudioEngine::getInstance().shutdown();
|
||||
|
||||
eventDispatcher_.reset();
|
||||
eventQueue_.reset();
|
||||
|
||||
if (renderer_) {
|
||||
renderer_->shutdown();
|
||||
renderer_.reset();
|
||||
}
|
||||
|
||||
if (window_) {
|
||||
window_->destroy();
|
||||
window_.reset();
|
||||
}
|
||||
ServiceLocator::instance().shutdown();
|
||||
ServiceLocator::instance().clear();
|
||||
Registry::instance().shutdown();
|
||||
Registry::instance().clear();
|
||||
|
||||
initialized_ = false;
|
||||
running_ = false;
|
||||
|
||||
E2D_LOG_INFO("Application shutdown complete");
|
||||
}
|
||||
|
||||
void Application::run() {
|
||||
if (!initialized_) {
|
||||
E2D_LOG_ERROR("Application not initialized");
|
||||
if (!initialized_)
|
||||
return;
|
||||
|
||||
auto *winMod = get<WindowModule>();
|
||||
if (!winMod || !winMod->win())
|
||||
return;
|
||||
}
|
||||
|
||||
lastFrameTime_ = getTimeSeconds();
|
||||
|
||||
while (running_ && !window_->shouldClose()) {
|
||||
while (running_ && !winMod->win()->shouldClose()) {
|
||||
mainLoop();
|
||||
}
|
||||
}
|
||||
|
||||
void Application::quit() {
|
||||
shouldQuit_ = true;
|
||||
running_ = false;
|
||||
}
|
||||
|
||||
void Application::pause() {
|
||||
if (!paused_) {
|
||||
paused_ = true;
|
||||
E2D_LOG_INFO("Application paused");
|
||||
ServiceLocator::instance().pause();
|
||||
}
|
||||
}
|
||||
|
||||
void Application::resume() {
|
||||
if (paused_) {
|
||||
paused_ = false;
|
||||
ServiceLocator::instance().resume();
|
||||
lastFrameTime_ = getTimeSeconds();
|
||||
E2D_LOG_INFO("Application resumed");
|
||||
}
|
||||
}
|
||||
|
||||
void Application::mainLoop() {
|
||||
double currentTime = getTimeSeconds();
|
||||
deltaTime_ = static_cast<float>(currentTime - lastFrameTime_);
|
||||
f64 currentTime = getTimeSeconds();
|
||||
dt_ = static_cast<f32>(currentTime - lastFrameTime_);
|
||||
lastFrameTime_ = currentTime;
|
||||
|
||||
totalTime_ += deltaTime_;
|
||||
totalTime_ += dt_;
|
||||
|
||||
frameCount_++;
|
||||
fpsTimer_ += deltaTime_;
|
||||
fpsTimer_ += dt_;
|
||||
if (fpsTimer_ >= 1.0f) {
|
||||
currentFps_ = frameCount_;
|
||||
fps_ = frameCount_;
|
||||
frameCount_ = 0;
|
||||
fpsTimer_ -= 1.0f;
|
||||
}
|
||||
|
||||
window_->pollEvents();
|
||||
auto *winMod = get<WindowModule>();
|
||||
if (winMod && winMod->win()) {
|
||||
winMod->win()->poll();
|
||||
}
|
||||
|
||||
if (eventDispatcher_ && eventQueue_) {
|
||||
eventDispatcher_->processQueue(*eventQueue_);
|
||||
auto eventService = ServiceLocator::instance().get<IEventService>();
|
||||
if (eventService) {
|
||||
eventService->process();
|
||||
}
|
||||
|
||||
if (!paused_) {
|
||||
|
|
@ -151,39 +132,21 @@ void Application::mainLoop() {
|
|||
}
|
||||
|
||||
render();
|
||||
|
||||
if (!config_.vsync && config_.fpsLimit > 0) {
|
||||
double frameEndTime = getTimeSeconds();
|
||||
double frameTime = frameEndTime - currentTime;
|
||||
double target = 1.0 / static_cast<double>(config_.fpsLimit);
|
||||
if (frameTime < target) {
|
||||
auto sleepSeconds = target - frameTime;
|
||||
std::this_thread::sleep_for(std::chrono::duration<double>(sleepSeconds));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Application::update() {
|
||||
// 子系统更新可以在这里添加
|
||||
}
|
||||
void Application::update() { ServiceLocator::instance().update(dt_); }
|
||||
|
||||
void Application::render() {
|
||||
if (!renderer_) {
|
||||
auto *winMod = get<WindowModule>();
|
||||
if (!winMod || !winMod->win())
|
||||
return;
|
||||
}
|
||||
|
||||
renderer_->beginFrame(Color::Black);
|
||||
|
||||
// 渲染内容可以在这里添加
|
||||
|
||||
renderer_->endFrame();
|
||||
window_->swapBuffers();
|
||||
winMod->win()->swap();
|
||||
}
|
||||
|
||||
Input &Application::input() { return *window_->getInput(); }
|
||||
|
||||
EventQueue &Application::eventQueue() { return *eventQueue_; }
|
||||
|
||||
EventDispatcher &Application::eventDispatcher() { return *eventDispatcher_; }
|
||||
GLFWWindow *Application::window() {
|
||||
auto *winMod = get<WindowModule>();
|
||||
return winMod ? winMod->win() : nullptr;
|
||||
}
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
|
|||
|
|
@ -0,0 +1,62 @@
|
|||
#include <extra2d/asset/asset.h>
|
||||
|
||||
#define STB_TRUETYPE_IMPLEMENTATION
|
||||
#include <stb/stb_truetype.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// FontAsset::Impl - Pimpl 实现类
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
class FontAsset::Impl {
|
||||
public:
|
||||
stbtt_fontinfo info;
|
||||
bool initialized = false;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// FontAsset 实现
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
FontAsset::FontAsset() : impl_(ptr::makeUnique<Impl>()) {}
|
||||
|
||||
FontAsset::~FontAsset() = default;
|
||||
|
||||
bool FontAsset::loaded() const {
|
||||
return state_.load(std::memory_order_acquire) == AssetState::Loaded && impl_->initialized;
|
||||
}
|
||||
|
||||
float FontAsset::scaleForPixelHeight(float pixels) const {
|
||||
if (!impl_->initialized || data_.empty()) {
|
||||
return 0.0f;
|
||||
}
|
||||
return stbtt_ScaleForPixelHeight(&impl_->info, pixels);
|
||||
}
|
||||
|
||||
bool FontAsset::setData(std::vector<u8> data) {
|
||||
if (data.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
data_ = std::move(data);
|
||||
|
||||
if (!stbtt_InitFont(&impl_->info, data_.data(), 0)) {
|
||||
data_.clear();
|
||||
impl_->initialized = false;
|
||||
setState(AssetState::Failed);
|
||||
return false;
|
||||
}
|
||||
|
||||
impl_->initialized = true;
|
||||
setState(AssetState::Loaded);
|
||||
return true;
|
||||
}
|
||||
|
||||
void FontAsset::release() {
|
||||
data_.clear();
|
||||
impl_->initialized = false;
|
||||
setState(AssetState::Unloaded);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,121 @@
|
|||
#include <extra2d/asset/asset_cache.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// AssetCache 实现
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
AssetCache::AssetCache(size_t limit)
|
||||
: limit_(limit) {
|
||||
stats_.limit = limit;
|
||||
}
|
||||
|
||||
bool AssetCache::has(const AssetID& id) const {
|
||||
std::shared_lock<std::shared_mutex> lock(mutex_);
|
||||
return entries_.find(id) != entries_.end();
|
||||
}
|
||||
|
||||
bool AssetCache::remove(const AssetID& id) {
|
||||
std::unique_lock<std::shared_mutex> lock(mutex_);
|
||||
|
||||
auto it = entries_.find(id);
|
||||
if (it == entries_.end()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bytes_ -= it->second.entry->asset->memSize();
|
||||
lruList_.erase(it->second.lruIterator);
|
||||
entries_.erase(it);
|
||||
--stats_.count;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void AssetCache::setLimit(size_t limit) {
|
||||
std::unique_lock<std::shared_mutex> lock(mutex_);
|
||||
limit_ = limit;
|
||||
stats_.limit = limit;
|
||||
|
||||
if (limit > 0 && bytes_ > limit) {
|
||||
evict();
|
||||
}
|
||||
}
|
||||
|
||||
size_t AssetCache::count() const {
|
||||
std::shared_lock<std::shared_mutex> lock(mutex_);
|
||||
return entries_.size();
|
||||
}
|
||||
|
||||
size_t AssetCache::purge() {
|
||||
std::unique_lock<std::shared_mutex> lock(mutex_);
|
||||
|
||||
size_t purged = 0;
|
||||
auto it = entries_.begin();
|
||||
|
||||
while (it != entries_.end()) {
|
||||
if (canEvict(*it->second.entry)) {
|
||||
bytes_ -= it->second.entry->asset->memSize();
|
||||
lruList_.erase(it->second.lruIterator);
|
||||
it = entries_.erase(it);
|
||||
++purged;
|
||||
--stats_.count;
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
return purged;
|
||||
}
|
||||
|
||||
void AssetCache::clear() {
|
||||
std::unique_lock<std::shared_mutex> lock(mutex_);
|
||||
entries_.clear();
|
||||
lruList_.clear();
|
||||
bytes_ = 0;
|
||||
stats_.count = 0;
|
||||
stats_.bytes = 0;
|
||||
}
|
||||
|
||||
CacheStats AssetCache::stats() const {
|
||||
std::shared_lock<std::shared_mutex> lock(mutex_);
|
||||
stats_.bytes = bytes_;
|
||||
return stats_;
|
||||
}
|
||||
|
||||
void AssetCache::resetStats() {
|
||||
std::unique_lock<std::shared_mutex> lock(mutex_);
|
||||
stats_.hits = 0;
|
||||
stats_.misses = 0;
|
||||
}
|
||||
|
||||
void AssetCache::evict() {
|
||||
while (!lruList_.empty() && (limit_ > 0 && bytes_ > limit_)) {
|
||||
AssetID id = lruList_.front();
|
||||
|
||||
auto it = entries_.find(id);
|
||||
if (it != entries_.end()) {
|
||||
if (canEvict(*it->second.entry)) {
|
||||
bytes_ -= it->second.entry->asset->memSize();
|
||||
entries_.erase(it);
|
||||
lruList_.pop_front();
|
||||
--stats_.count;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
lruList_.pop_front();
|
||||
if (it != entries_.end()) {
|
||||
it->second.lruIterator = lruList_.insert(lruList_.end(), id);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool AssetCache::canEvict(const CacheEntry& entry) const {
|
||||
long useCount = entry.asset.use_count();
|
||||
return useCount <= 1;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,418 @@
|
|||
#include <extra2d/asset/asset_loader.h>
|
||||
#include <extra2d/services/logger_service.h>
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
|
||||
#define STB_IMAGE_IMPLEMENTATION
|
||||
#include <stb/stb_image.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 辅助函数
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
namespace {
|
||||
|
||||
/**
|
||||
* @brief 转换为小写
|
||||
*/
|
||||
std::string toLower(const std::string& str) {
|
||||
std::string result = str;
|
||||
std::transform(result.begin(), result.end(), result.begin(),
|
||||
[](unsigned char c) { return std::tolower(c); });
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取文件扩展名
|
||||
*/
|
||||
std::string getExtension(const std::string& path) {
|
||||
size_t pos = path.rfind('.');
|
||||
if (pos == std::string::npos) {
|
||||
return "";
|
||||
}
|
||||
return toLower(path.substr(pos));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 读取文件内容
|
||||
*/
|
||||
std::vector<u8> readFile(const std::string& path) {
|
||||
std::ifstream file(path, std::ios::binary | std::ios::ate);
|
||||
if (!file) {
|
||||
return {};
|
||||
}
|
||||
|
||||
size_t size = static_cast<size_t>(file.tellg());
|
||||
file.seekg(0, std::ios::beg);
|
||||
|
||||
std::vector<u8> data(size);
|
||||
if (!file.read(reinterpret_cast<char*>(data.data()), size)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// TextureLoader 实现
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
TextureLoader::TextureLoader() = default;
|
||||
|
||||
TextureLoader::~TextureLoader() = default;
|
||||
|
||||
Ref<TextureAsset> TextureLoader::load(const std::string& path) {
|
||||
auto data = readFile(path);
|
||||
if (data.empty()) {
|
||||
E2D_ERROR("Failed to read texture file: {}", path);
|
||||
return nullptr;
|
||||
}
|
||||
return loadFromMemory(data.data(), data.size());
|
||||
}
|
||||
|
||||
Ref<TextureAsset> TextureLoader::loadFromMemory(const u8* data, size_t size) {
|
||||
if (!data || size == 0) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int width, height, channels;
|
||||
u8* pixels = stbi_load_from_memory(data, static_cast<int>(size),
|
||||
&width, &height, &channels,
|
||||
desiredChannels_);
|
||||
|
||||
if (!pixels) {
|
||||
E2D_ERROR("Failed to load texture from memory: {}", stbi_failure_reason());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int actualChannels = desiredChannels_ > 0 ? desiredChannels_ : channels;
|
||||
|
||||
auto asset = ptr::make<TextureAsset>();
|
||||
|
||||
Unique<u8[]> pixelData(new u8[static_cast<size_t>(width) * height * actualChannels]);
|
||||
std::memcpy(pixelData.get(), pixels,
|
||||
static_cast<size_t>(width) * height * actualChannels);
|
||||
|
||||
asset->setData(width, height, actualChannels, std::move(pixelData));
|
||||
|
||||
stbi_image_free(pixels);
|
||||
|
||||
return asset;
|
||||
}
|
||||
|
||||
bool TextureLoader::canLoad(const std::string& path) const {
|
||||
std::string ext = getExtension(path);
|
||||
auto exts = extensions();
|
||||
return std::find(exts.begin(), exts.end(), ext) != exts.end();
|
||||
}
|
||||
|
||||
std::vector<std::string> TextureLoader::extensions() const {
|
||||
return {".png", ".jpg", ".jpeg", ".bmp", ".tga", ".gif", ".psd", ".hdr", ".pic"};
|
||||
}
|
||||
|
||||
void TextureLoader::setDesiredChannels(int channels) {
|
||||
desiredChannels_ = std::clamp(channels, 0, 4);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// FontLoader 实现
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
Ref<FontAsset> FontLoader::load(const std::string& path) {
|
||||
auto data = readFile(path);
|
||||
if (data.empty()) {
|
||||
E2D_ERROR("Failed to read font file: {}", path);
|
||||
return nullptr;
|
||||
}
|
||||
return loadFromMemory(data.data(), data.size());
|
||||
}
|
||||
|
||||
Ref<FontAsset> FontLoader::loadFromMemory(const u8* data, size_t size) {
|
||||
if (!data || size == 0) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto asset = ptr::make<FontAsset>();
|
||||
|
||||
std::vector<u8> fontData(data, data + size);
|
||||
if (!asset->setData(std::move(fontData))) {
|
||||
E2D_ERROR("Failed to initialize font from memory");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return asset;
|
||||
}
|
||||
|
||||
bool FontLoader::canLoad(const std::string& path) const {
|
||||
std::string ext = getExtension(path);
|
||||
auto exts = extensions();
|
||||
return std::find(exts.begin(), exts.end(), ext) != exts.end();
|
||||
}
|
||||
|
||||
std::vector<std::string> FontLoader::extensions() const {
|
||||
return {".ttf", ".otf", ".ttc"};
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// ShaderLoader 实现
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
Ref<ShaderAsset> ShaderLoader::load(const std::string& path) {
|
||||
auto data = readFile(path);
|
||||
if (data.empty()) {
|
||||
E2D_ERROR("Failed to read shader file: {}", path);
|
||||
return nullptr;
|
||||
}
|
||||
return loadFromMemory(data.data(), data.size());
|
||||
}
|
||||
|
||||
Ref<ShaderAsset> ShaderLoader::loadFromMemory(const u8* data, size_t size) {
|
||||
if (!data || size == 0) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::string content(reinterpret_cast<const char*>(data), size);
|
||||
|
||||
std::string vertexSrc, fragmentSrc;
|
||||
|
||||
if (content.find(vertexMarker_) != std::string::npos) {
|
||||
if (!parseCombined(content, vertexSrc, fragmentSrc)) {
|
||||
E2D_ERROR("Failed to parse combined shader file");
|
||||
return nullptr;
|
||||
}
|
||||
} else {
|
||||
vertexSrc = content;
|
||||
fragmentSrc = content;
|
||||
}
|
||||
|
||||
auto asset = ptr::make<ShaderAsset>();
|
||||
asset->setSource(std::move(vertexSrc), std::move(fragmentSrc));
|
||||
|
||||
return asset;
|
||||
}
|
||||
|
||||
bool ShaderLoader::canLoad(const std::string& path) const {
|
||||
std::string ext = getExtension(path);
|
||||
auto exts = extensions();
|
||||
return std::find(exts.begin(), exts.end(), ext) != exts.end();
|
||||
}
|
||||
|
||||
std::vector<std::string> ShaderLoader::extensions() const {
|
||||
return {".vert", ".frag", ".glsl", ".vs", ".fs"};
|
||||
}
|
||||
|
||||
bool ShaderLoader::parseCombined(const std::string& content,
|
||||
std::string& vertex,
|
||||
std::string& fragment) {
|
||||
size_t vertexPos = content.find(vertexMarker_);
|
||||
size_t fragmentPos = content.find(fragmentMarker_);
|
||||
|
||||
if (vertexPos == std::string::npos || fragmentPos == std::string::npos) {
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t vertexStart = vertexPos + vertexMarker_.length();
|
||||
|
||||
if (vertexPos < fragmentPos) {
|
||||
vertex = content.substr(vertexStart, fragmentPos - vertexStart);
|
||||
fragment = content.substr(fragmentPos + fragmentMarker_.length());
|
||||
} else {
|
||||
fragment = content.substr(fragmentPos + fragmentMarker_.length(),
|
||||
vertexPos - fragmentPos - fragmentMarker_.length());
|
||||
vertex = content.substr(vertexStart);
|
||||
}
|
||||
|
||||
auto trim = [](std::string& s) {
|
||||
size_t start = s.find_first_not_of(" \t\r\n");
|
||||
if (start == std::string::npos) {
|
||||
s.clear();
|
||||
return;
|
||||
}
|
||||
size_t end = s.find_last_not_of(" \t\r\n");
|
||||
s = s.substr(start, end - start + 1);
|
||||
};
|
||||
|
||||
trim(vertex);
|
||||
trim(fragment);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// AudioLoader 实现
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
Ref<AudioAsset> AudioLoader::load(const std::string& path) {
|
||||
auto data = readFile(path);
|
||||
if (data.empty()) {
|
||||
E2D_ERROR("Failed to read audio file: {}", path);
|
||||
return nullptr;
|
||||
}
|
||||
return loadFromMemory(data.data(), data.size());
|
||||
}
|
||||
|
||||
Ref<AudioAsset> AudioLoader::loadFromMemory(const u8* data, size_t size) {
|
||||
if (!data || size == 0) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::string ext = ".wav";
|
||||
if (size >= 4) {
|
||||
if (data[0] == 'R' && data[1] == 'I' && data[2] == 'F' && data[3] == 'F') {
|
||||
return loadWav(data, size);
|
||||
}
|
||||
}
|
||||
|
||||
E2D_ERROR("Unsupported audio format");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool AudioLoader::canLoad(const std::string& path) const {
|
||||
std::string ext = getExtension(path);
|
||||
auto exts = extensions();
|
||||
return std::find(exts.begin(), exts.end(), ext) != exts.end();
|
||||
}
|
||||
|
||||
std::vector<std::string> AudioLoader::extensions() const {
|
||||
return {".wav"};
|
||||
}
|
||||
|
||||
Ref<AudioAsset> AudioLoader::loadWav(const u8* data, size_t size) {
|
||||
if (size < 44) {
|
||||
E2D_ERROR("WAV file too small");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (data[0] != 'R' || data[1] != 'I' || data[2] != 'F' || data[3] != 'F') {
|
||||
E2D_ERROR("Invalid WAV file: missing RIFF header");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (data[8] != 'W' || data[9] != 'A' || data[10] != 'V' || data[11] != 'E') {
|
||||
E2D_ERROR("Invalid WAV file: missing WAVE format");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
size_t pos = 12;
|
||||
u16 audioFormat = 0;
|
||||
u16 numChannels = 0;
|
||||
u32 sampleRate = 0;
|
||||
u16 bitsPerSample = 0;
|
||||
size_t dataSize = 0;
|
||||
const u8* audioData = nullptr;
|
||||
|
||||
while (pos < size) {
|
||||
u32 chunkId = *reinterpret_cast<const u32*>(data + pos);
|
||||
u32 chunkSize = *reinterpret_cast<const u32*>(data + pos + 4);
|
||||
|
||||
if (chunkId == 0x20746D66) { // "fmt "
|
||||
audioFormat = *reinterpret_cast<const u16*>(data + pos + 8);
|
||||
numChannels = *reinterpret_cast<const u16*>(data + pos + 10);
|
||||
sampleRate = *reinterpret_cast<const u32*>(data + pos + 12);
|
||||
bitsPerSample = *reinterpret_cast<const u16*>(data + pos + 22);
|
||||
} else if (chunkId == 0x61746164) { // "data"
|
||||
dataSize = chunkSize;
|
||||
audioData = data + pos + 8;
|
||||
break;
|
||||
}
|
||||
|
||||
pos += 8 + chunkSize;
|
||||
if (chunkSize % 2 == 1) {
|
||||
pos++;
|
||||
}
|
||||
}
|
||||
|
||||
if (!audioData || dataSize == 0) {
|
||||
E2D_ERROR("Invalid WAV file: missing data chunk");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (audioFormat != 1) {
|
||||
E2D_ERROR("Unsupported WAV format: only PCM supported");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto asset = ptr::make<AudioAsset>();
|
||||
|
||||
std::vector<u8> pcmData(audioData, audioData + dataSize);
|
||||
asset->setData(AudioFormat::PCM, numChannels, sampleRate, bitsPerSample,
|
||||
std::move(pcmData));
|
||||
|
||||
return asset;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// DataLoader 实现
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
Ref<DataAsset> DataLoader::load(const std::string& path) {
|
||||
auto data = readFile(path);
|
||||
if (data.empty()) {
|
||||
E2D_ERROR("Failed to read data file: {}", path);
|
||||
return nullptr;
|
||||
}
|
||||
return loadFromMemory(data.data(), data.size());
|
||||
}
|
||||
|
||||
Ref<DataAsset> DataLoader::loadFromMemory(const u8* data, size_t size) {
|
||||
if (!data || size == 0) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto asset = ptr::make<DataAsset>();
|
||||
std::vector<u8> assetData(data, data + size);
|
||||
asset->setData(std::move(assetData));
|
||||
|
||||
return asset;
|
||||
}
|
||||
|
||||
bool DataLoader::canLoad(const std::string& path) const {
|
||||
return !path.empty();
|
||||
}
|
||||
|
||||
std::vector<std::string> DataLoader::extensions() const {
|
||||
return {".bin", ".dat"};
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// AssetLoaderFactory 实现
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
AssetType AssetLoaderFactory::getTypeByExtension(const std::string& extension) {
|
||||
std::string ext = toLower(extension);
|
||||
|
||||
if (ext == ".png" || ext == ".jpg" || ext == ".jpeg" ||
|
||||
ext == ".bmp" || ext == ".tga" || ext == ".gif" ||
|
||||
ext == ".psd" || ext == ".hdr" || ext == ".pic") {
|
||||
return AssetType::Texture;
|
||||
}
|
||||
|
||||
if (ext == ".ttf" || ext == ".otf" || ext == ".ttc") {
|
||||
return AssetType::Font;
|
||||
}
|
||||
|
||||
if (ext == ".vert" || ext == ".frag" || ext == ".glsl" ||
|
||||
ext == ".vs" || ext == ".fs") {
|
||||
return AssetType::Shader;
|
||||
}
|
||||
|
||||
if (ext == ".wav" || ext == ".mp3" || ext == ".ogg") {
|
||||
return AssetType::Audio;
|
||||
}
|
||||
|
||||
if (ext == ".bin" || ext == ".dat") {
|
||||
return AssetType::Data;
|
||||
}
|
||||
|
||||
return AssetType::Unknown;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,439 @@
|
|||
#include "extra2d/asset/asset_pack.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <filesystem>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// AssetPack 实现
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
AssetPack::AssetPack(AssetPack&& other) noexcept
|
||||
: path_(std::move(other.path_))
|
||||
, file_(std::move(other.file_))
|
||||
, header_(other.header_)
|
||||
, entries_(std::move(other.entries_))
|
||||
, pipe_(std::move(other.pipe_)) {
|
||||
other.header_ = AssetPackageHeader{};
|
||||
}
|
||||
|
||||
AssetPack& AssetPack::operator=(AssetPack&& other) noexcept {
|
||||
if (this != &other) {
|
||||
close();
|
||||
path_ = std::move(other.path_);
|
||||
file_ = std::move(other.file_);
|
||||
header_ = other.header_;
|
||||
entries_ = std::move(other.entries_);
|
||||
pipe_ = std::move(other.pipe_);
|
||||
other.header_ = AssetPackageHeader{};
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
AssetPack::~AssetPack() {
|
||||
close();
|
||||
}
|
||||
|
||||
bool AssetPack::open(const std::string& path) {
|
||||
close();
|
||||
|
||||
path_ = path;
|
||||
file_.open(path, std::ios::binary);
|
||||
|
||||
if (!file_.is_open()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!readHeader()) {
|
||||
close();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!readIndex()) {
|
||||
close();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void AssetPack::close() {
|
||||
if (file_.is_open()) {
|
||||
file_.close();
|
||||
}
|
||||
entries_.clear();
|
||||
path_.clear();
|
||||
header_ = AssetPackageHeader{};
|
||||
}
|
||||
|
||||
bool AssetPack::has(const AssetID& id) const {
|
||||
return entries_.find(id) != entries_.end();
|
||||
}
|
||||
|
||||
bool AssetPack::has(const std::string& path) const {
|
||||
return has(AssetID(path));
|
||||
}
|
||||
|
||||
std::vector<u8> AssetPack::read(const AssetID& id) {
|
||||
auto raw = readRaw(id);
|
||||
if (raw.empty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (!pipe_.empty()) {
|
||||
return pipe_.process(raw);
|
||||
}
|
||||
|
||||
return raw;
|
||||
}
|
||||
|
||||
std::vector<u8> AssetPack::read(const std::string& path) {
|
||||
return read(AssetID(path));
|
||||
}
|
||||
|
||||
std::vector<u8> AssetPack::readRaw(const AssetID& id) {
|
||||
auto* entry = getEntry(id);
|
||||
if (!entry) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return readEntryData(*entry);
|
||||
}
|
||||
|
||||
std::vector<AssetID> AssetPack::assets() const {
|
||||
std::vector<AssetID> result;
|
||||
result.reserve(entries_.size());
|
||||
for (const auto& pair : entries_) {
|
||||
result.push_back(pair.first);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
const AssetPackageEntry* AssetPack::getEntry(const AssetID& id) const {
|
||||
auto it = entries_.find(id);
|
||||
return it != entries_.end() ? &it->second : nullptr;
|
||||
}
|
||||
|
||||
bool AssetPack::readHeader() {
|
||||
file_.seekg(0, std::ios::beg);
|
||||
file_.read(reinterpret_cast<char*>(&header_), sizeof(header_));
|
||||
|
||||
if (!file_.good()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!header_.valid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AssetPack::readIndex() {
|
||||
u32 entryCount = 0;
|
||||
file_.read(reinterpret_cast<char*>(&entryCount), sizeof(entryCount));
|
||||
|
||||
if (!file_.good()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
entries_.clear();
|
||||
entries_.reserve(entryCount);
|
||||
|
||||
for (u32 i = 0; i < entryCount; ++i) {
|
||||
u32 pathLen = 0;
|
||||
file_.read(reinterpret_cast<char*>(&pathLen), sizeof(pathLen));
|
||||
|
||||
if (!file_.good()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string path(pathLen, '\0');
|
||||
file_.read(path.data(), pathLen);
|
||||
|
||||
AssetPackageEntry entry;
|
||||
entry.id = AssetID(path);
|
||||
|
||||
file_.read(reinterpret_cast<char*>(&entry.offset), sizeof(entry.offset));
|
||||
file_.read(reinterpret_cast<char*>(&entry.size), sizeof(entry.size));
|
||||
file_.read(reinterpret_cast<char*>(&entry.originalSize), sizeof(entry.originalSize));
|
||||
file_.read(reinterpret_cast<char*>(&entry.compression), sizeof(entry.compression));
|
||||
file_.read(reinterpret_cast<char*>(&entry.flags), sizeof(entry.flags));
|
||||
|
||||
if (!file_.good()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
entries_[entry.id] = entry;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<u8> AssetPack::readEntryData(const AssetPackageEntry& entry) {
|
||||
std::vector<u8> data(entry.size);
|
||||
|
||||
file_.seekg(entry.offset, std::ios::beg);
|
||||
file_.read(reinterpret_cast<char*>(data.data()), entry.size);
|
||||
|
||||
if (!file_.good()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// PackManager 实现
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
bool PackManager::mount(const std::string& path) {
|
||||
auto pack = std::make_unique<AssetPack>();
|
||||
if (!pack->open(path)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
(void)defaultPipe_;
|
||||
|
||||
|
||||
packs_.push_back(std::move(pack));
|
||||
return true;
|
||||
}
|
||||
|
||||
void PackManager::unmount(const std::string& path) {
|
||||
packs_.erase(
|
||||
std::remove_if(packs_.begin(), packs_.end(),
|
||||
[&path](const Unique<AssetPack>& pack) {
|
||||
return pack->path() == path;
|
||||
}),
|
||||
packs_.end()
|
||||
);
|
||||
}
|
||||
|
||||
void PackManager::unmountAll() {
|
||||
packs_.clear();
|
||||
}
|
||||
|
||||
AssetPack* PackManager::find(const AssetID& id) {
|
||||
for (auto& pack : packs_) {
|
||||
if (pack->has(id)) {
|
||||
return pack.get();
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
AssetPack* PackManager::find(const std::string& path) {
|
||||
return find(AssetID(path));
|
||||
}
|
||||
|
||||
bool PackManager::has(const AssetID& id) const {
|
||||
for (const auto& pack : packs_) {
|
||||
if (pack->has(id)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PackManager::has(const std::string& path) const {
|
||||
return has(AssetID(path));
|
||||
}
|
||||
|
||||
std::vector<u8> PackManager::read(const AssetID& id) {
|
||||
auto* pack = find(id);
|
||||
if (pack) {
|
||||
return pack->read(id);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<u8> PackManager::read(const std::string& path) {
|
||||
return read(AssetID(path));
|
||||
}
|
||||
|
||||
std::vector<AssetID> PackManager::allAssets() const {
|
||||
std::vector<AssetID> result;
|
||||
for (const auto& pack : packs_) {
|
||||
auto assets = pack->assets();
|
||||
result.insert(result.end(), assets.begin(), assets.end());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<std::string> PackManager::mountedPacks() const {
|
||||
std::vector<std::string> result;
|
||||
result.reserve(packs_.size());
|
||||
for (const auto& pack : packs_) {
|
||||
result.push_back(pack->path());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// AssetPackBuilder 实现
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
AssetPackBuilder::AssetPackBuilder(Compression compression, int level)
|
||||
: compression_(compression)
|
||||
, level_(level) {
|
||||
}
|
||||
|
||||
void AssetPackBuilder::add(const std::string& path, const std::vector<u8>& data) {
|
||||
BuilderEntry entry;
|
||||
entry.id = AssetID(path);
|
||||
entry.data = data;
|
||||
entry.compression = static_cast<u32>(compression_);
|
||||
entry.flags = encryptType_ != Decryptor::Type::None ? 1 : 0;
|
||||
|
||||
entry.compressedData = processData(entry.data);
|
||||
if (entry.compressedData.empty()) {
|
||||
entry.compressedData = entry.data;
|
||||
entry.compression = static_cast<u32>(Compression::None);
|
||||
}
|
||||
|
||||
entries_.push_back(std::move(entry));
|
||||
|
||||
totalOriginalSize_ += data.size();
|
||||
totalCompressedSize_ += entry.compressedData.size();
|
||||
}
|
||||
|
||||
void AssetPackBuilder::add(const std::string& path, std::vector<u8>&& data) {
|
||||
add(path, data);
|
||||
}
|
||||
|
||||
bool AssetPackBuilder::addFile(const std::string& filePath, const std::string& packPath) {
|
||||
std::ifstream file(filePath, std::ios::binary | std::ios::ate);
|
||||
if (!file.is_open()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto size = file.tellg();
|
||||
file.seekg(0, std::ios::beg);
|
||||
|
||||
std::vector<u8> data(size);
|
||||
file.read(reinterpret_cast<char*>(data.data()), size);
|
||||
|
||||
if (!file.good()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string ppath = packPath.empty()
|
||||
? std::filesystem::path(filePath).filename().string()
|
||||
: packPath;
|
||||
|
||||
add(ppath, std::move(data));
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t AssetPackBuilder::addDirectory(const std::string& dirPath, const std::string& prefix) {
|
||||
size_t count = 0;
|
||||
std::filesystem::path dir(dirPath);
|
||||
|
||||
if (!std::filesystem::exists(dir) || !std::filesystem::is_directory(dir)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
for (const auto& entry : std::filesystem::recursive_directory_iterator(dir)) {
|
||||
if (entry.is_regular_file()) {
|
||||
std::string relativePath = std::filesystem::relative(entry.path(), dir).string();
|
||||
std::string packPath = prefix.empty()
|
||||
? relativePath
|
||||
: prefix + "/" + relativePath;
|
||||
|
||||
if (addFile(entry.path().string(), packPath)) {
|
||||
++count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
void AssetPackBuilder::setEncryption(const std::string& key, Decryptor::Type type) {
|
||||
encryptKey_ = key;
|
||||
encryptType_ = type;
|
||||
}
|
||||
|
||||
bool AssetPackBuilder::build(const std::string& outputPath) {
|
||||
std::ofstream out(outputPath, std::ios::binary);
|
||||
if (!out.is_open()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
AssetPackageHeader header;
|
||||
header.magic = AssetPackageHeader::MAGIC;
|
||||
header.version = 1;
|
||||
header.compressionType = static_cast<u32>(compression_);
|
||||
header.encryptionType = static_cast<u32>(encryptType_);
|
||||
header.originalSize = totalOriginalSize_;
|
||||
|
||||
u64 dataOffset = sizeof(header) + sizeof(u32);
|
||||
for (const auto& entry : entries_) {
|
||||
dataOffset += sizeof(u32) + entry.id.path.size();
|
||||
dataOffset += sizeof(u64) * 3 + sizeof(u32) * 2;
|
||||
}
|
||||
|
||||
for (auto& entry : entries_) {
|
||||
entry.offset = dataOffset;
|
||||
dataOffset += entry.compressedData.size();
|
||||
}
|
||||
|
||||
header.compressedSize = dataOffset - sizeof(header) - sizeof(u32);
|
||||
for (const auto& entry : entries_) {
|
||||
header.compressedSize -= sizeof(u32) + entry.id.path.size();
|
||||
header.compressedSize -= sizeof(u64) * 3 + sizeof(u32) * 2;
|
||||
}
|
||||
|
||||
out.write(reinterpret_cast<const char*>(&header), sizeof(header));
|
||||
|
||||
u32 entryCount = static_cast<u32>(entries_.size());
|
||||
out.write(reinterpret_cast<const char*>(&entryCount), sizeof(entryCount));
|
||||
|
||||
for (const auto& entry : entries_) {
|
||||
u32 pathLen = static_cast<u32>(entry.id.path.size());
|
||||
out.write(reinterpret_cast<const char*>(&pathLen), sizeof(pathLen));
|
||||
out.write(entry.id.path.data(), pathLen);
|
||||
|
||||
u64 offset = entry.offset;
|
||||
u64 size = entry.compressedData.size();
|
||||
u64 originalSize = entry.data.size();
|
||||
|
||||
out.write(reinterpret_cast<const char*>(&offset), sizeof(offset));
|
||||
out.write(reinterpret_cast<const char*>(&size), sizeof(size));
|
||||
out.write(reinterpret_cast<const char*>(&originalSize), sizeof(originalSize));
|
||||
out.write(reinterpret_cast<const char*>(&entry.compression), sizeof(entry.compression));
|
||||
out.write(reinterpret_cast<const char*>(&entry.flags), sizeof(entry.flags));
|
||||
}
|
||||
|
||||
for (const auto& entry : entries_) {
|
||||
out.write(reinterpret_cast<const char*>(entry.compressedData.data()),
|
||||
entry.compressedData.size());
|
||||
}
|
||||
|
||||
return out.good();
|
||||
}
|
||||
|
||||
void AssetPackBuilder::clear() {
|
||||
entries_.clear();
|
||||
totalOriginalSize_ = 0;
|
||||
totalCompressedSize_ = 0;
|
||||
}
|
||||
|
||||
std::vector<u8> AssetPackBuilder::processData(const std::vector<u8>& data) {
|
||||
DataPipe pipe;
|
||||
|
||||
if (compression_ != Compression::None) {
|
||||
pipe.compress(compression_, level_);
|
||||
}
|
||||
|
||||
if (encryptType_ != Decryptor::Type::None && !encryptKey_.empty()) {
|
||||
pipe.encrypt(encryptKey_, encryptType_);
|
||||
}
|
||||
|
||||
return pipe.process(data);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,449 @@
|
|||
#include "extra2d/asset/data_processor.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
|
||||
#ifdef E2D_USE_ZSTD
|
||||
#include <zstd.h>
|
||||
#endif
|
||||
|
||||
#ifdef E2D_USE_LZ4
|
||||
#include <lz4.h>
|
||||
#endif
|
||||
|
||||
#ifdef E2D_USE_ZLIB
|
||||
#include <zlib.h>
|
||||
#endif
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Decryptor 实现
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
Decryptor::Decryptor(const std::string& key, Type type)
|
||||
: key_(key), type_(type) {
|
||||
}
|
||||
|
||||
std::vector<u8> Decryptor::process(const std::vector<u8>& input) {
|
||||
if (input.empty() || type_ == Type::None) {
|
||||
return processNext(input);
|
||||
}
|
||||
|
||||
std::vector<u8> result;
|
||||
switch (type_) {
|
||||
case Type::XOR:
|
||||
result = decryptXOR(input);
|
||||
break;
|
||||
case Type::AES256:
|
||||
result = decryptAES256(input);
|
||||
break;
|
||||
default:
|
||||
result = input;
|
||||
break;
|
||||
}
|
||||
|
||||
return processNext(result);
|
||||
}
|
||||
|
||||
std::vector<u8> Decryptor::decryptXOR(const std::vector<u8>& input) {
|
||||
if (key_.empty()) {
|
||||
return input;
|
||||
}
|
||||
|
||||
std::vector<u8> result(input.size());
|
||||
const size_t keyLen = key_.size();
|
||||
|
||||
for (size_t i = 0; i < input.size(); ++i) {
|
||||
result[i] = input[i] ^ static_cast<u8>(key_[i % keyLen]);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<u8> Decryptor::decryptAES256(const std::vector<u8>& input) {
|
||||
return decryptXOR(input);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Decompressor 实现
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
Decompressor::Decompressor(Compression algo)
|
||||
: algo_(algo) {
|
||||
}
|
||||
|
||||
std::vector<u8> Decompressor::process(const std::vector<u8>& input) {
|
||||
if (input.empty() || algo_ == Compression::None) {
|
||||
return processNext(input);
|
||||
}
|
||||
|
||||
std::vector<u8> result;
|
||||
switch (algo_) {
|
||||
case Compression::Zstd:
|
||||
result = decompressZstd(input);
|
||||
break;
|
||||
case Compression::LZ4:
|
||||
result = decompressLZ4(input);
|
||||
break;
|
||||
case Compression::Zlib:
|
||||
result = decompressZlib(input);
|
||||
break;
|
||||
default:
|
||||
result = input;
|
||||
break;
|
||||
}
|
||||
|
||||
return processNext(result);
|
||||
}
|
||||
|
||||
std::vector<u8> Decompressor::decompressZstd(const std::vector<u8>& input) {
|
||||
#ifdef E2D_USE_ZSTD
|
||||
unsigned long long const decompressedSize = ZSTD_getFrameContentSize(input.data(), input.size());
|
||||
|
||||
if (decompressedSize == ZSTD_CONTENTSIZE_ERROR ||
|
||||
decompressedSize == ZSTD_CONTENTSIZE_UNKNOWN) {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<u8> result(static_cast<size_t>(decompressedSize));
|
||||
|
||||
size_t const actualSize = ZSTD_decompress(
|
||||
result.data(), result.size(),
|
||||
input.data(), input.size()
|
||||
);
|
||||
|
||||
if (ZSTD_isError(actualSize)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
result.resize(actualSize);
|
||||
return result;
|
||||
#else
|
||||
return input;
|
||||
#endif
|
||||
}
|
||||
|
||||
std::vector<u8> Decompressor::decompressLZ4(const std::vector<u8>& input) {
|
||||
#ifdef E2D_USE_LZ4
|
||||
if (input.size() < sizeof(u32)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
u32 originalSize;
|
||||
std::memcpy(&originalSize, input.data(), sizeof(u32));
|
||||
|
||||
std::vector<u8> result(originalSize);
|
||||
|
||||
int const decompressedSize = LZ4_decompress_safe(
|
||||
reinterpret_cast<const char*>(input.data() + sizeof(u32)),
|
||||
reinterpret_cast<char*>(result.data()),
|
||||
static_cast<int>(input.size() - sizeof(u32)),
|
||||
static_cast<int>(originalSize)
|
||||
);
|
||||
|
||||
if (decompressedSize < 0) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return result;
|
||||
#else
|
||||
return input;
|
||||
#endif
|
||||
}
|
||||
|
||||
std::vector<u8> Decompressor::decompressZlib(const std::vector<u8>& input) {
|
||||
#ifdef E2D_USE_ZLIB
|
||||
std::vector<u8> result;
|
||||
result.reserve(input.size() * 4);
|
||||
|
||||
const size_t CHUNK = 16384;
|
||||
u8 out[CHUNK];
|
||||
|
||||
z_stream strm;
|
||||
strm.zalloc = Z_NULL;
|
||||
strm.zfree = Z_NULL;
|
||||
strm.opaque = Z_NULL;
|
||||
strm.avail_in = static_cast<uInt>(input.size());
|
||||
strm.next_in = const_cast<Bytef*>(input.data());
|
||||
|
||||
if (inflateInit(&strm) != Z_OK) {
|
||||
return {};
|
||||
}
|
||||
|
||||
int ret;
|
||||
do {
|
||||
strm.avail_out = CHUNK;
|
||||
strm.next_out = out;
|
||||
|
||||
ret = inflate(&strm, Z_NO_FLUSH);
|
||||
if (ret == Z_STREAM_ERROR || ret == Z_DATA_ERROR || ret == Z_MEM_ERROR) {
|
||||
inflateEnd(&strm);
|
||||
return {};
|
||||
}
|
||||
|
||||
result.insert(result.end(), out, out + CHUNK - strm.avail_out);
|
||||
} while (strm.avail_out == 0);
|
||||
|
||||
inflateEnd(&strm);
|
||||
return result;
|
||||
#else
|
||||
return input;
|
||||
#endif
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Encryptor 实现
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
Encryptor::Encryptor(const std::string& key, Decryptor::Type type)
|
||||
: key_(key), type_(type) {
|
||||
}
|
||||
|
||||
std::vector<u8> Encryptor::process(const std::vector<u8>& input) {
|
||||
if (input.empty() || type_ == Decryptor::Type::None) {
|
||||
return processNext(input);
|
||||
}
|
||||
|
||||
std::vector<u8> result;
|
||||
switch (type_) {
|
||||
case Decryptor::Type::XOR:
|
||||
result = encryptXOR(input);
|
||||
break;
|
||||
case Decryptor::Type::AES256:
|
||||
result = encryptAES256(input);
|
||||
break;
|
||||
default:
|
||||
result = input;
|
||||
break;
|
||||
}
|
||||
|
||||
return processNext(result);
|
||||
}
|
||||
|
||||
std::vector<u8> Encryptor::encryptXOR(const std::vector<u8>& input) {
|
||||
if (key_.empty()) {
|
||||
return input;
|
||||
}
|
||||
|
||||
std::vector<u8> result(input.size());
|
||||
const size_t keyLen = key_.size();
|
||||
|
||||
for (size_t i = 0; i < input.size(); ++i) {
|
||||
result[i] = input[i] ^ static_cast<u8>(key_[i % keyLen]);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<u8> Encryptor::encryptAES256(const std::vector<u8>& input) {
|
||||
return encryptXOR(input);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Compressor 实现
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
Compressor::Compressor(Compression algo, int level)
|
||||
: algo_(algo), level_(level) {
|
||||
}
|
||||
|
||||
std::vector<u8> Compressor::process(const std::vector<u8>& input) {
|
||||
if (input.empty() || algo_ == Compression::None) {
|
||||
return processNext(input);
|
||||
}
|
||||
|
||||
std::vector<u8> result;
|
||||
switch (algo_) {
|
||||
case Compression::Zstd:
|
||||
result = compressZstd(input);
|
||||
break;
|
||||
case Compression::LZ4:
|
||||
result = compressLZ4(input);
|
||||
break;
|
||||
case Compression::Zlib:
|
||||
result = compressZlib(input);
|
||||
break;
|
||||
default:
|
||||
result = input;
|
||||
break;
|
||||
}
|
||||
|
||||
return processNext(result);
|
||||
}
|
||||
|
||||
std::vector<u8> Compressor::compressZstd(const std::vector<u8>& input) {
|
||||
#ifdef E2D_USE_ZSTD
|
||||
size_t const bound = ZSTD_compressBound(input.size());
|
||||
std::vector<u8> result(bound);
|
||||
|
||||
size_t const compressedSize = ZSTD_compress(
|
||||
result.data(), result.size(),
|
||||
input.data(), input.size(),
|
||||
level_
|
||||
);
|
||||
|
||||
if (ZSTD_isError(compressedSize)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
result.resize(compressedSize);
|
||||
return result;
|
||||
#else
|
||||
return input;
|
||||
#endif
|
||||
}
|
||||
|
||||
std::vector<u8> Compressor::compressLZ4(const std::vector<u8>& input) {
|
||||
#ifdef E2D_USE_LZ4
|
||||
int const bound = LZ4_compressBound(static_cast<int>(input.size()));
|
||||
std::vector<u8> result(sizeof(u32) + bound);
|
||||
|
||||
u32 originalSize = static_cast<u32>(input.size());
|
||||
std::memcpy(result.data(), &originalSize, sizeof(u32));
|
||||
|
||||
int const compressedSize = LZ4_compress_default(
|
||||
reinterpret_cast<const char*>(input.data()),
|
||||
reinterpret_cast<char*>(result.data() + sizeof(u32)),
|
||||
static_cast<int>(input.size()),
|
||||
bound
|
||||
);
|
||||
|
||||
if (compressedSize <= 0) {
|
||||
return {};
|
||||
}
|
||||
|
||||
result.resize(sizeof(u32) + compressedSize);
|
||||
return result;
|
||||
#else
|
||||
return input;
|
||||
#endif
|
||||
}
|
||||
|
||||
std::vector<u8> Compressor::compressZlib(const std::vector<u8>& input) {
|
||||
#ifdef E2D_USE_ZLIB
|
||||
std::vector<u8> result;
|
||||
result.reserve(input.size() / 2);
|
||||
|
||||
const size_t CHUNK = 16384;
|
||||
u8 out[CHUNK];
|
||||
|
||||
z_stream strm;
|
||||
strm.zalloc = Z_NULL;
|
||||
strm.zfree = Z_NULL;
|
||||
strm.opaque = Z_NULL;
|
||||
|
||||
if (deflateInit(&strm, level_) != Z_OK) {
|
||||
return {};
|
||||
}
|
||||
|
||||
strm.avail_in = static_cast<uInt>(input.size());
|
||||
strm.next_in = const_cast<Bytef*>(input.data());
|
||||
|
||||
int ret;
|
||||
do {
|
||||
strm.avail_out = CHUNK;
|
||||
strm.next_out = out;
|
||||
|
||||
ret = deflate(&strm, Z_FINISH);
|
||||
if (ret == Z_STREAM_ERROR) {
|
||||
deflateEnd(&strm);
|
||||
return {};
|
||||
}
|
||||
|
||||
result.insert(result.end(), out, out + CHUNK - strm.avail_out);
|
||||
} while (strm.avail_out == 0);
|
||||
|
||||
deflateEnd(&strm);
|
||||
return result;
|
||||
#else
|
||||
return input;
|
||||
#endif
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// DataPipe 实现
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
DataPipe& DataPipe::decrypt(const std::string& key, Decryptor::Type type) {
|
||||
processors_.push_back(std::make_unique<Decryptor>(key, type));
|
||||
return *this;
|
||||
}
|
||||
|
||||
DataPipe& DataPipe::decompress(Compression algo) {
|
||||
processors_.push_back(std::make_unique<Decompressor>(algo));
|
||||
return *this;
|
||||
}
|
||||
|
||||
DataPipe& DataPipe::encrypt(const std::string& key, Decryptor::Type type) {
|
||||
processors_.push_back(std::make_unique<Encryptor>(key, type));
|
||||
return *this;
|
||||
}
|
||||
|
||||
DataPipe& DataPipe::compress(Compression algo, int level) {
|
||||
processors_.push_back(std::make_unique<Compressor>(algo, level));
|
||||
return *this;
|
||||
}
|
||||
|
||||
DataPipe& DataPipe::add(Unique<DataProcessor> processor) {
|
||||
processors_.push_back(std::move(processor));
|
||||
return *this;
|
||||
}
|
||||
|
||||
std::vector<u8> DataPipe::process(const std::vector<u8>& input) {
|
||||
if (processors_.empty()) {
|
||||
return input;
|
||||
}
|
||||
|
||||
std::vector<u8> result = input;
|
||||
for (auto& processor : processors_) {
|
||||
result = processor->process(result);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void DataPipe::clear() {
|
||||
processors_.clear();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 工具函数实现
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
std::vector<u8> computeChecksum(const std::vector<u8>& data) {
|
||||
std::vector<u8> result(32, 0);
|
||||
|
||||
u64 hash1 = 14695981039346656037ULL;
|
||||
u64 hash2 = 14695981039346656037ULL;
|
||||
|
||||
for (size_t i = 0; i < data.size(); ++i) {
|
||||
hash1 ^= static_cast<u64>(data[i]);
|
||||
hash1 *= 1099511628211ULL;
|
||||
|
||||
hash2 ^= static_cast<u64>(data[i]) << ((i % 8) * 8);
|
||||
hash2 *= 1099511628211ULL;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < 8; ++i) {
|
||||
result[i] = static_cast<u8>((hash1 >> (i * 8)) & 0xFF);
|
||||
result[i + 8] = static_cast<u8>((hash2 >> (i * 8)) & 0xFF);
|
||||
}
|
||||
|
||||
for (size_t i = 16; i < 32; ++i) {
|
||||
result[i] = static_cast<u8>((hash1 ^ hash2) >> ((i - 16) * 8) & 0xFF);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool verifyChecksum(const std::vector<u8>& data, const std::vector<u8>& checksum) {
|
||||
if (checksum.size() != 32) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto computed = computeChecksum(data);
|
||||
return std::equal(computed.begin(), computed.end(), checksum.begin());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,223 @@
|
|||
#include <extra2d/core/registry.h>
|
||||
#include <extra2d/services/logger_service.h>
|
||||
#include <future>
|
||||
#include <queue>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
Registry &Registry::instance() {
|
||||
static Registry instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
bool Registry::init() {
|
||||
auto levels = group();
|
||||
E2D_REGISTRY("正在初始化 {} 个模块,共 {} 个层级...", moduleCount_,
|
||||
levels.size());
|
||||
|
||||
for (size_t level = 0; level < levels.size(); ++level) {
|
||||
auto &modules = levels[level];
|
||||
|
||||
// 检查当前层级是否有支持并行初始化的模块
|
||||
bool hasParallelModules = false;
|
||||
for (auto *module : modules) {
|
||||
if (module->parallel()) {
|
||||
hasParallelModules = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果只有一个模块或不支持并行,使用串行初始化
|
||||
if (modules.size() <= 1 || !hasParallelModules) {
|
||||
for (auto *module : modules) {
|
||||
E2D_REGISTRY("正在初始化模块: {} (层级 {})", module->name(), level);
|
||||
|
||||
if (!module->init()) {
|
||||
E2D_ERROR("初始化模块失败: {}", module->name());
|
||||
return false;
|
||||
}
|
||||
|
||||
E2D_REGISTRY("模块 {} 初始化成功", module->name());
|
||||
}
|
||||
} else {
|
||||
// 并行初始化当前层级的模块
|
||||
E2D_REGISTRY("正在并行初始化 {} 个模块 (层级 {})...", modules.size(),
|
||||
level);
|
||||
|
||||
std::vector<std::future<std::pair<Module *, bool>>> futures;
|
||||
std::vector<Module *> serialModules;
|
||||
|
||||
// 分离支持并行和不支持并行的模块
|
||||
for (auto *module : modules) {
|
||||
if (module->parallel()) {
|
||||
futures.push_back(std::async(std::launch::async, [module]() {
|
||||
return std::make_pair(module, module->init());
|
||||
}));
|
||||
} else {
|
||||
serialModules.push_back(module);
|
||||
}
|
||||
}
|
||||
|
||||
// 等待并行模块完成
|
||||
for (auto &future : futures) {
|
||||
auto [module, success] = future.get();
|
||||
if (!success) {
|
||||
E2D_ERROR("初始化模块失败: {}", module->name());
|
||||
return false;
|
||||
}
|
||||
E2D_REGISTRY("模块 {} 初始化成功 (并行)", module->name());
|
||||
}
|
||||
|
||||
// 串行初始化不支持并行的模块
|
||||
for (auto *module : serialModules) {
|
||||
E2D_REGISTRY("正在初始化模块: {} (串行, 层级 {})", module->name(),
|
||||
level);
|
||||
if (!module->init()) {
|
||||
E2D_ERROR("初始化模块失败: {}", module->name());
|
||||
return false;
|
||||
}
|
||||
E2D_REGISTRY("模块 {} 初始化成功", module->name());
|
||||
}
|
||||
}
|
||||
|
||||
E2D_REGISTRY("层级 {} 初始化完成", level);
|
||||
}
|
||||
|
||||
E2D_REGISTRY("所有模块初始化完成");
|
||||
return true;
|
||||
}
|
||||
|
||||
void Registry::shutdown() {
|
||||
// 从后向前关闭模块
|
||||
for (size_t i = moduleCount_; i > 0; --i) {
|
||||
if (modules_[i - 1].valid && modules_[i - 1].module) {
|
||||
modules_[i - 1].module->shutdown();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Registry::clear() {
|
||||
shutdown();
|
||||
|
||||
// 销毁所有模块
|
||||
for (size_t i = 0; i < moduleCount_; ++i) {
|
||||
modules_[i].module.reset();
|
||||
modules_[i].valid = false;
|
||||
}
|
||||
moduleCount_ = 0;
|
||||
}
|
||||
|
||||
std::vector<Module *> Registry::sort() {
|
||||
std::vector<Module *> result;
|
||||
std::vector<int> inDegree(moduleCount_, 0);
|
||||
std::vector<std::vector<size_t>> adj(moduleCount_);
|
||||
|
||||
// 构建依赖图
|
||||
for (size_t i = 0; i < moduleCount_; ++i) {
|
||||
if (!modules_[i].valid)
|
||||
continue;
|
||||
|
||||
auto deps = modules_[i].module->deps();
|
||||
for (auto &depType : deps) {
|
||||
// 查找依赖模块的索引
|
||||
for (size_t j = 0; j < moduleCount_; ++j) {
|
||||
if (modules_[j].valid &&
|
||||
std::type_index(typeid(*modules_[j].module)) == depType) {
|
||||
adj[j].push_back(i);
|
||||
inDegree[i]++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 使用优先队列,按优先级排序
|
||||
auto cmp = [this](size_t a, size_t b) {
|
||||
return modules_[a].module->priority() > modules_[b].module->priority();
|
||||
};
|
||||
std::priority_queue<size_t, std::vector<size_t>, decltype(cmp)> pq(cmp);
|
||||
|
||||
for (size_t i = 0; i < moduleCount_; ++i) {
|
||||
if (inDegree[i] == 0) {
|
||||
pq.push(i);
|
||||
}
|
||||
}
|
||||
|
||||
while (!pq.empty()) {
|
||||
size_t curr = pq.top();
|
||||
pq.pop();
|
||||
result.push_back(modules_[curr].module.get());
|
||||
|
||||
for (size_t next : adj[curr]) {
|
||||
inDegree[next]--;
|
||||
if (inDegree[next] == 0) {
|
||||
pq.push(next);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<std::vector<Module *>> Registry::group() {
|
||||
std::vector<std::vector<Module *>> levels;
|
||||
std::vector<int> inDegree(moduleCount_, 0);
|
||||
std::vector<std::vector<size_t>> adj(moduleCount_);
|
||||
|
||||
// 构建依赖图
|
||||
for (size_t i = 0; i < moduleCount_; ++i) {
|
||||
if (!modules_[i].valid)
|
||||
continue;
|
||||
|
||||
auto deps = modules_[i].module->deps();
|
||||
for (auto &depType : deps) {
|
||||
for (size_t j = 0; j < moduleCount_; ++j) {
|
||||
if (modules_[j].valid &&
|
||||
std::type_index(typeid(*modules_[j].module)) == depType) {
|
||||
adj[j].push_back(i);
|
||||
inDegree[i]++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 使用 BFS 按层级分组
|
||||
std::queue<size_t> q;
|
||||
std::vector<int> levelMap(moduleCount_, -1);
|
||||
|
||||
// 找到所有入度为 0 的模块(第一层)
|
||||
for (size_t i = 0; i < moduleCount_; ++i) {
|
||||
if (inDegree[i] == 0) {
|
||||
q.push(i);
|
||||
levelMap[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// BFS 遍历
|
||||
while (!q.empty()) {
|
||||
size_t curr = q.front();
|
||||
q.pop();
|
||||
|
||||
int currLevel = levelMap[curr];
|
||||
|
||||
// 确保当前层级存在
|
||||
if (levels.size() <= static_cast<size_t>(currLevel)) {
|
||||
levels.resize(currLevel + 1);
|
||||
}
|
||||
levels[currLevel].push_back(modules_[curr].module.get());
|
||||
|
||||
// 处理依赖当前模块的其他模块
|
||||
for (size_t next : adj[curr]) {
|
||||
inDegree[next]--;
|
||||
if (inDegree[next] == 0) {
|
||||
q.push(next);
|
||||
levelMap[next] = currLevel + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return levels;
|
||||
}
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -0,0 +1,110 @@
|
|||
#include <extra2d/core/service_locator.h>
|
||||
#include <algorithm>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
ServiceLocator& ServiceLocator::instance() {
|
||||
static ServiceLocator instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
bool ServiceLocator::init() {
|
||||
std::shared_lock<std::shared_mutex> lock(mutex_);
|
||||
|
||||
for (auto& svc : orderedServices_) {
|
||||
if (!svc) continue;
|
||||
|
||||
auto info = svc->info();
|
||||
if (!info.enabled) continue;
|
||||
|
||||
if (!svc->initialized()) {
|
||||
svc->setState(ServiceState::Initializing);
|
||||
if (!svc->init()) {
|
||||
svc->setState(ServiceState::Stopped);
|
||||
return false;
|
||||
}
|
||||
svc->setState(ServiceState::Running);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ServiceLocator::shutdown() {
|
||||
std::shared_lock<std::shared_mutex> lock(mutex_);
|
||||
|
||||
for (auto it = orderedServices_.rbegin();
|
||||
it != orderedServices_.rend(); ++it) {
|
||||
if (*it && (*it)->initialized()) {
|
||||
(*it)->setState(ServiceState::Stopping);
|
||||
(*it)->shutdown();
|
||||
(*it)->setState(ServiceState::Stopped);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ServiceLocator::update(f32 dt) {
|
||||
std::shared_lock<std::shared_mutex> lock(mutex_);
|
||||
|
||||
for (auto& svc : orderedServices_) {
|
||||
if (svc && svc->initialized()) {
|
||||
auto state = svc->state();
|
||||
if (state == ServiceState::Running) {
|
||||
svc->update(dt);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ServiceLocator::pause() {
|
||||
std::shared_lock<std::shared_mutex> lock(mutex_);
|
||||
|
||||
for (auto& svc : orderedServices_) {
|
||||
if (svc && svc->initialized()) {
|
||||
svc->pause();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ServiceLocator::resume() {
|
||||
std::shared_lock<std::shared_mutex> lock(mutex_);
|
||||
|
||||
for (auto& svc : orderedServices_) {
|
||||
if (svc && svc->initialized()) {
|
||||
svc->resume();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<Ref<IService>> ServiceLocator::all() const {
|
||||
std::shared_lock<std::shared_mutex> lock(mutex_);
|
||||
return orderedServices_;
|
||||
}
|
||||
|
||||
void ServiceLocator::clear() {
|
||||
std::unique_lock<std::shared_mutex> lock(mutex_);
|
||||
|
||||
for (auto it = orderedServices_.rbegin();
|
||||
it != orderedServices_.rend(); ++it) {
|
||||
if (*it && (*it)->initialized()) {
|
||||
(*it)->setState(ServiceState::Stopping);
|
||||
(*it)->shutdown();
|
||||
(*it)->setState(ServiceState::Stopped);
|
||||
}
|
||||
}
|
||||
|
||||
services_.clear();
|
||||
factories_.clear();
|
||||
orderedServices_.clear();
|
||||
}
|
||||
|
||||
void ServiceLocator::sort() {
|
||||
std::stable_sort(orderedServices_.begin(), orderedServices_.end(),
|
||||
[](const Ref<IService>& a, const Ref<IService>& b) {
|
||||
if (!a || !b) return false;
|
||||
return static_cast<int>(a->info().priority) <
|
||||
static_cast<int>(b->info().priority);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
#include <extra2d/core/service_registry.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
ServiceRegistry& ServiceRegistry::instance() {
|
||||
static ServiceRegistry instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void ServiceRegistry::setEnabled(const std::string& name, bool enabled) {
|
||||
for (auto& reg : registrations_) {
|
||||
if (reg.name == name) {
|
||||
reg.enabled = enabled;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ServiceRegistry::createAll() {
|
||||
std::sort(registrations_.begin(), registrations_.end(),
|
||||
[](const ServiceRegistration& a, const ServiceRegistration& b) {
|
||||
return static_cast<int>(a.priority) < static_cast<int>(b.priority);
|
||||
});
|
||||
|
||||
for (const auto& reg : registrations_) {
|
||||
if (!reg.enabled) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto service = reg.factory();
|
||||
if (service) {
|
||||
ServiceLocator::instance().add<IService>(service);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,48 +0,0 @@
|
|||
#include <extra2d/core/string.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
std::string utf8ToGbkImpl(const std::string& utf8) {
|
||||
if (utf8.empty()) return std::string();
|
||||
|
||||
// UTF-8 → Wide → GBK
|
||||
int wideLen = MultiByteToWideChar(CP_UTF8, 0, utf8.c_str(), -1, nullptr, 0);
|
||||
if (wideLen <= 0) return std::string();
|
||||
|
||||
std::wstring wide(wideLen - 1, 0);
|
||||
MultiByteToWideChar(CP_UTF8, 0, utf8.c_str(), -1, &wide[0], wideLen);
|
||||
|
||||
int gbkLen = WideCharToMultiByte(CP_ACP, 0, wide.c_str(), -1, nullptr, 0, nullptr, nullptr);
|
||||
if (gbkLen <= 0) return std::string();
|
||||
|
||||
std::string gbk(gbkLen - 1, 0);
|
||||
WideCharToMultiByte(CP_ACP, 0, wide.c_str(), -1, &gbk[0], gbkLen, nullptr, nullptr);
|
||||
|
||||
return gbk;
|
||||
}
|
||||
|
||||
std::string gbkToUtf8Impl(const std::string& gbk) {
|
||||
if (gbk.empty()) return std::string();
|
||||
|
||||
// GBK → Wide → UTF-8
|
||||
int wideLen = MultiByteToWideChar(CP_ACP, 0, gbk.c_str(), -1, nullptr, 0);
|
||||
if (wideLen <= 0) return std::string();
|
||||
|
||||
std::wstring wide(wideLen - 1, 0);
|
||||
MultiByteToWideChar(CP_ACP, 0, gbk.c_str(), -1, &wide[0], wideLen);
|
||||
|
||||
int utf8Len = WideCharToMultiByte(CP_UTF8, 0, wide.c_str(), -1, nullptr, 0, nullptr, nullptr);
|
||||
if (utf8Len <= 0) return std::string();
|
||||
|
||||
std::string utf8(utf8Len - 1, 0);
|
||||
WideCharToMultiByte(CP_UTF8, 0, wide.c_str(), -1, &utf8[0], utf8Len, nullptr, nullptr);
|
||||
|
||||
return utf8;
|
||||
}
|
||||
|
||||
} // namespace extra2d
|
||||
|
||||
#endif // _WIN32
|
||||
|
|
@ -2,55 +2,55 @@
|
|||
|
||||
namespace extra2d {
|
||||
|
||||
Event Event::createWindowResize(int width, int height) {
|
||||
Event Event::windowResize(i32 w, i32 h) {
|
||||
Event event;
|
||||
event.type = EventType::WindowResize;
|
||||
event.data = WindowResizeEvent{width, height};
|
||||
event.data = WindowResizeEvent{w, h};
|
||||
return event;
|
||||
}
|
||||
|
||||
Event Event::createWindowClose() {
|
||||
Event Event::windowClose() {
|
||||
Event event;
|
||||
event.type = EventType::WindowClose;
|
||||
return event;
|
||||
}
|
||||
|
||||
Event Event::createKeyPress(int keyCode, int scancode, int mods) {
|
||||
Event Event::keyPress(i32 key, i32 scancode, i32 mods) {
|
||||
Event event;
|
||||
event.type = EventType::KeyPressed;
|
||||
event.data = KeyEvent{keyCode, scancode, mods};
|
||||
event.data = KeyEvent{key, scancode, mods};
|
||||
return event;
|
||||
}
|
||||
|
||||
Event Event::createKeyRelease(int keyCode, int scancode, int mods) {
|
||||
Event Event::keyRelease(i32 key, i32 scancode, i32 mods) {
|
||||
Event event;
|
||||
event.type = EventType::KeyReleased;
|
||||
event.data = KeyEvent{keyCode, scancode, mods};
|
||||
event.data = KeyEvent{key, scancode, mods};
|
||||
return event;
|
||||
}
|
||||
|
||||
Event Event::createMouseButtonPress(int button, int mods, const Vec2 &pos) {
|
||||
Event Event::mousePress(i32 btn, i32 mods, Vec2 pos) {
|
||||
Event event;
|
||||
event.type = EventType::MouseButtonPressed;
|
||||
event.data = MouseButtonEvent{button, mods, pos};
|
||||
event.data = MouseButtonEvent{btn, mods, pos};
|
||||
return event;
|
||||
}
|
||||
|
||||
Event Event::createMouseButtonRelease(int button, int mods, const Vec2 &pos) {
|
||||
Event Event::mouseRelease(i32 btn, i32 mods, Vec2 pos) {
|
||||
Event event;
|
||||
event.type = EventType::MouseButtonReleased;
|
||||
event.data = MouseButtonEvent{button, mods, pos};
|
||||
event.data = MouseButtonEvent{btn, mods, pos};
|
||||
return event;
|
||||
}
|
||||
|
||||
Event Event::createMouseMove(const Vec2 &pos, const Vec2 &delta) {
|
||||
Event Event::mouseMove(Vec2 pos, Vec2 delta) {
|
||||
Event event;
|
||||
event.type = EventType::MouseMoved;
|
||||
event.data = MouseMoveEvent{pos, delta};
|
||||
return event;
|
||||
}
|
||||
|
||||
Event Event::createMouseScroll(const Vec2 &offset, const Vec2 &pos) {
|
||||
Event Event::mouseScroll(Vec2 offset, Vec2 pos) {
|
||||
Event event;
|
||||
event.type = EventType::MouseScrolled;
|
||||
event.data = MouseScrollEvent{offset, pos};
|
||||
|
|
|
|||
|
|
@ -5,14 +5,13 @@ namespace extra2d {
|
|||
|
||||
EventDispatcher::EventDispatcher() : nextId_(1) {}
|
||||
|
||||
ListenerId EventDispatcher::addListener(EventType type,
|
||||
EventCallback callback) {
|
||||
ListenerId id = nextId_++;
|
||||
listeners_[type].push_back({id, type, callback});
|
||||
ListenerID EventDispatcher::on(EventType type, EventFn fn) {
|
||||
ListenerID id = nextId_++;
|
||||
listeners_[type].push_back({id, type, fn});
|
||||
return id;
|
||||
}
|
||||
|
||||
void EventDispatcher::removeListener(ListenerId id) {
|
||||
void EventDispatcher::off(ListenerID id) {
|
||||
for (auto &[type, listeners] : listeners_) {
|
||||
auto it = std::remove_if(listeners.begin(), listeners.end(),
|
||||
[id](const Listener &l) { return l.id == id; });
|
||||
|
|
@ -23,11 +22,11 @@ void EventDispatcher::removeListener(ListenerId id) {
|
|||
}
|
||||
}
|
||||
|
||||
void EventDispatcher::removeAllListeners(EventType type) {
|
||||
void EventDispatcher::offAll(EventType type) {
|
||||
listeners_.erase(type);
|
||||
}
|
||||
|
||||
void EventDispatcher::removeAllListeners() { listeners_.clear(); }
|
||||
void EventDispatcher::offAll() { listeners_.clear(); }
|
||||
|
||||
void EventDispatcher::dispatch(Event &event) {
|
||||
auto it = listeners_.find(event.type);
|
||||
|
|
@ -35,7 +34,7 @@ void EventDispatcher::dispatch(Event &event) {
|
|||
for (auto &listener : it->second) {
|
||||
if (event.handled)
|
||||
break;
|
||||
listener.callback(event);
|
||||
listener.fn(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -45,19 +44,19 @@ void EventDispatcher::dispatch(const Event &event) {
|
|||
dispatch(mutableEvent);
|
||||
}
|
||||
|
||||
void EventDispatcher::processQueue(EventQueue &queue) {
|
||||
void EventDispatcher::process(EventQueue &queue) {
|
||||
Event event;
|
||||
while (queue.poll(event)) {
|
||||
dispatch(event);
|
||||
}
|
||||
}
|
||||
|
||||
size_t EventDispatcher::getListenerCount(EventType type) const {
|
||||
size_t EventDispatcher::listenerCount(EventType type) const {
|
||||
auto it = listeners_.find(type);
|
||||
return (it != listeners_.end()) ? it->second.size() : 0;
|
||||
}
|
||||
|
||||
size_t EventDispatcher::getTotalListenerCount() const {
|
||||
size_t EventDispatcher::totalListeners() const {
|
||||
size_t count = 0;
|
||||
for (const auto &[type, listeners] : listeners_) {
|
||||
count += listeners.size();
|
||||
|
|
|
|||
|
|
@ -4,50 +4,42 @@ namespace extra2d {
|
|||
|
||||
EventQueue::EventQueue() = default;
|
||||
|
||||
void EventQueue::push(const Event &event) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
queue_.push(event);
|
||||
bool EventQueue::push(const Event &event) {
|
||||
return buffer_.push(event);
|
||||
}
|
||||
|
||||
void EventQueue::push(Event &&event) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
queue_.push(std::move(event));
|
||||
bool EventQueue::push(Event &&event) {
|
||||
return buffer_.push(std::move(event));
|
||||
}
|
||||
|
||||
bool EventQueue::poll(Event &event) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
if (queue_.empty()) {
|
||||
return false;
|
||||
}
|
||||
event = queue_.front();
|
||||
queue_.pop();
|
||||
return true;
|
||||
return buffer_.pop(event);
|
||||
}
|
||||
|
||||
bool EventQueue::peek(Event &event) const {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
if (queue_.empty()) {
|
||||
if (buffer_.empty()) {
|
||||
return false;
|
||||
}
|
||||
event = queue_.front();
|
||||
return true;
|
||||
// 环形缓冲区不支持peek,这里简化处理
|
||||
// 实际应用中可能需要双缓冲或其他机制
|
||||
return false;
|
||||
}
|
||||
|
||||
void EventQueue::clear() {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
while (!queue_.empty()) {
|
||||
queue_.pop();
|
||||
Event event;
|
||||
while (buffer_.pop(event)) {
|
||||
// 持续弹出直到为空
|
||||
}
|
||||
}
|
||||
|
||||
bool EventQueue::empty() const {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
return queue_.empty();
|
||||
return buffer_.empty();
|
||||
}
|
||||
|
||||
size_t EventQueue::size() const {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
return queue_.size();
|
||||
return buffer_.size();
|
||||
}
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
|
|||
10992
Extra2D/src/glad/glad.c
10992
Extra2D/src/glad/glad.c
File diff suppressed because one or more lines are too long
|
|
@ -1,6 +1,6 @@
|
|||
#include <extra2d/graphics/opengl/gl_font_atlas.h>
|
||||
#include <extra2d/core/string.h>
|
||||
#include <extra2d/utils/logger.h>
|
||||
#include <simdutf/simdutf.h>
|
||||
#include <fstream>
|
||||
#define STB_TRUETYPE_IMPLEMENTATION
|
||||
#include <stb/stb_truetype.h>
|
||||
|
|
@ -75,7 +75,10 @@ Vec2 GLFontAtlas::measureText(const std::string &text) {
|
|||
float height = getAscent() - getDescent();
|
||||
float currentWidth = 0.0f;
|
||||
|
||||
for (char32_t codepoint : utf8ToUtf32(text)) {
|
||||
std::u32string utf32_text;
|
||||
utf32_text.resize(simdutf::utf32_length_from_utf8(text.data(), text.size()));
|
||||
simdutf::convert_utf8_to_utf32(text.data(), text.size(), utf32_text.data());
|
||||
for (char32_t codepoint : utf32_text) {
|
||||
if (codepoint == '\n') {
|
||||
width = std::max(width, currentWidth);
|
||||
currentWidth = 0.0f;
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <extra2d/core/string.h>
|
||||
#include <simdutf/simdutf.h>
|
||||
#include <extra2d/graphics/gpu_context.h>
|
||||
#include <extra2d/graphics/opengl/gl_font_atlas.h>
|
||||
#include <extra2d/graphics/opengl/gl_renderer.h>
|
||||
|
|
@ -448,7 +448,10 @@ void GLRenderer::drawText(const FontAtlas &font, const std::string &text,
|
|||
std::vector<GLSpriteBatch::SpriteData> sprites;
|
||||
sprites.reserve(text.size()); // 预分配空间
|
||||
|
||||
for (char32_t codepoint : utf8ToUtf32(text)) {
|
||||
std::u32string utf32_text;
|
||||
utf32_text.resize(simdutf::utf32_length_from_utf8(text.data(), text.size()));
|
||||
simdutf::convert_utf8_to_utf32(text.data(), text.size(), utf32_text.data());
|
||||
for (char32_t codepoint : utf32_text) {
|
||||
if (codepoint == '\n') {
|
||||
cursorX = x;
|
||||
cursorY += font.getLineHeight();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,332 @@
|
|||
#include "extra2d/services/asset_service.h"
|
||||
|
||||
#include <filesystem>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// AssetService 实现
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
AssetService::AssetService() : cache_(ptr::makeUnique<AssetCache>()) {}
|
||||
|
||||
AssetService::~AssetService() { shutdown(); }
|
||||
|
||||
bool AssetService::init() {
|
||||
if (initialized()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
setState(ServiceState::Initializing);
|
||||
|
||||
registerLoader<TextureAsset>(AssetLoaderFactory::createTextureLoader());
|
||||
registerLoader<FontAsset>(AssetLoaderFactory::createFontLoader());
|
||||
registerLoader<ShaderAsset>(AssetLoaderFactory::createShaderLoader());
|
||||
registerLoader<AudioAsset>(AssetLoaderFactory::createAudioLoader());
|
||||
registerLoader<DataAsset>(AssetLoaderFactory::createDataLoader());
|
||||
|
||||
running_ = true;
|
||||
workerThread_ = std::thread(&AssetService::workerFunc, this);
|
||||
|
||||
setState(ServiceState::Running);
|
||||
return true;
|
||||
}
|
||||
|
||||
void AssetService::shutdown() {
|
||||
if (!initialized()) {
|
||||
return;
|
||||
}
|
||||
|
||||
setState(ServiceState::Stopping);
|
||||
|
||||
running_ = false;
|
||||
taskCv_.notify_all();
|
||||
|
||||
if (workerThread_.joinable()) {
|
||||
workerThread_.join();
|
||||
}
|
||||
|
||||
clear();
|
||||
packManager_.unmountAll();
|
||||
loaders_.clear();
|
||||
|
||||
setState(ServiceState::Stopped);
|
||||
}
|
||||
|
||||
bool AssetService::isLoaded(const std::string &path) const {
|
||||
std::shared_lock<std::shared_mutex> lock(mutex_);
|
||||
auto it = states_.find(AssetID(path));
|
||||
return it != states_.end() && it->second == AssetState::Loaded;
|
||||
}
|
||||
|
||||
bool AssetService::isLoading(const std::string &path) const {
|
||||
std::shared_lock<std::shared_mutex> lock(mutex_);
|
||||
auto it = states_.find(AssetID(path));
|
||||
return it != states_.end() && it->second == AssetState::Loading;
|
||||
}
|
||||
|
||||
void AssetService::unload(const std::string &path) {
|
||||
std::unique_lock<std::shared_mutex> lock(mutex_);
|
||||
AssetID id(path);
|
||||
assets_.erase(id);
|
||||
states_.erase(id);
|
||||
}
|
||||
|
||||
void AssetService::setLimit(size_t maxBytes) { cache_->setLimit(maxBytes); }
|
||||
|
||||
size_t AssetService::size() const { return cache_->size(); }
|
||||
|
||||
void AssetService::purge() {
|
||||
std::unique_lock<std::shared_mutex> lock(mutex_);
|
||||
|
||||
std::vector<AssetID> toRemove;
|
||||
for (const auto &[id, loaded] : assets_) {
|
||||
if (loaded.asset.use_count() <= 2) {
|
||||
toRemove.push_back(id);
|
||||
}
|
||||
}
|
||||
for (const auto &id : toRemove) {
|
||||
assets_.erase(id);
|
||||
states_.erase(id);
|
||||
}
|
||||
|
||||
cache_->purge();
|
||||
}
|
||||
|
||||
void AssetService::clear() {
|
||||
std::unique_lock<std::shared_mutex> lock(mutex_);
|
||||
assets_.clear();
|
||||
states_.clear();
|
||||
cache_->clear();
|
||||
}
|
||||
|
||||
CacheStats AssetService::stats() const { return cache_->stats(); }
|
||||
|
||||
bool AssetService::mount(const std::string &path) {
|
||||
return packManager_.mount(path);
|
||||
}
|
||||
|
||||
void AssetService::unmount(const std::string &path) {
|
||||
packManager_.unmount(path);
|
||||
}
|
||||
|
||||
void AssetService::setPipe(DataPipe pipe) { pipe_ = std::move(pipe); }
|
||||
|
||||
void AssetService::setRoot(const std::string &path) { root_ = path; }
|
||||
|
||||
std::string AssetService::root() const { return root_; }
|
||||
|
||||
void AssetService::process() {
|
||||
std::queue<std::function<void()>> callbacks;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(callbackMutex_);
|
||||
callbacks = std::move(callbackQueue_);
|
||||
callbackQueue_ = {};
|
||||
}
|
||||
|
||||
while (!callbacks.empty()) {
|
||||
callbacks.front()();
|
||||
callbacks.pop();
|
||||
}
|
||||
}
|
||||
|
||||
AssetHandleBase AssetService::loadImpl(const AssetID &id,
|
||||
std::type_index type) {
|
||||
{
|
||||
std::shared_lock<std::shared_mutex> lock(mutex_);
|
||||
auto it = assets_.find(id);
|
||||
if (it != assets_.end()) {
|
||||
cache_->hit();
|
||||
return AssetHandleBase(id, it->second.asset);
|
||||
}
|
||||
}
|
||||
|
||||
cache_->miss();
|
||||
|
||||
Ref<Asset> asset = loadFromFile(id, type);
|
||||
if (!asset) {
|
||||
asset = loadFromPack(id, type);
|
||||
}
|
||||
|
||||
if (!asset) {
|
||||
return AssetHandleBase();
|
||||
}
|
||||
|
||||
{
|
||||
std::unique_lock<std::shared_mutex> lock(mutex_);
|
||||
assets_[id] = {asset, type};
|
||||
states_[id] = AssetState::Loaded;
|
||||
}
|
||||
|
||||
cache_->add(asset);
|
||||
return AssetHandleBase(id, asset);
|
||||
}
|
||||
|
||||
void AssetService::loadAsyncImpl(
|
||||
const AssetID &id, std::type_index type,
|
||||
std::function<void(AssetHandleBase)> callback) {
|
||||
{
|
||||
std::shared_lock<std::shared_mutex> lock(mutex_);
|
||||
auto it = assets_.find(id);
|
||||
if (it != assets_.end()) {
|
||||
cache_->hit();
|
||||
callback(AssetHandleBase(id, it->second.asset));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
cache_->miss();
|
||||
|
||||
{
|
||||
std::unique_lock<std::shared_mutex> lock(mutex_);
|
||||
states_[id] = AssetState::Loading;
|
||||
}
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(taskMutex_);
|
||||
taskQueue_.push({id, type, std::move(callback)});
|
||||
}
|
||||
taskCv_.notify_one();
|
||||
}
|
||||
|
||||
AssetHandleBase AssetService::getImpl(const AssetID &id, std::type_index type) {
|
||||
(void)type;
|
||||
std::shared_lock<std::shared_mutex> lock(mutex_);
|
||||
auto it = assets_.find(id);
|
||||
if (it != assets_.end()) {
|
||||
cache_->hit();
|
||||
return AssetHandleBase(id, it->second.asset);
|
||||
}
|
||||
|
||||
cache_->miss();
|
||||
return AssetHandleBase();
|
||||
}
|
||||
|
||||
void AssetService::preloadImpl(const AssetID &id, std::type_index type) {
|
||||
{
|
||||
std::shared_lock<std::shared_mutex> lock(mutex_);
|
||||
if (assets_.find(id) != assets_.end()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(taskMutex_);
|
||||
taskQueue_.push({id, type, nullptr});
|
||||
}
|
||||
taskCv_.notify_one();
|
||||
}
|
||||
|
||||
void AssetService::registerLoaderImpl(std::type_index type,
|
||||
Unique<AssetLoaderBase> loader) {
|
||||
std::unique_lock<std::shared_mutex> lock(mutex_);
|
||||
loaders_[type] = std::move(loader);
|
||||
}
|
||||
|
||||
void AssetService::workerFunc() {
|
||||
while (running_) {
|
||||
LoadTask task;
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(taskMutex_);
|
||||
taskCv_.wait(lock, [this] { return !taskQueue_.empty() || !running_; });
|
||||
|
||||
if (!running_) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (taskQueue_.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
task = std::move(taskQueue_.front());
|
||||
taskQueue_.pop();
|
||||
}
|
||||
|
||||
Ref<Asset> asset = loadFromFile(task.id, task.type);
|
||||
if (!asset) {
|
||||
asset = loadFromPack(task.id, task.type);
|
||||
}
|
||||
|
||||
if (asset) {
|
||||
std::unique_lock<std::shared_mutex> lock(mutex_);
|
||||
assets_[task.id] = {asset, task.type};
|
||||
states_[task.id] = AssetState::Loaded;
|
||||
cache_->add(asset);
|
||||
} else {
|
||||
std::unique_lock<std::shared_mutex> lock(mutex_);
|
||||
states_[task.id] = AssetState::Failed;
|
||||
}
|
||||
|
||||
if (task.callback) {
|
||||
std::lock_guard<std::mutex> lock(callbackMutex_);
|
||||
callbackQueue_.push(
|
||||
[task, asset]() { task.callback(AssetHandleBase(task.id, asset)); });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ref<Asset> AssetService::loadFromFile(const AssetID &id, std::type_index type) {
|
||||
auto *loader = getLoader(type);
|
||||
if (!loader) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::string fullPath = root_.empty() ? id.path : root_ + "/" + id.path;
|
||||
|
||||
if (!std::filesystem::exists(fullPath)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return loader->loadBase(fullPath);
|
||||
}
|
||||
|
||||
Ref<Asset> AssetService::loadFromPack(const AssetID &id, std::type_index type) {
|
||||
auto *loader = getLoader(type);
|
||||
if (!loader) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto *pack = packManager_.find(id);
|
||||
if (!pack) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto data = pack->read(id);
|
||||
if (data.empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!pipe_.empty()) {
|
||||
data = pipe_.process(data);
|
||||
}
|
||||
|
||||
return loader->loadFromMemoryBase(data.data(), data.size());
|
||||
}
|
||||
|
||||
AssetLoaderBase *AssetService::getLoader(std::type_index type) {
|
||||
auto it = loaders_.find(type);
|
||||
return it != loaders_.end() ? it->second.get() : nullptr;
|
||||
}
|
||||
|
||||
std::type_index AssetService::inferType(const std::string &path) {
|
||||
std::filesystem::path p(path);
|
||||
std::string ext = p.extension().string();
|
||||
|
||||
AssetType type = AssetLoaderFactory::getTypeByExtension(ext);
|
||||
switch (type) {
|
||||
case AssetType::Texture:
|
||||
return typeid(TextureAsset);
|
||||
case AssetType::Font:
|
||||
return typeid(FontAsset);
|
||||
case AssetType::Shader:
|
||||
return typeid(ShaderAsset);
|
||||
case AssetType::Audio:
|
||||
return typeid(AudioAsset);
|
||||
case AssetType::Data:
|
||||
return typeid(DataAsset);
|
||||
default:
|
||||
return typeid(DataAsset);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
#include <extra2d/services/event_service.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
EventService::EventService() {
|
||||
info_.name = "EventService";
|
||||
info_.priority = ServicePriority::Event;
|
||||
info_.enabled = true;
|
||||
}
|
||||
|
||||
ServiceInfo EventService::info() const { return info_; }
|
||||
|
||||
bool EventService::init() {
|
||||
setState(ServiceState::Running);
|
||||
return true;
|
||||
}
|
||||
|
||||
void EventService::shutdown() {
|
||||
queue_.clear();
|
||||
dispatcher_.offAll();
|
||||
setState(ServiceState::Stopped);
|
||||
}
|
||||
|
||||
void EventService::update(f32 dt) {
|
||||
if (state() == ServiceState::Running) {
|
||||
process();
|
||||
}
|
||||
}
|
||||
|
||||
void EventService::push(const Event &event) { queue_.push(event); }
|
||||
|
||||
void EventService::push(Event &&event) { queue_.push(std::move(event)); }
|
||||
|
||||
bool EventService::poll(Event &event) { return queue_.poll(event); }
|
||||
|
||||
ListenerID EventService::on(EventType type, EventDispatcher::EventFn fn) {
|
||||
return dispatcher_.on(type, fn);
|
||||
}
|
||||
|
||||
void EventService::off(ListenerID id) {
|
||||
dispatcher_.off(id);
|
||||
}
|
||||
|
||||
void EventService::offAll(EventType type) {
|
||||
dispatcher_.offAll(type);
|
||||
}
|
||||
|
||||
void EventService::offAll() { dispatcher_.offAll(); }
|
||||
|
||||
void EventService::dispatch(Event &event) { dispatcher_.dispatch(event); }
|
||||
|
||||
void EventService::process() { dispatcher_.process(queue_); }
|
||||
|
||||
size_t EventService::listenerCount(EventType type) const {
|
||||
return dispatcher_.listenerCount(type);
|
||||
}
|
||||
|
||||
size_t EventService::totalListeners() const {
|
||||
return dispatcher_.totalListeners();
|
||||
}
|
||||
|
||||
size_t EventService::queueSize() const { return queue_.size(); }
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -0,0 +1,254 @@
|
|||
#include <chrono>
|
||||
#include <cstdarg>
|
||||
#include <cstdio>
|
||||
#include <extra2d/services/logger_service.h>
|
||||
#include <mutex>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
#ifdef _WIN32
|
||||
static bool enableWindowsConsoleFeatures() {
|
||||
bool success = true;
|
||||
|
||||
HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
|
||||
if (hOut != INVALID_HANDLE_VALUE) {
|
||||
DWORD dwMode = 0;
|
||||
if (GetConsoleMode(hOut, &dwMode)) {
|
||||
dwMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
|
||||
if (!SetConsoleMode(hOut, dwMode)) {
|
||||
success = false;
|
||||
}
|
||||
} else {
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
|
||||
SetConsoleOutputCP(CP_UTF8);
|
||||
SetConsoleCP(CP_UTF8);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
static bool g_windowsConsoleInitialized = false;
|
||||
#endif
|
||||
|
||||
class ConsoleLogger::Impl {
|
||||
public:
|
||||
std::mutex mutex_;
|
||||
};
|
||||
|
||||
ConsoleLogger::ConsoleLogger()
|
||||
: level_(LogLevel::Info), colors_(true),
|
||||
impl_(std::make_unique<Impl>()) {
|
||||
info_.name = "ConsoleLogger";
|
||||
info_.priority = ServicePriority::Core;
|
||||
|
||||
levelColors_[static_cast<int>(LogLevel::Trace)] = LogColor::Gray();
|
||||
levelColors_[static_cast<int>(LogLevel::Debug)] = LogColor::Cyan();
|
||||
levelColors_[static_cast<int>(LogLevel::Info)] = LogColor::SkyLight();
|
||||
levelColors_[static_cast<int>(LogLevel::Registry)] = LogColor::IndigoLight();
|
||||
levelColors_[static_cast<int>(LogLevel::Warn)] = LogColor::Yellow();
|
||||
levelColors_[static_cast<int>(LogLevel::Error)] = LogColor::Red();
|
||||
levelColors_[static_cast<int>(LogLevel::Fatal)] = LogColor::Magenta();
|
||||
|
||||
#ifdef _WIN32
|
||||
if (!g_windowsConsoleInitialized) {
|
||||
g_windowsConsoleInitialized = enableWindowsConsoleFeatures();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
ConsoleLogger::~ConsoleLogger() = default;
|
||||
|
||||
bool ConsoleLogger::init() {
|
||||
setState(ServiceState::Running);
|
||||
return true;
|
||||
}
|
||||
|
||||
void ConsoleLogger::shutdown() { setState(ServiceState::Stopped); }
|
||||
|
||||
void ConsoleLogger::level(LogLevel lvl) { level_ = lvl; }
|
||||
|
||||
LogLevel ConsoleLogger::level() const { return level_; }
|
||||
|
||||
bool ConsoleLogger::enabled(LogLevel lvl) const {
|
||||
return static_cast<int>(lvl) >= static_cast<int>(level_);
|
||||
}
|
||||
|
||||
void ConsoleLogger::log(LogLevel lvl, const char *fmt, ...) {
|
||||
if (!enabled(lvl))
|
||||
return;
|
||||
|
||||
char buffer[1024];
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
vsnprintf(buffer, sizeof(buffer), fmt, args);
|
||||
va_end(args);
|
||||
|
||||
output(lvl, buffer);
|
||||
}
|
||||
|
||||
void ConsoleLogger::log(LogLevel lvl, const std::string &msg) {
|
||||
if (!enabled(lvl))
|
||||
return;
|
||||
output(lvl, msg.c_str());
|
||||
}
|
||||
|
||||
void ConsoleLogger::trace(const char *fmt, ...) {
|
||||
if (!enabled(LogLevel::Trace))
|
||||
return;
|
||||
char buffer[1024];
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
vsnprintf(buffer, sizeof(buffer), fmt, args);
|
||||
va_end(args);
|
||||
output(LogLevel::Trace, buffer);
|
||||
}
|
||||
|
||||
void ConsoleLogger::debug(const char *fmt, ...) {
|
||||
if (!enabled(LogLevel::Debug))
|
||||
return;
|
||||
char buffer[1024];
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
vsnprintf(buffer, sizeof(buffer), fmt, args);
|
||||
va_end(args);
|
||||
output(LogLevel::Debug, buffer);
|
||||
}
|
||||
|
||||
void ConsoleLogger::info(const char *fmt, ...) {
|
||||
if (!enabled(LogLevel::Info))
|
||||
return;
|
||||
char buffer[1024];
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
vsnprintf(buffer, sizeof(buffer), fmt, args);
|
||||
va_end(args);
|
||||
output(LogLevel::Info, buffer);
|
||||
}
|
||||
|
||||
void ConsoleLogger::registry(const char *fmt, ...) {
|
||||
if (!enabled(LogLevel::Registry))
|
||||
return;
|
||||
char buffer[1024];
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
vsnprintf(buffer, sizeof(buffer), fmt, args);
|
||||
va_end(args);
|
||||
output(LogLevel::Registry, buffer);
|
||||
}
|
||||
|
||||
void ConsoleLogger::warn(const char *fmt, ...) {
|
||||
if (!enabled(LogLevel::Warn))
|
||||
return;
|
||||
char buffer[1024];
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
vsnprintf(buffer, sizeof(buffer), fmt, args);
|
||||
va_end(args);
|
||||
output(LogLevel::Warn, buffer);
|
||||
}
|
||||
|
||||
void ConsoleLogger::error(const char *fmt, ...) {
|
||||
if (!enabled(LogLevel::Error))
|
||||
return;
|
||||
char buffer[1024];
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
vsnprintf(buffer, sizeof(buffer), fmt, args);
|
||||
va_end(args);
|
||||
output(LogLevel::Error, buffer);
|
||||
}
|
||||
|
||||
void ConsoleLogger::fatal(const char *fmt, ...) {
|
||||
if (!enabled(LogLevel::Fatal))
|
||||
return;
|
||||
char buffer[1024];
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
vsnprintf(buffer, sizeof(buffer), fmt, args);
|
||||
va_end(args);
|
||||
output(LogLevel::Fatal, buffer);
|
||||
}
|
||||
|
||||
void ConsoleLogger::levelColor(LogLevel lvl, const LogColor &c) {
|
||||
int idx = static_cast<int>(lvl);
|
||||
if (idx >= 0 && idx < 7) {
|
||||
levelColors_[idx] = c;
|
||||
}
|
||||
}
|
||||
|
||||
LogColor ConsoleLogger::levelColor(LogLevel lvl) const {
|
||||
int idx = static_cast<int>(lvl);
|
||||
if (idx >= 0 && idx < 7) {
|
||||
return levelColors_[idx];
|
||||
}
|
||||
return LogColor::White();
|
||||
}
|
||||
|
||||
void ConsoleLogger::colors(bool on) { colors_ = on; }
|
||||
|
||||
bool ConsoleLogger::colors() const { return colors_; }
|
||||
|
||||
std::string ConsoleLogger::ansiColor(LogLevel lvl) {
|
||||
const LogColor &c = levelColor(lvl);
|
||||
char buf[32];
|
||||
snprintf(buf, sizeof(buf), "\033[38;2;%d;%d;%dm", c.r, c.g, c.b);
|
||||
return std::string(buf);
|
||||
}
|
||||
|
||||
void ConsoleLogger::output(LogLevel lvl, const char *msg) {
|
||||
std::lock_guard<std::mutex> lock(impl_->mutex_);
|
||||
|
||||
auto now = std::chrono::system_clock::now();
|
||||
auto time = std::chrono::system_clock::to_time_t(now);
|
||||
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
now.time_since_epoch()) %
|
||||
1000;
|
||||
|
||||
std::tm tm;
|
||||
#ifdef _WIN32
|
||||
localtime_s(&tm, &time);
|
||||
#else
|
||||
localtime_r(&time, &tm);
|
||||
#endif
|
||||
|
||||
const char *levelStr = levelString(lvl);
|
||||
const char *reset = "\033[0m";
|
||||
|
||||
if (colors_) {
|
||||
std::string color = ansiColor(lvl);
|
||||
printf("%s[%02d:%02d:%02d.%03d] [%s] %s%s\n", color.c_str(), tm.tm_hour,
|
||||
tm.tm_min, tm.tm_sec, (int)ms.count(), levelStr, msg, reset);
|
||||
} else {
|
||||
printf("[%02d:%02d:%02d.%03d] [%s] %s\n", tm.tm_hour, tm.tm_min, tm.tm_sec,
|
||||
(int)ms.count(), levelStr, msg);
|
||||
}
|
||||
}
|
||||
|
||||
const char *ConsoleLogger::levelString(LogLevel lvl) {
|
||||
switch (lvl) {
|
||||
case LogLevel::Trace:
|
||||
return "TRACE";
|
||||
case LogLevel::Debug:
|
||||
return "DEBUG";
|
||||
case LogLevel::Info:
|
||||
return "INFO";
|
||||
case LogLevel::Registry:
|
||||
return "REGISTRY";
|
||||
case LogLevel::Warn:
|
||||
return "WARN";
|
||||
case LogLevel::Error:
|
||||
return "ERROR";
|
||||
case LogLevel::Fatal:
|
||||
return "FATAL";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
#include <extra2d/services/timer_service.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
TimerService::TimerService() {
|
||||
info_.name = "TimerService";
|
||||
info_.priority = ServicePriority::Timer;
|
||||
info_.enabled = true;
|
||||
}
|
||||
|
||||
ServiceInfo TimerService::info() const { return info_; }
|
||||
|
||||
bool TimerService::init() {
|
||||
setState(ServiceState::Running);
|
||||
return true;
|
||||
}
|
||||
|
||||
void TimerService::shutdown() {
|
||||
mgr_.clear();
|
||||
setState(ServiceState::Stopped);
|
||||
}
|
||||
|
||||
void TimerService::update(f32 dt) {
|
||||
if (state() == ServiceState::Running) {
|
||||
mgr_.update(dt);
|
||||
}
|
||||
}
|
||||
|
||||
u32 TimerService::add(f32 delay, Timer::Fn fn) {
|
||||
return mgr_.add(delay, fn);
|
||||
}
|
||||
|
||||
u32 TimerService::addRepeat(f32 interval, Timer::Fn fn) {
|
||||
return mgr_.addRepeat(interval, fn);
|
||||
}
|
||||
|
||||
void TimerService::cancel(u32 timerId) {
|
||||
mgr_.cancel(timerId);
|
||||
}
|
||||
|
||||
void TimerService::pauseTimer(u32 timerId) { mgr_.pause(timerId); }
|
||||
|
||||
void TimerService::resumeTimer(u32 timerId) {
|
||||
mgr_.resume(timerId);
|
||||
}
|
||||
|
||||
void TimerService::clear() { mgr_.clear(); }
|
||||
|
||||
size_t TimerService::count() const { return mgr_.count(); }
|
||||
|
||||
} // namespace extra2d
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -3,52 +3,100 @@
|
|||
|
||||
namespace extra2d {
|
||||
|
||||
/**
|
||||
* @brief 构造函数,初始化随机数生成器
|
||||
*
|
||||
* 使用当前时间作为默认种子初始化随机数生成器
|
||||
*/
|
||||
Random::Random() : floatDist_(0.0f, 1.0f) {
|
||||
// 使用当前时间作为默认种子
|
||||
randomize();
|
||||
}
|
||||
|
||||
Random &Random::getInstance() {
|
||||
/**
|
||||
* @brief 获取Random单例实例
|
||||
* @return Random单例的引用
|
||||
*/
|
||||
Random &Random::get() {
|
||||
static Random instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void Random::setSeed(uint32 seed) { generator_.seed(seed); }
|
||||
/**
|
||||
* @brief 设置随机数种子
|
||||
* @param s 随机数种子值
|
||||
*/
|
||||
void Random::seed(u32 s) { generator_.seed(s); }
|
||||
|
||||
/**
|
||||
* @brief 使用当前时间随机化种子
|
||||
*
|
||||
* 使用高精度时钟的当前时间作为随机数生成器的种子
|
||||
*/
|
||||
void Random::randomize() {
|
||||
auto now = std::chrono::high_resolution_clock::now();
|
||||
auto time = now.time_since_epoch().count();
|
||||
generator_.seed(static_cast<uint32>(time));
|
||||
generator_.seed(static_cast<u32>(time));
|
||||
}
|
||||
|
||||
float Random::getFloat() { return floatDist_(generator_); }
|
||||
/**
|
||||
* @brief 获取[0, 1)范围内的随机浮点数
|
||||
* @return 随机浮点数,范围[0, 1)
|
||||
*/
|
||||
f32 Random::randomF32() { return floatDist_(generator_); }
|
||||
|
||||
float Random::getFloat(float min, float max) {
|
||||
/**
|
||||
* @brief 获取指定范围内的随机浮点数
|
||||
* @param min 最小值
|
||||
* @param max 最大值
|
||||
* @return 随机浮点数,范围[min, max]
|
||||
*/
|
||||
f32 Random::randomF32(f32 min, f32 max) {
|
||||
if (min >= max) {
|
||||
return min;
|
||||
}
|
||||
return min + floatDist_(generator_) * (max - min);
|
||||
}
|
||||
|
||||
int Random::getInt(int max) {
|
||||
/**
|
||||
* @brief 获取[0, max]范围内的随机整数
|
||||
* @param max 最大值(包含)
|
||||
* @return 随机整数,范围[0, max]
|
||||
*/
|
||||
i32 Random::randomI32(i32 max) {
|
||||
if (max <= 0) {
|
||||
return 0;
|
||||
}
|
||||
std::uniform_int_distribution<int> dist(0, max);
|
||||
std::uniform_int_distribution<i32> dist(0, max);
|
||||
return dist(generator_);
|
||||
}
|
||||
|
||||
int Random::getInt(int min, int max) {
|
||||
/**
|
||||
* @brief 获取指定范围内的随机整数
|
||||
* @param min 最小值(包含)
|
||||
* @param max 最大值(包含)
|
||||
* @return 随机整数,范围[min, max]
|
||||
*/
|
||||
i32 Random::randomI32(i32 min, i32 max) {
|
||||
if (min >= max) {
|
||||
return min;
|
||||
}
|
||||
std::uniform_int_distribution<int> dist(min, max);
|
||||
std::uniform_int_distribution<i32> dist(min, max);
|
||||
return dist(generator_);
|
||||
}
|
||||
|
||||
bool Random::getBool() { return floatDist_(generator_) >= 0.5f; }
|
||||
/**
|
||||
* @brief 获取随机布尔值(50%概率)
|
||||
* @return 随机布尔值
|
||||
*/
|
||||
bool Random::boolean() { return floatDist_(generator_) >= 0.5f; }
|
||||
|
||||
bool Random::getBool(float probability) {
|
||||
/**
|
||||
* @brief 以指定概率获取随机布尔值
|
||||
* @param probability 返回true的概率,范围[0.0, 1.0]
|
||||
* @return 随机布尔值
|
||||
*/
|
||||
bool Random::boolean(f32 probability) {
|
||||
if (probability <= 0.0f) {
|
||||
return false;
|
||||
}
|
||||
|
|
@ -58,11 +106,19 @@ bool Random::getBool(float probability) {
|
|||
return floatDist_(generator_) < probability;
|
||||
}
|
||||
|
||||
float Random::getAngle() {
|
||||
static const float TWO_PI = 6.28318530718f;
|
||||
/**
|
||||
* @brief 获取随机角度值
|
||||
* @return 随机角度,范围[0, 2π]
|
||||
*/
|
||||
f32 Random::angle() {
|
||||
static const f32 TWO_PI = 6.28318530718f;
|
||||
return floatDist_(generator_) * TWO_PI;
|
||||
}
|
||||
|
||||
float Random::getSigned() { return floatDist_(generator_) * 2.0f - 1.0f; }
|
||||
/**
|
||||
* @brief 获取有符号随机数
|
||||
* @return 随机浮点数,范围[-1.0, 1.0]
|
||||
*/
|
||||
f32 Random::signedF32() { return floatDist_(generator_) * 2.0f - 1.0f; }
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
|
|||
|
|
@ -3,24 +3,24 @@
|
|||
|
||||
namespace extra2d {
|
||||
|
||||
uint32 Timer::nextId_ = 1;
|
||||
u32 Timer::nextId_ = 1;
|
||||
|
||||
Timer::Timer(float interval, bool repeat, Callback callback)
|
||||
Timer::Timer(f32 interval, bool repeat, Fn fn)
|
||||
: interval_(interval), elapsed_(0.0f), repeat_(repeat), paused_(false),
|
||||
valid_(true), callback_(std::move(callback)) {
|
||||
valid_(true), fn_(std::move(fn)) {
|
||||
id_ = nextId_++;
|
||||
}
|
||||
|
||||
bool Timer::update(float deltaTime) {
|
||||
bool Timer::update(f32 dt) {
|
||||
if (!valid_ || paused_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
elapsed_ += deltaTime;
|
||||
elapsed_ += dt;
|
||||
|
||||
if (elapsed_ >= interval_) {
|
||||
if (callback_) {
|
||||
callback_();
|
||||
if (fn_) {
|
||||
fn_();
|
||||
}
|
||||
|
||||
if (repeat_) {
|
||||
|
|
@ -47,33 +47,28 @@ void Timer::resume() { paused_ = false; }
|
|||
|
||||
void Timer::cancel() { valid_ = false; }
|
||||
|
||||
float Timer::getRemaining() const {
|
||||
f32 Timer::remaining() const {
|
||||
if (!valid_ || paused_) {
|
||||
return 0.0f;
|
||||
}
|
||||
return std::max(0.0f, interval_ - elapsed_);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// TimerManager 实现
|
||||
// ============================================================================
|
||||
|
||||
uint32 TimerManager::addTimer(float delay, Timer::Callback callback) {
|
||||
auto timer = std::make_unique<Timer>(delay, false, std::move(callback));
|
||||
uint32 id = timer->getId();
|
||||
u32 TimerManager::add(f32 delay, Timer::Fn fn) {
|
||||
auto timer = std::make_unique<Timer>(delay, false, std::move(fn));
|
||||
u32 id = timer->id();
|
||||
timers_.emplace(id, std::move(timer));
|
||||
return id;
|
||||
}
|
||||
|
||||
uint32 TimerManager::addRepeatingTimer(float interval,
|
||||
Timer::Callback callback) {
|
||||
auto timer = std::make_unique<Timer>(interval, true, std::move(callback));
|
||||
uint32 id = timer->getId();
|
||||
u32 TimerManager::addRepeat(f32 interval, Timer::Fn fn) {
|
||||
auto timer = std::make_unique<Timer>(interval, true, std::move(fn));
|
||||
u32 id = timer->id();
|
||||
timers_.emplace(id, std::move(timer));
|
||||
return id;
|
||||
}
|
||||
|
||||
void TimerManager::cancelTimer(uint32 timerId) {
|
||||
void TimerManager::cancel(u32 timerId) {
|
||||
auto it = timers_.find(timerId);
|
||||
if (it != timers_.end()) {
|
||||
it->second->cancel();
|
||||
|
|
@ -81,31 +76,31 @@ void TimerManager::cancelTimer(uint32 timerId) {
|
|||
}
|
||||
}
|
||||
|
||||
void TimerManager::pauseTimer(uint32 timerId) {
|
||||
void TimerManager::pause(u32 timerId) {
|
||||
auto it = timers_.find(timerId);
|
||||
if (it != timers_.end()) {
|
||||
it->second->pause();
|
||||
}
|
||||
}
|
||||
|
||||
void TimerManager::resumeTimer(uint32 timerId) {
|
||||
void TimerManager::resume(u32 timerId) {
|
||||
auto it = timers_.find(timerId);
|
||||
if (it != timers_.end()) {
|
||||
it->second->resume();
|
||||
}
|
||||
}
|
||||
|
||||
void TimerManager::update(float deltaTime) {
|
||||
void TimerManager::update(f32 dt) {
|
||||
timersToRemove_.clear();
|
||||
|
||||
for (auto &[id, timer] : timers_) {
|
||||
timer->update(deltaTime);
|
||||
if (!timer->isValid()) {
|
||||
timer->update(dt);
|
||||
if (!timer->valid()) {
|
||||
timersToRemove_.push_back(id);
|
||||
}
|
||||
}
|
||||
|
||||
for (uint32 id : timersToRemove_) {
|
||||
for (u32 id : timersToRemove_) {
|
||||
timers_.erase(id);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -88,10 +88,6 @@ define_extra2d_engine()
|
|||
-- 示例程序目标(作为子项目)
|
||||
if is_config("examples","true") then
|
||||
includes("examples/hello_world", {rootdir = "examples/hello_world"})
|
||||
includes("examples/spatial_index_demo", {rootdir = "examples/spatial_index_demo"})
|
||||
includes("examples/collision_demo", {rootdir = "examples/collision_demo"})
|
||||
includes("examples/push_box", {rootdir = "examples/push_box"})
|
||||
includes("examples/flappy_bird", {rootdir = "examples/flappy_bird"})
|
||||
end
|
||||
|
||||
-- ==============================================
|
||||
|
|
|
|||
Loading…
Reference in New Issue