refactor(core): 重构核心模块与类型系统

- 移除旧的字符串处理模块,改用标准库和simdutf
- 重构事件系统,使用环形缓冲区和改进接口命名
- 新增服务注册与定位系统
- 引入模块化架构和依赖管理
- 统一类型别名命名规范
- 实现对象池和环形缓冲区等基础数据结构
- 重构计时器服务,优化接口设计
- 新增资产管理系统基础框架
- 改进随机数生成器接口
- 重构应用框架,支持模块化初始化
This commit is contained in:
ChestnutYueyue 2026-02-22 23:11:52 +08:00
parent 6fbebafef3
commit 9bf328b1dc
56 changed files with 98798 additions and 1533 deletions

View File

@ -1,80 +1,113 @@
#pragma once #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/core/types.h>
#include <extra2d/window/window.h> #include <string>
#include <memory>
namespace extra2d { namespace extra2d {
class Renderer; class GLFWWindow;
class Input; class WindowModule;
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;
};
/**
* @brief
*/
class Application { class Application {
public: public:
static Application &instance(); static Application &get();
Application(const Application &) = delete; Application(const Application &) = delete;
Application &operator=(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(); void shutdown();
/**
* @brief
*/
void run(); void run();
/**
* @brief 退
*/
void quit(); void quit();
/**
* @brief
*/
void pause(); void pause();
/**
* @brief
*/
void resume(); void resume();
bool isPaused() const { return paused_; }
bool isRunning() const { return running_; }
Window &window() { return *window_; } bool paused() const { return paused_; }
Renderer &renderer() { return *renderer_; } bool running() const { return running_; }
Input &input();
EventQueue &eventQueue();
EventDispatcher &eventDispatcher();
float deltaTime() const { return deltaTime_; } /**
float totalTime() const { return totalTime_; } * @brief
int fps() const { return currentFps_; } * @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: private:
Application() = default; Application();
~Application(); ~Application();
void mainLoop(); void mainLoop();
void update(); void update();
void render(); void render();
AppConfig config_;
UniquePtr<Window> window_;
UniquePtr<Renderer> renderer_;
UniquePtr<EventQueue> eventQueue_;
UniquePtr<EventDispatcher> eventDispatcher_;
bool initialized_ = false; bool initialized_ = false;
bool running_ = false; bool running_ = false;
bool paused_ = false; bool paused_ = false;
bool shouldQuit_ = false;
float deltaTime_ = 0.0f; f32 dt_ = 0.0f;
float totalTime_ = 0.0f; f32 totalTime_ = 0.0f;
double lastFrameTime_ = 0.0; f64 lastFrameTime_ = 0.0;
int frameCount_ = 0; int frameCount_ = 0;
float fpsTimer_ = 0.0f; f32 fpsTimer_ = 0.0f;
int currentFps_ = 0; int fps_ = 0;
}; };
} // namespace extra2d } // namespace extra2d

View File

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

View File

@ -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_;
};
}

View File

@ -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>)>;
}

View File

@ -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-40
*/
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);
};
}

View File

@ -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);
};
}

View File

@ -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);
}
};
}

View File

@ -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
*
*
* ZstdLZ4 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);
}

View File

@ -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}; inline constexpr Color Transparent{0.0f, 0.0f, 0.0f, 0.0f};
} // namespace Colors } // 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 } // namespace extra2d

View File

@ -330,6 +330,221 @@ inline float degrees(float radians) { return radians * RAD_TO_DEG; }
inline float radians(float degrees) { return degrees * DEG_TO_RAD; } 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 &center, 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 &center1, float radius1,
const Vec2 &center2, 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 math
} // namespace extra2d } // namespace extra2d

View File

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

View File

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

View File

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

View File

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

View File

@ -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 truefalse
*/
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 truefalse
*/
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 truefalse
*/
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

View File

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

View File

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

View File

@ -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); \
}

View File

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

View File

@ -7,41 +7,55 @@
namespace extra2d { 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>; /// 智能指针工厂函数命名空间
namespace ptr {
template <typename T> using WeakPtr = std::weak_ptr<T>; /// 创建 Ref 的便捷函数
template <typename T, typename... Args> inline Ref<T> make(Args &&...args) {
/// 创建 shared_ptr 的便捷函数
template <typename T, typename... Args> inline Ptr<T> makePtr(Args &&...args) {
return std::make_shared<T>(std::forward<Args>(args)...); return std::make_shared<T>(std::forward<Args>(args)...);
} }
/// 创建 unique_ptr 的便捷函数 /// 创建 Unique 的便捷函数
template <typename T, typename... Args> template <typename T, typename... Args>
inline UniquePtr<T> makeUnique(Args &&...args) { inline Unique<T> makeUnique(Args &&...args) {
return std::make_unique<T>(std::forward<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 i8 = int8_t;
using int16 = std::int16_t; using i16 = int16_t;
using int32 = std::int32_t; using i32 = int32_t;
using int64 = std::int64_t; using i64 = int64_t;
using uint8 = std::uint8_t; using u8 = uint8_t;
using uint16 = std::uint16_t; using u16 = uint16_t;
using uint32 = std::uint32_t; using u32 = uint32_t;
using uint64 = std::uint64_t; using u64 = uint64_t;
using f32 = float;
using f64 = double;
// ---------------------------------------------------------------------------
// ID 类型别名
// ---------------------------------------------------------------------------
using ID = u32;
} // namespace extra2d } // namespace extra2d

View File

@ -59,70 +59,70 @@ enum class EventType {
// 键盘事件数据 // 键盘事件数据
// ============================================================================ // ============================================================================
struct KeyEvent { struct KeyEvent {
int keyCode; i32 key;
int scancode; i32 scancode;
int mods; // 修饰键 (Shift, Ctrl, Alt, etc.) i32 mods; // 修饰键 (Shift, Ctrl, Alt, etc.)
}; };
// ============================================================================ // ============================================================================
// 鼠标事件数据 // 鼠标事件数据
// ============================================================================ // ============================================================================
struct MouseButtonEvent { struct MouseButtonEvent {
int button; i32 button;
int mods; i32 mods;
Vec2 position; Vec2 pos;
}; };
struct MouseMoveEvent { struct MouseMoveEvent {
Vec2 position; Vec2 pos;
Vec2 delta; Vec2 delta;
}; };
struct MouseScrollEvent { struct MouseScrollEvent {
Vec2 offset; Vec2 offset;
Vec2 position; Vec2 pos;
}; };
// ============================================================================ // ============================================================================
// 窗口事件数据 // 窗口事件数据
// ============================================================================ // ============================================================================
struct WindowResizeEvent { struct WindowResizeEvent {
int width; i32 w;
int height; i32 h;
}; };
struct WindowMoveEvent { struct WindowMoveEvent {
int x; i32 x;
int y; i32 y;
}; };
// ============================================================================ // ============================================================================
// 游戏手柄事件数据 // 游戏手柄事件数据
// ============================================================================ // ============================================================================
struct GamepadButtonEvent { struct GamepadButtonEvent {
int gamepadId; i32 gamepadId;
int button; i32 button;
}; };
struct GamepadAxisEvent { struct GamepadAxisEvent {
int gamepadId; i32 gamepadId;
int axis; i32 axis;
float value; f32 value;
}; };
// ============================================================================ // ============================================================================
// 触摸事件数据 // 触摸事件数据
// ============================================================================ // ============================================================================
struct TouchEvent { struct TouchEvent {
int touchId; i32 touchId;
Vec2 position; Vec2 pos;
}; };
// ============================================================================ // ============================================================================
// 自定义事件数据 // 自定义事件数据
// ============================================================================ // ============================================================================
struct CustomEvent { struct CustomEvent {
uint32_t id; u32 id;
void *data; void *data;
}; };
@ -131,7 +131,7 @@ struct CustomEvent {
// ============================================================================ // ============================================================================
struct Event { struct Event {
EventType type = EventType::None; EventType type = EventType::None;
double timestamp = 0.0; f64 timestamp = 0.0;
bool handled = false; bool handled = false;
// 事件数据联合体 // 事件数据联合体
@ -141,32 +141,32 @@ struct Event {
data; data;
// 便捷访问方法 // 便捷访问方法
bool isWindowEvent() const { bool window() const {
return type == EventType::WindowClose || type == EventType::WindowResize || return type == EventType::WindowClose || type == EventType::WindowResize ||
type == EventType::WindowFocus || type == EventType::WindowFocus ||
type == EventType::WindowLostFocus || type == EventType::WindowMoved; type == EventType::WindowLostFocus || type == EventType::WindowMoved;
} }
bool isKeyboardEvent() const { bool keyboard() const {
return type == EventType::KeyPressed || type == EventType::KeyReleased || return type == EventType::KeyPressed || type == EventType::KeyReleased ||
type == EventType::KeyRepeat; type == EventType::KeyRepeat;
} }
bool isMouseEvent() const { bool mouse() const {
return type == EventType::MouseButtonPressed || return type == EventType::MouseButtonPressed ||
type == EventType::MouseButtonReleased || type == EventType::MouseButtonReleased ||
type == EventType::MouseMoved || type == EventType::MouseScrolled; type == EventType::MouseMoved || type == EventType::MouseScrolled;
} }
// 静态工厂方法 // 静态工厂方法
static Event createWindowResize(int width, int height); static Event windowResize(i32 w, i32 h);
static Event createWindowClose(); static Event windowClose();
static Event createKeyPress(int keyCode, int scancode, int mods); static Event keyPress(i32 key, i32 scancode, i32 mods);
static Event createKeyRelease(int keyCode, int scancode, int mods); static Event keyRelease(i32 key, i32 scancode, i32 mods);
static Event createMouseButtonPress(int button, int mods, const Vec2 &pos); static Event mousePress(i32 btn, i32 mods, Vec2 pos);
static Event createMouseButtonRelease(int button, int mods, const Vec2 &pos); static Event mouseRelease(i32 btn, i32 mods, Vec2 pos);
static Event createMouseMove(const Vec2 &pos, const Vec2 &delta); static Event mouseMove(Vec2 pos, Vec2 delta);
static Event createMouseScroll(const Vec2 &offset, const Vec2 &pos); static Event mouseScroll(Vec2 offset, Vec2 pos);
}; };
} // namespace extra2d } // namespace extra2d

View File

@ -11,46 +11,46 @@ namespace extra2d {
// ============================================================================ // ============================================================================
// 事件监听器 ID // 事件监听器 ID
// ============================================================================ // ============================================================================
using ListenerId = uint64_t; using ListenerID = u64;
// ============================================================================ // ============================================================================
// 事件分发器 // 事件分发器
// ============================================================================ // ============================================================================
class EventDispatcher { class EventDispatcher {
public: public:
using EventCallback = std::function<void(Event &)>; using EventFn = Fn<void(Event &)>;
EventDispatcher(); EventDispatcher();
~EventDispatcher() = default; ~EventDispatcher() = default;
// 添加监听器 // 添加监听器
ListenerId addListener(EventType type, EventCallback callback); ListenerID on(EventType type, EventFn fn);
// 移除监听器 // 移除监听器
void removeListener(ListenerId id); void off(ListenerID id);
void removeAllListeners(EventType type); void offAll(EventType type);
void removeAllListeners(); void offAll();
// 分发事件 // 分发事件
void dispatch(Event &event); void dispatch(Event &event);
void dispatch(const 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 listenerCount(EventType type) const;
size_t getTotalListenerCount() const; size_t totalListeners() const;
private: private:
struct Listener { struct Listener {
ListenerId id; ListenerID id;
EventType type; EventType type;
EventCallback callback; EventFn fn;
}; };
std::unordered_map<EventType, std::vector<Listener>> listeners_; std::unordered_map<EventType, std::vector<Listener>> listeners_;
ListenerId nextId_; ListenerID nextId_;
}; };
} // namespace extra2d } // namespace extra2d

View File

@ -1,9 +1,9 @@
#pragma once #pragma once
#include <extra2d/core/ring_buffer.h>
#include <extra2d/core/types.h> #include <extra2d/core/types.h>
#include <extra2d/event/event.h> #include <extra2d/event/event.h>
#include <mutex> #include <mutex>
#include <queue>
namespace extra2d { namespace extra2d {
@ -12,12 +12,14 @@ namespace extra2d {
// ============================================================================ // ============================================================================
class EventQueue { class EventQueue {
public: public:
static constexpr size_t DEFAULT_CAPACITY = 1024;
EventQueue(); EventQueue();
~EventQueue() = default; ~EventQueue() = default;
// 添加事件到队列 // 添加事件到队列
void push(const Event &event); bool push(const Event &event);
void push(Event &&event); bool push(Event &&event);
// 从队列取出事件 // 从队列取出事件
bool poll(Event &event); bool poll(Event &event);
@ -31,10 +33,11 @@ public:
// 队列状态 // 队列状态
bool empty() const; bool empty() const;
size_t size() const; size_t size() const;
size_t capacity() const { return buffer_.capacity(); }
private: private:
std::queue<Event> queue_; RingBuffer<Event, DEFAULT_CAPACITY> buffer_;
mutable std::mutex mutex_; mutable std::mutex mutex_; // 用于peek和clear的互斥
}; };
} // namespace extra2d } // namespace extra2d

View File

@ -1,56 +1,43 @@
#pragma once #pragma once
// Easy2D v3.0 - 统一入口头文件 // Extra2D - 统一入口头文件
// 包含所有公共 API // 包含所有公共 API
// Core // Core
#include <extra2d/core/types.h>
#include <extra2d/core/string.h>
#include <extra2d/core/color.h> #include <extra2d/core/color.h>
#include <extra2d/core/math_types.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 // Platform
#include <extra2d/window/window.h> #include <extra2d/platform/glfw/glfw_window.h>
#include <extra2d/platform/keys.h>
// Graphics #include <extra2d/platform/window_module.h>
#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>
// Event // Event
#include <extra2d/event/event.h> #include <extra2d/event/event.h>
#include <extra2d/event/event_queue.h>
#include <extra2d/event/event_dispatcher.h> #include <extra2d/event/event_dispatcher.h>
#include <extra2d/event/input_codes.h> #include <extra2d/event/event_queue.h>
// Audio
#include <extra2d/audio/audio_engine.h>
#include <extra2d/audio/sound.h>
// Resource
#include <extra2d/resource/resource_manager.h>
// Utils // Utils
#include <extra2d/utils/logger.h>
#include <extra2d/utils/timer.h>
#include <extra2d/utils/data.h>
#include <extra2d/utils/random.h> #include <extra2d/utils/random.h>
#include <extra2d/utils/timer.h>
// Spatial // Services
#include <extra2d/spatial/spatial_index.h> #include <extra2d/services/asset_service.h>
#include <extra2d/spatial/quadtree.h> #include <extra2d/services/event_service.h>
#include <extra2d/spatial/spatial_hash.h> #include <extra2d/services/logger_service.h>
#include <extra2d/spatial/spatial_manager.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 // Application
#include <extra2d/app/application.h> #include <extra2d/app/application.h>

View File

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

View File

@ -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);
};
}

View File

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

View File

@ -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);
};
}

View File

@ -11,37 +11,37 @@ namespace extra2d {
class Random { class Random {
public: public:
/// 获取单例实例 /// 获取单例实例
static Random &getInstance(); static Random &get();
/// 设置随机种子 /// 设置随机种子
void setSeed(uint32 seed); void seed(u32 s);
/// 使用当前时间作为种子 /// 使用当前时间作为种子
void randomize(); void randomize();
/// 获取 [0, 1) 范围内的随机浮点数 /// 获取 [0, 1) 范围内的随机浮点数
float getFloat(); f32 randomF32();
/// 获取 [min, max] 范围内的随机浮点数 /// 获取 [min, max] 范围内的随机浮点数
float getFloat(float min, float max); f32 randomF32(f32 min, f32 max);
/// 获取 [0, max] 范围内的随机整数 /// 获取 [0, max] 范围内的随机整数
int getInt(int max); i32 randomI32(i32 max);
/// 获取 [min, 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] 范围内的随机数(用于方向) /// 获取 [-1, 1] 范围内的随机数(用于方向)
float getSigned(); f32 signedF32();
private: private:
Random(); Random();
@ -59,27 +59,27 @@ private:
// ============================================================================ // ============================================================================
/// 获取 [0, 1) 范围内的随机浮点数 /// 获取 [0, 1) 范围内的随机浮点数
inline float randomFloat() { return Random::getInstance().getFloat(); } inline f32 randF32() { return Random::get().randomF32(); }
/// 获取 [min, max] 范围内的随机浮点数 /// 获取 [min, max] 范围内的随机浮点数
inline float randomFloat(float min, float max) { inline f32 randF32(f32 min, f32 max) {
return Random::getInstance().getFloat(min, max); return Random::get().randomF32(min, max);
} }
/// 获取 [0, 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] 范围内的随机整数 /// 获取 [min, max] 范围内的随机整数
inline int randomInt(int min, int max) { inline i32 randI32(i32 min, i32 max) {
return Random::getInstance().getInt(min, 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) { inline bool randBool(f32 probability) {
return Random::getInstance().getBool(probability); return Random::get().boolean(probability);
} }
} // namespace extra2d } // namespace extra2d

View File

@ -16,12 +16,12 @@ public:
using Clock = std::chrono::steady_clock; using Clock = std::chrono::steady_clock;
using TimePoint = Clock::time_point; using TimePoint = Clock::time_point;
using Duration = Clock::duration; 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 如果触发了回调 /// 更新计时器,返回 true 如果触发了回调
bool update(float deltaTime); bool update(f32 dt);
/// 重置计时器 /// 重置计时器
void reset(); void reset();
@ -36,27 +36,27 @@ public:
void cancel(); 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 /// 获取唯一ID
uint32 getId() const { return id_; } u32 id() const { return id_; }
private: private:
uint32 id_; u32 id_;
float interval_; f32 interval_;
float elapsed_; f32 elapsed_;
bool repeat_; bool repeat_;
bool paused_; bool paused_;
bool valid_; bool valid_;
Callback callback_; Fn fn_;
static uint32 nextId_; static u32 nextId_;
}; };
// ============================================================================ // ============================================================================
@ -68,32 +68,32 @@ public:
~TimerManager() = default; ~TimerManager() = default;
/// 创建单次计时器返回计时器ID /// 创建单次计时器返回计时器ID
uint32 addTimer(float delay, Timer::Callback callback); u32 add(f32 delay, Timer::Fn fn);
/// 创建重复计时器返回计时器ID /// 创建重复计时器返回计时器ID
uint32 addRepeatingTimer(float interval, Timer::Callback callback); u32 addRepeat(f32 interval, Timer::Fn fn);
/// 取消指定ID的计时器 /// 取消指定ID的计时器
void cancelTimer(uint32 timerId); void cancel(u32 timerId);
/// 暂停指定ID的计时器 /// 暂停指定ID的计时器
void pauseTimer(uint32 timerId); void pause(u32 timerId);
/// 恢复指定ID的计时器 /// 恢复指定ID的计时器
void resumeTimer(uint32 timerId); void resume(u32 timerId);
/// 更新所有计时器(每帧调用) /// 更新所有计时器(每帧调用)
void update(float deltaTime); void update(f32 dt);
/// 清除所有计时器 /// 清除所有计时器
void clear(); void clear();
/// 获取计时器数量 /// 获取计时器数量
size_t getTimerCount() const { return timers_.size(); } size_t count() const { return timers_.size(); }
private: private:
std::map<uint32, std::unique_ptr<Timer>> timers_; std::map<u32, std::unique_ptr<Timer>> timers_;
std::vector<uint32> timersToRemove_; std::vector<u32> timersToRemove_;
}; };
} // namespace extra2d } // namespace extra2d

View File

@ -1,8 +1,8 @@
#pragma once #pragma once
#include <extra2d/core/types.h> #include <extra2d/core/types.h>
#include <extra2d/core/string.h>
#include <extra2d/core/math_types.h> #include <extra2d/core/math_types.h>
#include <string>
#include <functional> #include <functional>
#include <SDL.h> #include <SDL.h>

File diff suppressed because it is too large Load Diff

View File

@ -1,68 +1,56 @@
#include <extra2d/app/application.h> #include <extra2d/app/application.h>
#include <extra2d/audio/audio_engine.h> #include <extra2d/core/registry.h>
#include <extra2d/event/event_dispatcher.h> #include <extra2d/platform/glfw/glfw_window.h>
#include <extra2d/event/event_queue.h> #include <extra2d/platform/window_module.h>
#include <extra2d/graphics/renderer.h> #include <extra2d/services/event_service.h>
#include <extra2d/platform/input.h> #include <extra2d/services/logger_service.h>
#include <extra2d/utils/logger.h> #include <extra2d/services/timer_service.h>
#include <extra2d/window/window.h>
#include <chrono>
#include <thread>
namespace extra2d { 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; using namespace std::chrono;
auto now = steady_clock::now(); auto now = steady_clock::now();
auto duration = now.time_since_epoch(); 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; static Application instance;
return 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_) { if (initialized_) {
E2D_LOG_WARN("Application already initialized");
return true; return true;
} }
config_ = config; // 初始化所有模块(拓扑排序)
// 服务通过 E2D_AUTO_REGISTER_SERVICE 宏自动注册
window_ = makeUnique<Window>(); if (!Registry::instance().init()) {
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");
return false; return false;
} }
renderer_ = makeUnique<Renderer>(); // 初始化所有服务
if (!renderer_->init(window_.get())) { ServiceLocator::instance().init();
E2D_LOG_ERROR("Failed to initialize renderer");
window_->destroy();
return false;
}
eventQueue_ = makeUnique<EventQueue>();
eventDispatcher_ = makeUnique<EventDispatcher>();
AudioEngine::getInstance().initialize();
initialized_ = true; initialized_ = true;
running_ = true; running_ = true;
E2D_LOG_INFO("Application initialized successfully");
return true; return true;
} }
@ -70,80 +58,73 @@ void Application::shutdown() {
if (!initialized_) if (!initialized_)
return; return;
E2D_LOG_INFO("Shutting down application..."); ServiceLocator::instance().shutdown();
ServiceLocator::instance().clear();
AudioEngine::getInstance().shutdown(); Registry::instance().shutdown();
Registry::instance().clear();
eventDispatcher_.reset();
eventQueue_.reset();
if (renderer_) {
renderer_->shutdown();
renderer_.reset();
}
if (window_) {
window_->destroy();
window_.reset();
}
initialized_ = false; initialized_ = false;
running_ = false; running_ = false;
E2D_LOG_INFO("Application shutdown complete");
} }
void Application::run() { void Application::run() {
if (!initialized_) { if (!initialized_)
E2D_LOG_ERROR("Application not initialized"); return;
auto *winMod = get<WindowModule>();
if (!winMod || !winMod->win())
return; return;
}
lastFrameTime_ = getTimeSeconds(); lastFrameTime_ = getTimeSeconds();
while (running_ && !window_->shouldClose()) { while (running_ && !winMod->win()->shouldClose()) {
mainLoop(); mainLoop();
} }
} }
void Application::quit() { void Application::quit() {
shouldQuit_ = true;
running_ = false; running_ = false;
} }
void Application::pause() { void Application::pause() {
if (!paused_) { if (!paused_) {
paused_ = true; paused_ = true;
E2D_LOG_INFO("Application paused"); ServiceLocator::instance().pause();
} }
} }
void Application::resume() { void Application::resume() {
if (paused_) { if (paused_) {
paused_ = false; paused_ = false;
ServiceLocator::instance().resume();
lastFrameTime_ = getTimeSeconds(); lastFrameTime_ = getTimeSeconds();
E2D_LOG_INFO("Application resumed");
} }
} }
void Application::mainLoop() { void Application::mainLoop() {
double currentTime = getTimeSeconds(); f64 currentTime = getTimeSeconds();
deltaTime_ = static_cast<float>(currentTime - lastFrameTime_); dt_ = static_cast<f32>(currentTime - lastFrameTime_);
lastFrameTime_ = currentTime; lastFrameTime_ = currentTime;
totalTime_ += deltaTime_; totalTime_ += dt_;
frameCount_++; frameCount_++;
fpsTimer_ += deltaTime_; fpsTimer_ += dt_;
if (fpsTimer_ >= 1.0f) { if (fpsTimer_ >= 1.0f) {
currentFps_ = frameCount_; fps_ = frameCount_;
frameCount_ = 0; frameCount_ = 0;
fpsTimer_ -= 1.0f; fpsTimer_ -= 1.0f;
} }
window_->pollEvents(); auto *winMod = get<WindowModule>();
if (winMod && winMod->win()) {
winMod->win()->poll();
}
if (eventDispatcher_ && eventQueue_) { auto eventService = ServiceLocator::instance().get<IEventService>();
eventDispatcher_->processQueue(*eventQueue_); if (eventService) {
eventService->process();
} }
if (!paused_) { if (!paused_) {
@ -151,39 +132,21 @@ void Application::mainLoop() {
} }
render(); 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() { void Application::render() {
if (!renderer_) { auto *winMod = get<WindowModule>();
if (!winMod || !winMod->win())
return; return;
}
renderer_->beginFrame(Color::Black); winMod->win()->swap();
// 渲染内容可以在这里添加
renderer_->endFrame();
window_->swapBuffers();
} }
Input &Application::input() { return *window_->getInput(); } GLFWWindow *Application::window() {
auto *winMod = get<WindowModule>();
EventQueue &Application::eventQueue() { return *eventQueue_; } return winMod ? winMod->win() : nullptr;
}
EventDispatcher &Application::eventDispatcher() { return *eventDispatcher_; }
} // namespace extra2d } // namespace extra2d

View File

@ -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);
}
}

View File

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

View File

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

View File

@ -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);
}
}

View File

@ -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());
}
}

View File

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

View File

@ -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);
});
}
}

View File

@ -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);
}
}
}
}

View File

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

View File

@ -2,55 +2,55 @@
namespace extra2d { namespace extra2d {
Event Event::createWindowResize(int width, int height) { Event Event::windowResize(i32 w, i32 h) {
Event event; Event event;
event.type = EventType::WindowResize; event.type = EventType::WindowResize;
event.data = WindowResizeEvent{width, height}; event.data = WindowResizeEvent{w, h};
return event; return event;
} }
Event Event::createWindowClose() { Event Event::windowClose() {
Event event; Event event;
event.type = EventType::WindowClose; event.type = EventType::WindowClose;
return event; return event;
} }
Event Event::createKeyPress(int keyCode, int scancode, int mods) { Event Event::keyPress(i32 key, i32 scancode, i32 mods) {
Event event; Event event;
event.type = EventType::KeyPressed; event.type = EventType::KeyPressed;
event.data = KeyEvent{keyCode, scancode, mods}; event.data = KeyEvent{key, scancode, mods};
return event; return event;
} }
Event Event::createKeyRelease(int keyCode, int scancode, int mods) { Event Event::keyRelease(i32 key, i32 scancode, i32 mods) {
Event event; Event event;
event.type = EventType::KeyReleased; event.type = EventType::KeyReleased;
event.data = KeyEvent{keyCode, scancode, mods}; event.data = KeyEvent{key, scancode, mods};
return event; return event;
} }
Event Event::createMouseButtonPress(int button, int mods, const Vec2 &pos) { Event Event::mousePress(i32 btn, i32 mods, Vec2 pos) {
Event event; Event event;
event.type = EventType::MouseButtonPressed; event.type = EventType::MouseButtonPressed;
event.data = MouseButtonEvent{button, mods, pos}; event.data = MouseButtonEvent{btn, mods, pos};
return event; return event;
} }
Event Event::createMouseButtonRelease(int button, int mods, const Vec2 &pos) { Event Event::mouseRelease(i32 btn, i32 mods, Vec2 pos) {
Event event; Event event;
event.type = EventType::MouseButtonReleased; event.type = EventType::MouseButtonReleased;
event.data = MouseButtonEvent{button, mods, pos}; event.data = MouseButtonEvent{btn, mods, pos};
return event; return event;
} }
Event Event::createMouseMove(const Vec2 &pos, const Vec2 &delta) { Event Event::mouseMove(Vec2 pos, Vec2 delta) {
Event event; Event event;
event.type = EventType::MouseMoved; event.type = EventType::MouseMoved;
event.data = MouseMoveEvent{pos, delta}; event.data = MouseMoveEvent{pos, delta};
return event; return event;
} }
Event Event::createMouseScroll(const Vec2 &offset, const Vec2 &pos) { Event Event::mouseScroll(Vec2 offset, Vec2 pos) {
Event event; Event event;
event.type = EventType::MouseScrolled; event.type = EventType::MouseScrolled;
event.data = MouseScrollEvent{offset, pos}; event.data = MouseScrollEvent{offset, pos};

View File

@ -5,14 +5,13 @@ namespace extra2d {
EventDispatcher::EventDispatcher() : nextId_(1) {} EventDispatcher::EventDispatcher() : nextId_(1) {}
ListenerId EventDispatcher::addListener(EventType type, ListenerID EventDispatcher::on(EventType type, EventFn fn) {
EventCallback callback) { ListenerID id = nextId_++;
ListenerId id = nextId_++; listeners_[type].push_back({id, type, fn});
listeners_[type].push_back({id, type, callback});
return id; return id;
} }
void EventDispatcher::removeListener(ListenerId id) { void EventDispatcher::off(ListenerID id) {
for (auto &[type, listeners] : listeners_) { for (auto &[type, listeners] : listeners_) {
auto it = std::remove_if(listeners.begin(), listeners.end(), auto it = std::remove_if(listeners.begin(), listeners.end(),
[id](const Listener &l) { return l.id == id; }); [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); listeners_.erase(type);
} }
void EventDispatcher::removeAllListeners() { listeners_.clear(); } void EventDispatcher::offAll() { listeners_.clear(); }
void EventDispatcher::dispatch(Event &event) { void EventDispatcher::dispatch(Event &event) {
auto it = listeners_.find(event.type); auto it = listeners_.find(event.type);
@ -35,7 +34,7 @@ void EventDispatcher::dispatch(Event &event) {
for (auto &listener : it->second) { for (auto &listener : it->second) {
if (event.handled) if (event.handled)
break; break;
listener.callback(event); listener.fn(event);
} }
} }
} }
@ -45,19 +44,19 @@ void EventDispatcher::dispatch(const Event &event) {
dispatch(mutableEvent); dispatch(mutableEvent);
} }
void EventDispatcher::processQueue(EventQueue &queue) { void EventDispatcher::process(EventQueue &queue) {
Event event; Event event;
while (queue.poll(event)) { while (queue.poll(event)) {
dispatch(event); dispatch(event);
} }
} }
size_t EventDispatcher::getListenerCount(EventType type) const { size_t EventDispatcher::listenerCount(EventType type) const {
auto it = listeners_.find(type); auto it = listeners_.find(type);
return (it != listeners_.end()) ? it->second.size() : 0; return (it != listeners_.end()) ? it->second.size() : 0;
} }
size_t EventDispatcher::getTotalListenerCount() const { size_t EventDispatcher::totalListeners() const {
size_t count = 0; size_t count = 0;
for (const auto &[type, listeners] : listeners_) { for (const auto &[type, listeners] : listeners_) {
count += listeners.size(); count += listeners.size();

View File

@ -4,50 +4,42 @@ namespace extra2d {
EventQueue::EventQueue() = default; EventQueue::EventQueue() = default;
void EventQueue::push(const Event &event) { bool EventQueue::push(const Event &event) {
std::lock_guard<std::mutex> lock(mutex_); return buffer_.push(event);
queue_.push(event);
} }
void EventQueue::push(Event &&event) { bool EventQueue::push(Event &&event) {
std::lock_guard<std::mutex> lock(mutex_); return buffer_.push(std::move(event));
queue_.push(std::move(event));
} }
bool EventQueue::poll(Event &event) { bool EventQueue::poll(Event &event) {
std::lock_guard<std::mutex> lock(mutex_); return buffer_.pop(event);
if (queue_.empty()) {
return false;
}
event = queue_.front();
queue_.pop();
return true;
} }
bool EventQueue::peek(Event &event) const { bool EventQueue::peek(Event &event) const {
std::lock_guard<std::mutex> lock(mutex_); std::lock_guard<std::mutex> lock(mutex_);
if (queue_.empty()) { if (buffer_.empty()) {
return false; return false;
} }
event = queue_.front(); // 环形缓冲区不支持peek这里简化处理
return true; // 实际应用中可能需要双缓冲或其他机制
return false;
} }
void EventQueue::clear() { void EventQueue::clear() {
std::lock_guard<std::mutex> lock(mutex_); std::lock_guard<std::mutex> lock(mutex_);
while (!queue_.empty()) { Event event;
queue_.pop(); while (buffer_.pop(event)) {
// 持续弹出直到为空
} }
} }
bool EventQueue::empty() const { bool EventQueue::empty() const {
std::lock_guard<std::mutex> lock(mutex_); return buffer_.empty();
return queue_.empty();
} }
size_t EventQueue::size() const { size_t EventQueue::size() const {
std::lock_guard<std::mutex> lock(mutex_); return buffer_.size();
return queue_.size();
} }
} // namespace extra2d } // namespace extra2d

File diff suppressed because one or more lines are too long

View File

@ -1,6 +1,6 @@
#include <extra2d/graphics/opengl/gl_font_atlas.h> #include <extra2d/graphics/opengl/gl_font_atlas.h>
#include <extra2d/core/string.h>
#include <extra2d/utils/logger.h> #include <extra2d/utils/logger.h>
#include <simdutf/simdutf.h>
#include <fstream> #include <fstream>
#define STB_TRUETYPE_IMPLEMENTATION #define STB_TRUETYPE_IMPLEMENTATION
#include <stb/stb_truetype.h> #include <stb/stb_truetype.h>
@ -75,7 +75,10 @@ Vec2 GLFontAtlas::measureText(const std::string &text) {
float height = getAscent() - getDescent(); float height = getAscent() - getDescent();
float currentWidth = 0.0f; 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') { if (codepoint == '\n') {
width = std::max(width, currentWidth); width = std::max(width, currentWidth);
currentWidth = 0.0f; currentWidth = 0.0f;

View File

@ -2,7 +2,7 @@
#include <algorithm> #include <algorithm>
#include <cmath> #include <cmath>
#include <cstring> #include <cstring>
#include <extra2d/core/string.h> #include <simdutf/simdutf.h>
#include <extra2d/graphics/gpu_context.h> #include <extra2d/graphics/gpu_context.h>
#include <extra2d/graphics/opengl/gl_font_atlas.h> #include <extra2d/graphics/opengl/gl_font_atlas.h>
#include <extra2d/graphics/opengl/gl_renderer.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; std::vector<GLSpriteBatch::SpriteData> sprites;
sprites.reserve(text.size()); // 预分配空间 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') { if (codepoint == '\n') {
cursorX = x; cursorX = x;
cursorY += font.getLineHeight(); cursorY += font.getLineHeight();

View File

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

View File

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

View File

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

View File

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

View File

@ -3,52 +3,100 @@
namespace extra2d { namespace extra2d {
/**
* @brief
*
* 使
*/
Random::Random() : floatDist_(0.0f, 1.0f) { Random::Random() : floatDist_(0.0f, 1.0f) {
// 使用当前时间作为默认种子 // 使用当前时间作为默认种子
randomize(); randomize();
} }
Random &Random::getInstance() { /**
* @brief Random单例实例
* @return Random单例的引用
*/
Random &Random::get() {
static Random instance; static Random instance;
return 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() { void Random::randomize() {
auto now = std::chrono::high_resolution_clock::now(); auto now = std::chrono::high_resolution_clock::now();
auto time = now.time_since_epoch().count(); 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) { if (min >= max) {
return min; return min;
} }
return min + floatDist_(generator_) * (max - 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) { if (max <= 0) {
return 0; return 0;
} }
std::uniform_int_distribution<int> dist(0, max); std::uniform_int_distribution<i32> dist(0, max);
return dist(generator_); 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) { if (min >= max) {
return min; return min;
} }
std::uniform_int_distribution<int> dist(min, max); std::uniform_int_distribution<i32> dist(min, max);
return dist(generator_); 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) { if (probability <= 0.0f) {
return false; return false;
} }
@ -58,11 +106,19 @@ bool Random::getBool(float probability) {
return floatDist_(generator_) < 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; 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 } // namespace extra2d

View File

@ -3,24 +3,24 @@
namespace extra2d { 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), : interval_(interval), elapsed_(0.0f), repeat_(repeat), paused_(false),
valid_(true), callback_(std::move(callback)) { valid_(true), fn_(std::move(fn)) {
id_ = nextId_++; id_ = nextId_++;
} }
bool Timer::update(float deltaTime) { bool Timer::update(f32 dt) {
if (!valid_ || paused_) { if (!valid_ || paused_) {
return false; return false;
} }
elapsed_ += deltaTime; elapsed_ += dt;
if (elapsed_ >= interval_) { if (elapsed_ >= interval_) {
if (callback_) { if (fn_) {
callback_(); fn_();
} }
if (repeat_) { if (repeat_) {
@ -47,33 +47,28 @@ void Timer::resume() { paused_ = false; }
void Timer::cancel() { valid_ = false; } void Timer::cancel() { valid_ = false; }
float Timer::getRemaining() const { f32 Timer::remaining() const {
if (!valid_ || paused_) { if (!valid_ || paused_) {
return 0.0f; return 0.0f;
} }
return std::max(0.0f, interval_ - elapsed_); return std::max(0.0f, interval_ - elapsed_);
} }
// ============================================================================ u32 TimerManager::add(f32 delay, Timer::Fn fn) {
// TimerManager 实现 auto timer = std::make_unique<Timer>(delay, false, std::move(fn));
// ============================================================================ u32 id = timer->id();
uint32 TimerManager::addTimer(float delay, Timer::Callback callback) {
auto timer = std::make_unique<Timer>(delay, false, std::move(callback));
uint32 id = timer->getId();
timers_.emplace(id, std::move(timer)); timers_.emplace(id, std::move(timer));
return id; return id;
} }
uint32 TimerManager::addRepeatingTimer(float interval, u32 TimerManager::addRepeat(f32 interval, Timer::Fn fn) {
Timer::Callback callback) { auto timer = std::make_unique<Timer>(interval, true, std::move(fn));
auto timer = std::make_unique<Timer>(interval, true, std::move(callback)); u32 id = timer->id();
uint32 id = timer->getId();
timers_.emplace(id, std::move(timer)); timers_.emplace(id, std::move(timer));
return id; return id;
} }
void TimerManager::cancelTimer(uint32 timerId) { void TimerManager::cancel(u32 timerId) {
auto it = timers_.find(timerId); auto it = timers_.find(timerId);
if (it != timers_.end()) { if (it != timers_.end()) {
it->second->cancel(); 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); auto it = timers_.find(timerId);
if (it != timers_.end()) { if (it != timers_.end()) {
it->second->pause(); it->second->pause();
} }
} }
void TimerManager::resumeTimer(uint32 timerId) { void TimerManager::resume(u32 timerId) {
auto it = timers_.find(timerId); auto it = timers_.find(timerId);
if (it != timers_.end()) { if (it != timers_.end()) {
it->second->resume(); it->second->resume();
} }
} }
void TimerManager::update(float deltaTime) { void TimerManager::update(f32 dt) {
timersToRemove_.clear(); timersToRemove_.clear();
for (auto &[id, timer] : timers_) { for (auto &[id, timer] : timers_) {
timer->update(deltaTime); timer->update(dt);
if (!timer->isValid()) { if (!timer->valid()) {
timersToRemove_.push_back(id); timersToRemove_.push_back(id);
} }
} }
for (uint32 id : timersToRemove_) { for (u32 id : timersToRemove_) {
timers_.erase(id); timers_.erase(id);
} }
} }

View File

@ -88,10 +88,6 @@ define_extra2d_engine()
-- 示例程序目标(作为子项目) -- 示例程序目标(作为子项目)
if is_config("examples","true") then if is_config("examples","true") then
includes("examples/hello_world", {rootdir = "examples/hello_world"}) 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 end
-- ============================================== -- ==============================================