diff --git a/Extra2D/include/extra2d/asset/asset.h b/Extra2D/include/extra2d/asset/asset.h new file mode 100644 index 0000000..b6e929e --- /dev/null +++ b/Extra2D/include/extra2d/asset/asset.h @@ -0,0 +1,477 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace extra2d { + +// --------------------------------------------------------------------------- +// Asset - 资源基类 +// --------------------------------------------------------------------------- + +/** + * @brief 资源基类 + * + * 所有资源类型的基类,继承自 enable_shared_from_this 支持自动引用计数。 + * 提供资源的基本属性和生命周期管理接口。 + */ +class Asset : public std::enable_shared_from_this { +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 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(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 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 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 data); + + /** + * @brief 释放字体数据 + */ + void release(); + +private: + std::vector data_; + class Impl; + Unique 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 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(sampleRate) * channels * (bitsPerSample / 8); + if (bytesPerSecond > 0) { + duration_ = static_cast(data_.size()) / + static_cast(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 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 data) { + data_ = std::move(data); + setState(AssetState::Loaded); + } + + /** + * @brief 释放数据 + */ + void release() { + data_.clear(); + setState(AssetState::Unloaded); + } + +private: + std::vector data_; +}; + +} // namespace extra2d diff --git a/Extra2D/include/extra2d/asset/asset_cache.h b/Extra2D/include/extra2d/asset/asset_cache.h new file mode 100644 index 0000000..4fb5d14 --- /dev/null +++ b/Extra2D/include/extra2d/asset/asset_cache.h @@ -0,0 +1,263 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace extra2d { + +// --------------------------------------------------------------------------- +// CacheEntry - 缓存条目 +// --------------------------------------------------------------------------- + +/** + * @brief 缓存条目结构 + * + * 存储资源引用和访问信息,用于LRU缓存管理。 + */ +struct CacheEntry { + Ref asset; + std::chrono::steady_clock::time_point lastAccess; + size_t accessCount = 0; + + /** + * @brief 构造缓存条目 + * @param a 资源引用 + */ + explicit CacheEntry(Ref 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 + AssetHandle add(Ref asset) { + static_assert(std::is_base_of_v, + "T must derive from Asset"); + + if (!asset) { + return AssetHandle(); + } + + std::unique_lock 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(std::static_pointer_cast(asset)), + it + }; + + ++stats_.count; + + if (limit_ > 0 && bytes_ > limit_) { + evict(); + } + + return AssetHandle(id, Weak(asset)); + } + + /** + * @brief 从缓存获取资源 + * @tparam T 资源类型 + * @param id 资源ID + * @return 资源句柄 + */ + template + AssetHandle get(const AssetID& id) { + static_assert(std::is_base_of_v, + "T must derive from Asset"); + + std::unique_lock lock(mutex_); + + auto it = entries_.find(id); + if (it == entries_.end()) { + ++stats_.misses; + return AssetHandle(); + } + + 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(it->second.entry->asset); + return AssetHandle(id, Weak(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 entry; + std::list::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 entries_; + std::list lruList_; + + size_t limit_ = 0; + size_t bytes_ = 0; + mutable CacheStats stats_; +}; + +} diff --git a/Extra2D/include/extra2d/asset/asset_handle.h b/Extra2D/include/extra2d/asset/asset_handle.h new file mode 100644 index 0000000..7bdce63 --- /dev/null +++ b/Extra2D/include/extra2d/asset/asset_handle.h @@ -0,0 +1,304 @@ +#pragma once + +#include +#include +#include + +namespace extra2d { + +// --------------------------------------------------------------------------- +// AssetHandleBase - 资源句柄非模板基类 +// --------------------------------------------------------------------------- + +/** + * @brief 资源句柄非模板基类 + * + * 用于类型擦除,允许在容器中存储不同类型的句柄。 + */ +class AssetHandleBase { +public: + AssetHandleBase() = default; + + /** + * @brief 从资源ID和弱引用构造 + * @param id 资源ID + * @param ref 资源弱引用 + */ + AssetHandleBase(const AssetID& id, Weak ref) + : id_(id), cacheRef_(std::move(ref)) {} + + /** + * @brief 从强引用构造 + * @param ptr 资源强引用 + */ + explicit AssetHandleBase(Ref ptr) + : id_(ptr ? ptr->id() : AssetID()), + cacheRef_(ptr) {} + + /** + * @brief 检查句柄是否有效 + * @return 有效返回 true + */ + bool valid() const { + return !cacheRef_.expired(); + } + + /** + * @brief 获取资源强引用 + * @return 资源强引用 + */ + Ref 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 cacheRef_; +}; + +// --------------------------------------------------------------------------- +// AssetHandle - 资源句柄 +// --------------------------------------------------------------------------- + +/** + * @brief 资源句柄模板类 + * + * 使用强类型句柄替代裸指针,提供类型安全和自动生命周期管理。 + * 内部使用 weak_ptr 弱引用,不阻止资源回收。 + * + * 特性: + * - 类型安全:编译期检查资源类型 + * - 自动生命周期:资源无引用时自动回收 + * - 弱引用:不阻止缓存清理 + * - 空安全:使用前检查 valid() + * + * @tparam T 资源类型,必须继承自 Asset + */ +template +class AssetHandle : public AssetHandleBase { + static_assert(std::is_base_of_v, + "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 ref) + : AssetHandleBase(id, std::move(ref)) {} + + /** + * @brief 从强引用构造 + * @param ptr 资源强引用 + */ + explicit AssetHandle(Ref ptr) + : AssetHandleBase(ptr) {} + + /** + * @brief 获取资源强引用 + * @return 资源强引用,如果资源已被回收返回 nullptr + */ + Ref get() const { + return std::static_pointer_cast(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 +struct AssetLoadResult { + AssetHandle 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 handle) { + return { std::move(handle), AssetState::Loaded, "" }; + } + + /** + * @brief 创建失败结果 + * @param error 错误信息 + * @return 加载结果 + */ + static AssetLoadResult err(const std::string& error) { + return { AssetHandle(), AssetState::Failed, error }; + } + + /** + * @brief 创建加载中结果 + * @param handle 资源句柄(可能为空) + * @return 加载结果 + */ + static AssetLoadResult pending(AssetHandle handle = {}) { + return { std::move(handle), AssetState::Loading, "" }; + } +}; + +// --------------------------------------------------------------------------- +// AssetLoadCallback - 资源加载回调 +// --------------------------------------------------------------------------- + +/** + * @brief 资源加载回调类型 + * @tparam T 资源类型 + */ +template +using AssetLoadCallback = Fn)>; + +/** + * @brief 资源加载结果回调类型 + * @tparam T 资源类型 + */ +template +using AssetLoadResultCallback = Fn)>; + +} diff --git a/Extra2D/include/extra2d/asset/asset_loader.h b/Extra2D/include/extra2d/asset/asset_loader.h new file mode 100644 index 0000000..158a5ee --- /dev/null +++ b/Extra2D/include/extra2d/asset/asset_loader.h @@ -0,0 +1,312 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace extra2d { + +// --------------------------------------------------------------------------- +// AssetLoaderBase - 资源加载器非模板基类 +// --------------------------------------------------------------------------- + +/** + * @brief 资源加载器非模板基类 + * + * 用于类型擦除,允许在容器中存储不同类型的加载器。 + */ +class AssetLoaderBase { +public: + virtual ~AssetLoaderBase() = default; + + /** + * @brief 从文件加载资源(返回 Asset 基类指针) + * @param path 文件路径 + * @return 资源实例 + */ + virtual Ref loadBase(const std::string& path) = 0; + + /** + * @brief 从内存数据加载资源 + * @param data 数据指针 + * @param size 数据大小 + * @return 资源实例 + */ + virtual Ref 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 extensions() const = 0; +}; + +// --------------------------------------------------------------------------- +// AssetLoader - 资源加载器接口 +// --------------------------------------------------------------------------- + +/** + * @brief 资源加载器接口模板 + * + * 使用策略模式支持不同资源类型的加载。 + * 每种资源类型可以实现自己的加载器。 + * + * @tparam T 资源类型 + */ +template +class AssetLoader : public AssetLoaderBase { + static_assert(std::is_base_of_v, + "T must derive from Asset"); + +public: + virtual ~AssetLoader() = default; + + /** + * @brief 从文件加载资源 + * @param path 文件路径 + * @return 资源实例,失败返回 nullptr + */ + virtual Ref load(const std::string& path) = 0; + + /** + * @brief 从内存数据加载资源 + * @param data 数据指针 + * @param size 数据大小 + * @return 资源实例,失败返回 nullptr + */ + virtual Ref loadFromMemory(const u8* data, size_t size) = 0; + + Ref loadBase(const std::string& path) override { + return load(path); + } + + Ref 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 { +public: + TextureLoader(); + ~TextureLoader() override; + + Ref load(const std::string& path) override; + Ref 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 extensions() const override; + + /** + * @brief 设置期望的通道数 + * @param channels 通道数(1-4),0表示自动 + */ + void setDesiredChannels(int channels); + + /** + * @brief 获取期望的通道数 + * @return 通道数 + */ + int desiredChannels() const { return desiredChannels_; } + +private: + int desiredChannels_ = 4; +}; + +// --------------------------------------------------------------------------- +// FontLoader - 字体加载器 +// --------------------------------------------------------------------------- + +/** + * @brief 字体加载器 + * + * 加载 TrueType 字体文件(.ttf, .otf)。 + */ +class FontLoader : public AssetLoader { +public: + Ref load(const std::string& path) override; + Ref 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 extensions() const override; +}; + +// --------------------------------------------------------------------------- +// ShaderLoader - 着色器加载器 +// --------------------------------------------------------------------------- + +/** + * @brief 着色器加载器 + * + * 加载着色器源文件,支持以下格式: + * - .vert/.frag: 分离的顶点/片段着色器 + * - .glsl: 合并的着色器文件(使用标记分隔) + */ +class ShaderLoader : public AssetLoader { +public: + Ref load(const std::string& path) override; + Ref 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 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 { +public: + Ref load(const std::string& path) override; + Ref 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 extensions() const override; + +private: + /** + * @brief 加载 WAV 格式音频 + * @param data 数据指针 + * @param size 数据大小 + * @return 音频资源 + */ + Ref loadWav(const u8* data, size_t size); +}; + +// --------------------------------------------------------------------------- +// DataLoader - 通用数据加载器 +// --------------------------------------------------------------------------- + +/** + * @brief 通用数据加载器 + * + * 加载任意二进制数据文件。 + */ +class DataLoader : public AssetLoader { +public: + Ref load(const std::string& path) override; + Ref 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 extensions() const override; +}; + +// --------------------------------------------------------------------------- +// AssetLoaderFactory - 加载器工厂 +// --------------------------------------------------------------------------- + +/** + * @brief 加载器工厂 + * + * 根据资源类型或文件扩展名创建对应的加载器。 + * 使用模板方法返回具体类型的加载器。 + */ +class AssetLoaderFactory { +public: + /** + * @brief 根据资源类型创建纹理加载器 + * @return 纹理加载器实例 + */ + static Unique createTextureLoader() { + return ptr::makeUnique(); + } + + /** + * @brief 根据资源类型创建字体加载器 + * @return 字体加载器实例 + */ + static Unique createFontLoader() { + return ptr::makeUnique(); + } + + /** + * @brief 根据资源类型创建着色器加载器 + * @return 着色器加载器实例 + */ + static Unique createShaderLoader() { + return ptr::makeUnique(); + } + + /** + * @brief 根据资源类型创建音频加载器 + * @return 音频加载器实例 + */ + static Unique createAudioLoader() { + return ptr::makeUnique(); + } + + /** + * @brief 根据资源类型创建数据加载器 + * @return 数据加载器实例 + */ + static Unique createDataLoader() { + return ptr::makeUnique(); + } + + /** + * @brief 根据文件扩展名获取资源类型 + * @param extension 文件扩展名(包含点,如 ".png") + * @return 资源类型,无法识别返回 AssetType::Unknown + */ + static AssetType getTypeByExtension(const std::string& extension); +}; + +} diff --git a/Extra2D/include/extra2d/asset/asset_pack.h b/Extra2D/include/extra2d/asset/asset_pack.h new file mode 100644 index 0000000..a2801bb --- /dev/null +++ b/Extra2D/include/extra2d/asset/asset_pack.h @@ -0,0 +1,399 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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 read(const AssetID& id); + + /** + * @brief 读取资源数据(自动解压/解密) + * @param path 资源路径 + * @return 资源数据,失败返回空 + */ + std::vector read(const std::string& path); + + /** + * @brief 读取原始资源数据(不解压/不解密) + * @param id 资源ID + * @return 原始资源数据 + */ + std::vector readRaw(const AssetID& id); + + /** + * @brief 获取所有资源ID + * @return 资源ID列表 + */ + std::vector 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 entries_; + DataPipe pipe_; + + /** + * @brief 读取并解析头部 + * @return 成功返回 true + */ + bool readHeader(); + + /** + * @brief 读取并解析索引表 + * @return 成功返回 true + */ + bool readIndex(); + + /** + * @brief 读取条目数据 + * @param entry 条目信息 + * @return 原始数据 + */ + std::vector 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 read(const AssetID& id); + + /** + * @brief 读取资源数据 + * @param path 资源路径 + * @return 资源数据 + */ + std::vector 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 allAssets() const; + + /** + * @brief 获取已挂载的资源包路径列表 + * @return 路径列表 + */ + std::vector mountedPacks() const; + +private: + std::vector> 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& data); + + /** + * @brief 添加资源(移动语义) + * @param path 资源路径 + * @param data 资源数据 + */ + void add(const std::string& path, std::vector&& 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 data; + std::vector compressedData; + u64 offset; + u32 compression; + u32 flags; + }; + + Compression compression_; + int level_; + std::string encryptKey_; + Decryptor::Type encryptType_ = Decryptor::Type::None; + std::vector entries_; + size_t totalOriginalSize_ = 0; + size_t totalCompressedSize_ = 0; + + /** + * @brief 处理数据(压缩/加密) + * @param data 原始数据 + * @return 处理后的数据 + */ + std::vector processData(const std::vector& data); +}; + +} diff --git a/Extra2D/include/extra2d/asset/asset_types.h b/Extra2D/include/extra2d/asset/asset_types.h new file mode 100644 index 0000000..b621b08 --- /dev/null +++ b/Extra2D/include/extra2d/asset/asset_types.h @@ -0,0 +1,270 @@ +#pragma once + +#include +#include +#include +#include + +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(static_cast(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(hits) / static_cast(total) : 0.0f; + } + + /** + * @brief 计算缓存使用率 + * @return 使用率(0.0 - 1.0) + */ + float usage() const { + return limit > 0 ? static_cast(bytes) / static_cast(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 { + size_t operator()(const extra2d::AssetID& id) const noexcept { + return static_cast(id.hash); + } +}; + +} diff --git a/Extra2D/include/extra2d/asset/data_processor.h b/Extra2D/include/extra2d/asset/data_processor.h new file mode 100644 index 0000000..5311572 --- /dev/null +++ b/Extra2D/include/extra2d/asset/data_processor.h @@ -0,0 +1,397 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace extra2d { + +// --------------------------------------------------------------------------- +// DataProcessor - 数据处理器接口(装饰器模式) +// --------------------------------------------------------------------------- + +/** + * @brief 数据处理器接口 + * + * 使用装饰器模式支持链式处理数据流。 + * 支持压缩、解压、加密、解密等操作的组合。 + */ +class DataProcessor { +public: + virtual ~DataProcessor() = default; + + /** + * @brief 处理输入数据 + * @param input 输入数据 + * @return 处理后的数据 + */ + virtual std::vector process(const std::vector& input) = 0; + + /** + * @brief 设置下一个处理器 + * @param next 下一个处理器 + */ + void setNext(Unique next) { next_ = std::move(next); } + + /** + * @brief 获取下一个处理器 + * @return 下一个处理器的指针 + */ + DataProcessor* next() const { return next_.get(); } + +protected: + /** + * @brief 调用下一个处理器处理数据 + * @param input 输入数据 + * @return 处理后的数据,如果没有下一个处理器则返回原数据 + */ + std::vector processNext(const std::vector& input) { + return next_ ? next_->process(input) : input; + } + + Unique 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 process(const std::vector& input) override; + + /** + * @brief 获取加密类型 + * @return 加密类型 + */ + Type type() const { return type_; } + +private: + std::string key_; + Type type_; + + /** + * @brief XOR 解密 + * @param input 输入数据 + * @return 解密后的数据 + */ + std::vector decryptXOR(const std::vector& input); + + /** + * @brief AES-256 解密 + * @param input 输入数据 + * @return 解密后的数据 + */ + std::vector decryptAES256(const std::vector& input); +}; + +// --------------------------------------------------------------------------- +// Decompressor - 解压器 +// --------------------------------------------------------------------------- + +/** + * @brief 解压处理器 + * + * 支持多种压缩算法的解压操作。 + * 目前支持 Zstd、LZ4 和 Zlib。 + */ +class Decompressor : public DataProcessor { +public: + /** + * @brief 构造解压器 + * @param algo 压缩算法 + */ + explicit Decompressor(Compression algo = Compression::Zstd); + + /** + * @brief 处理(解压)输入数据 + * @param input 压缩的输入数据 + * @return 解压后的数据 + */ + std::vector process(const std::vector& input) override; + + /** + * @brief 获取压缩算法 + * @return 压缩算法 + */ + Compression algorithm() const { return algo_; } + +private: + Compression algo_; + + /** + * @brief Zstd 解压 + * @param input 压缩数据 + * @return 解压后的数据 + */ + std::vector decompressZstd(const std::vector& input); + + /** + * @brief LZ4 解压 + * @param input 压缩数据 + * @return 解压后的数据 + */ + std::vector decompressLZ4(const std::vector& input); + + /** + * @brief Zlib 解压 + * @param input 压缩数据 + * @return 解压后的数据 + */ + std::vector decompressZlib(const std::vector& 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 process(const std::vector& input) override; + + /** + * @brief 获取加密类型 + * @return 加密类型 + */ + Decryptor::Type type() const { return type_; } + +private: + std::string key_; + Decryptor::Type type_; + + /** + * @brief XOR 加密 + * @param input 输入数据 + * @return 加密后的数据 + */ + std::vector encryptXOR(const std::vector& input); + + /** + * @brief AES-256 加密 + * @param input 输入数据 + * @return 加密后的数据 + */ + std::vector encryptAES256(const std::vector& 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 process(const std::vector& input) override; + + /** + * @brief 获取压缩算法 + * @return 压缩算法 + */ + Compression algorithm() const { return algo_; } + +private: + Compression algo_; + int level_; + + /** + * @brief Zstd 压缩 + * @param input 原始数据 + * @return 压缩后的数据 + */ + std::vector compressZstd(const std::vector& input); + + /** + * @brief LZ4 压缩 + * @param input 原始数据 + * @return 压缩后的数据 + */ + std::vector compressLZ4(const std::vector& input); + + /** + * @brief Zlib 压缩 + * @param input 原始数据 + * @return 压缩后的数据 + */ + std::vector compressZlib(const std::vector& 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 processor); + + /** + * @brief 处理数据 + * @param input 输入数据 + * @return 处理后的数据 + */ + std::vector process(const std::vector& 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> processors_; +}; + +// --------------------------------------------------------------------------- +// 工具函数 +// --------------------------------------------------------------------------- + +/** + * @brief 计算数据的 SHA-256 校验和 + * @param data 输入数据 + * @return 32字节的校验和 + */ +std::vector computeChecksum(const std::vector& data); + +/** + * @brief 验证数据的 SHA-256 校验和 + * @param data 输入数据 + * @param checksum 预期的校验和 + * @return 匹配返回 true + */ +bool verifyChecksum(const std::vector& data, const std::vector& checksum); + +} diff --git a/Extra2D/include/extra2d/extra2d.h b/Extra2D/include/extra2d/extra2d.h index 6539b3a..f384383 100644 --- a/Extra2D/include/extra2d/extra2d.h +++ b/Extra2D/include/extra2d/extra2d.h @@ -25,10 +25,20 @@ #include // Services +#include #include #include #include +// Asset +#include +#include +#include +#include +#include +#include +#include + // Application #include diff --git a/Extra2D/include/extra2d/services/asset_service.h b/Extra2D/include/extra2d/services/asset_service.h new file mode 100644 index 0000000..6436477 --- /dev/null +++ b/Extra2D/include/extra2d/services/asset_service.h @@ -0,0 +1,334 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace extra2d { + +// --------------------------------------------------------------------------- +// IAssetService - 资源服务接口 +// --------------------------------------------------------------------------- + +/** + * @brief 资源服务接口 + * + * 提供资源加载、缓存、异步加载等功能。 + * 使用模板方法支持类型安全的资源加载。 + */ +class IAssetService : public IService { +public: + virtual ~IAssetService() = default; + + /** + * @brief 同步加载资源 + * @tparam T 资源类型 + * @param path 资源路径 + * @return 资源句柄 + */ + template AssetHandle load(const std::string &path) { + static_assert(std::is_base_of_v, "T must derive from Asset"); + return loadImpl(AssetID(path), typeid(T)); + } + + /** + * @brief 异步加载资源 + * @tparam T 资源类型 + * @param path 资源路径 + * @param callback 加载完成回调 + */ + template + void loadAsync(const std::string &path, AssetLoadCallback callback) { + static_assert(std::is_base_of_v, "T must derive from Asset"); + loadAsyncImpl(AssetID(path), typeid(T), + [cb = std::move(callback)](AssetHandleBase handle) { + cb(AssetHandle(handle)); + }); + } + + /** + * @brief 获取已缓存的资源 + * @tparam T 资源类型 + * @param path 资源路径 + * @return 资源句柄,不存在返回空句柄 + */ + template AssetHandle get(const std::string &path) { + static_assert(std::is_base_of_v, "T must derive from Asset"); + return getImpl(AssetID(path), typeid(T)); + } + + /** + * @brief 预加载资源(后台加载,不返回句柄) + * @tparam T 资源类型 + * @param path 资源路径 + */ + template void preload(const std::string &path) { + static_assert(std::is_base_of_v, "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 void registerLoader(Unique> loader) { + static_assert(std::is_base_of_v, "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 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 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 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 loader) override; + +private: + struct LoadTask { + AssetID id; + std::type_index type = typeid(void); + std::function callback; + }; + + struct LoadedAsset { + Ref asset; + std::type_index type = typeid(void); + }; + + std::string root_; + Unique cache_; + PackManager packManager_; + DataPipe pipe_; + + mutable std::shared_mutex mutex_; + std::unordered_map assets_; + std::unordered_map states_; + std::unordered_map> loaders_; + + std::thread workerThread_; + std::queue taskQueue_; + std::mutex taskMutex_; + std::condition_variable taskCv_; + std::atomic running_{false}; + + std::queue> callbackQueue_; + std::mutex callbackMutex_; + + /** + * @brief 工作线程函数 + */ + void workerFunc(); + + /** + * @brief 从文件系统加载资源 + * @param id 资源ID + * @param type 资源类型 + * @return 加载的资源 + */ + Ref loadFromFile(const AssetID &id, std::type_index type); + + /** + * @brief 从资源包加载资源 + * @param id 资源ID + * @param type 资源类型 + * @return 加载的资源 + */ + Ref 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 diff --git a/Extra2D/src/asset/asset.cpp b/Extra2D/src/asset/asset.cpp new file mode 100644 index 0000000..79cabf8 --- /dev/null +++ b/Extra2D/src/asset/asset.cpp @@ -0,0 +1,62 @@ +#include + +#define STB_TRUETYPE_IMPLEMENTATION +#include + +namespace extra2d { + +// --------------------------------------------------------------------------- +// FontAsset::Impl - Pimpl 实现类 +// --------------------------------------------------------------------------- + +class FontAsset::Impl { +public: + stbtt_fontinfo info; + bool initialized = false; +}; + +// --------------------------------------------------------------------------- +// FontAsset 实现 +// --------------------------------------------------------------------------- + +FontAsset::FontAsset() : impl_(ptr::makeUnique()) {} + +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 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); +} + +} diff --git a/Extra2D/src/asset/asset_cache.cpp b/Extra2D/src/asset/asset_cache.cpp new file mode 100644 index 0000000..100f0be --- /dev/null +++ b/Extra2D/src/asset/asset_cache.cpp @@ -0,0 +1,121 @@ +#include + +namespace extra2d { + +// --------------------------------------------------------------------------- +// AssetCache 实现 +// --------------------------------------------------------------------------- + +AssetCache::AssetCache(size_t limit) + : limit_(limit) { + stats_.limit = limit; +} + +bool AssetCache::has(const AssetID& id) const { + std::shared_lock lock(mutex_); + return entries_.find(id) != entries_.end(); +} + +bool AssetCache::remove(const AssetID& id) { + std::unique_lock 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 lock(mutex_); + limit_ = limit; + stats_.limit = limit; + + if (limit > 0 && bytes_ > limit) { + evict(); + } +} + +size_t AssetCache::count() const { + std::shared_lock lock(mutex_); + return entries_.size(); +} + +size_t AssetCache::purge() { + std::unique_lock 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 lock(mutex_); + entries_.clear(); + lruList_.clear(); + bytes_ = 0; + stats_.count = 0; + stats_.bytes = 0; +} + +CacheStats AssetCache::stats() const { + std::shared_lock lock(mutex_); + stats_.bytes = bytes_; + return stats_; +} + +void AssetCache::resetStats() { + std::unique_lock 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; +} + +} diff --git a/Extra2D/src/asset/asset_loader.cpp b/Extra2D/src/asset/asset_loader.cpp new file mode 100644 index 0000000..69e06a4 --- /dev/null +++ b/Extra2D/src/asset/asset_loader.cpp @@ -0,0 +1,418 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#define STB_IMAGE_IMPLEMENTATION +#include + +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 readFile(const std::string& path) { + std::ifstream file(path, std::ios::binary | std::ios::ate); + if (!file) { + return {}; + } + + size_t size = static_cast(file.tellg()); + file.seekg(0, std::ios::beg); + + std::vector data(size); + if (!file.read(reinterpret_cast(data.data()), size)) { + return {}; + } + + return data; +} + +} + +// --------------------------------------------------------------------------- +// TextureLoader 实现 +// --------------------------------------------------------------------------- + +TextureLoader::TextureLoader() = default; + +TextureLoader::~TextureLoader() = default; + +Ref 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 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(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(); + + Unique pixelData(new u8[static_cast(width) * height * actualChannels]); + std::memcpy(pixelData.get(), pixels, + static_cast(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 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 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 FontLoader::loadFromMemory(const u8* data, size_t size) { + if (!data || size == 0) { + return nullptr; + } + + auto asset = ptr::make(); + + std::vector 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 FontLoader::extensions() const { + return {".ttf", ".otf", ".ttc"}; +} + +// --------------------------------------------------------------------------- +// ShaderLoader 实现 +// --------------------------------------------------------------------------- + +Ref 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 ShaderLoader::loadFromMemory(const u8* data, size_t size) { + if (!data || size == 0) { + return nullptr; + } + + std::string content(reinterpret_cast(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(); + 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 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 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 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 AudioLoader::extensions() const { + return {".wav"}; +} + +Ref 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(data + pos); + u32 chunkSize = *reinterpret_cast(data + pos + 4); + + if (chunkId == 0x20746D66) { // "fmt " + audioFormat = *reinterpret_cast(data + pos + 8); + numChannels = *reinterpret_cast(data + pos + 10); + sampleRate = *reinterpret_cast(data + pos + 12); + bitsPerSample = *reinterpret_cast(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(); + + std::vector pcmData(audioData, audioData + dataSize); + asset->setData(AudioFormat::PCM, numChannels, sampleRate, bitsPerSample, + std::move(pcmData)); + + return asset; +} + +// --------------------------------------------------------------------------- +// DataLoader 实现 +// --------------------------------------------------------------------------- + +Ref 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 DataLoader::loadFromMemory(const u8* data, size_t size) { + if (!data || size == 0) { + return nullptr; + } + + auto asset = ptr::make(); + std::vector assetData(data, data + size); + asset->setData(std::move(assetData)); + + return asset; +} + +bool DataLoader::canLoad(const std::string& path) const { + return !path.empty(); +} + +std::vector 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; +} + +} diff --git a/Extra2D/src/asset/asset_pack.cpp b/Extra2D/src/asset/asset_pack.cpp new file mode 100644 index 0000000..4523159 --- /dev/null +++ b/Extra2D/src/asset/asset_pack.cpp @@ -0,0 +1,439 @@ +#include "extra2d/asset/asset_pack.h" + +#include +#include +#include + +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 AssetPack::read(const AssetID& id) { + auto raw = readRaw(id); + if (raw.empty()) { + return {}; + } + + if (!pipe_.empty()) { + return pipe_.process(raw); + } + + return raw; +} + +std::vector AssetPack::read(const std::string& path) { + return read(AssetID(path)); +} + +std::vector AssetPack::readRaw(const AssetID& id) { + auto* entry = getEntry(id); + if (!entry) { + return {}; + } + + return readEntryData(*entry); +} + +std::vector AssetPack::assets() const { + std::vector 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(&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(&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(&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(&entry.offset), sizeof(entry.offset)); + file_.read(reinterpret_cast(&entry.size), sizeof(entry.size)); + file_.read(reinterpret_cast(&entry.originalSize), sizeof(entry.originalSize)); + file_.read(reinterpret_cast(&entry.compression), sizeof(entry.compression)); + file_.read(reinterpret_cast(&entry.flags), sizeof(entry.flags)); + + if (!file_.good()) { + return false; + } + + entries_[entry.id] = entry; + } + + return true; +} + +std::vector AssetPack::readEntryData(const AssetPackageEntry& entry) { + std::vector data(entry.size); + + file_.seekg(entry.offset, std::ios::beg); + file_.read(reinterpret_cast(data.data()), entry.size); + + if (!file_.good()) { + return {}; + } + + return data; +} + +// --------------------------------------------------------------------------- +// PackManager 实现 +// --------------------------------------------------------------------------- + +bool PackManager::mount(const std::string& path) { + auto pack = std::make_unique(); + 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& 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 PackManager::read(const AssetID& id) { + auto* pack = find(id); + if (pack) { + return pack->read(id); + } + return {}; +} + +std::vector PackManager::read(const std::string& path) { + return read(AssetID(path)); +} + +std::vector PackManager::allAssets() const { + std::vector result; + for (const auto& pack : packs_) { + auto assets = pack->assets(); + result.insert(result.end(), assets.begin(), assets.end()); + } + return result; +} + +std::vector PackManager::mountedPacks() const { + std::vector 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& data) { + BuilderEntry entry; + entry.id = AssetID(path); + entry.data = data; + entry.compression = static_cast(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(Compression::None); + } + + entries_.push_back(std::move(entry)); + + totalOriginalSize_ += data.size(); + totalCompressedSize_ += entry.compressedData.size(); +} + +void AssetPackBuilder::add(const std::string& path, std::vector&& 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 data(size); + file.read(reinterpret_cast(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(compression_); + header.encryptionType = static_cast(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(&header), sizeof(header)); + + u32 entryCount = static_cast(entries_.size()); + out.write(reinterpret_cast(&entryCount), sizeof(entryCount)); + + for (const auto& entry : entries_) { + u32 pathLen = static_cast(entry.id.path.size()); + out.write(reinterpret_cast(&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(&offset), sizeof(offset)); + out.write(reinterpret_cast(&size), sizeof(size)); + out.write(reinterpret_cast(&originalSize), sizeof(originalSize)); + out.write(reinterpret_cast(&entry.compression), sizeof(entry.compression)); + out.write(reinterpret_cast(&entry.flags), sizeof(entry.flags)); + } + + for (const auto& entry : entries_) { + out.write(reinterpret_cast(entry.compressedData.data()), + entry.compressedData.size()); + } + + return out.good(); +} + +void AssetPackBuilder::clear() { + entries_.clear(); + totalOriginalSize_ = 0; + totalCompressedSize_ = 0; +} + +std::vector AssetPackBuilder::processData(const std::vector& 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); +} + +} diff --git a/Extra2D/src/asset/data_processor.cpp b/Extra2D/src/asset/data_processor.cpp new file mode 100644 index 0000000..524ee62 --- /dev/null +++ b/Extra2D/src/asset/data_processor.cpp @@ -0,0 +1,449 @@ +#include "extra2d/asset/data_processor.h" + +#include +#include + +#ifdef E2D_USE_ZSTD +#include +#endif + +#ifdef E2D_USE_LZ4 +#include +#endif + +#ifdef E2D_USE_ZLIB +#include +#endif + +namespace extra2d { + +// --------------------------------------------------------------------------- +// Decryptor 实现 +// --------------------------------------------------------------------------- + +Decryptor::Decryptor(const std::string& key, Type type) + : key_(key), type_(type) { +} + +std::vector Decryptor::process(const std::vector& input) { + if (input.empty() || type_ == Type::None) { + return processNext(input); + } + + std::vector 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 Decryptor::decryptXOR(const std::vector& input) { + if (key_.empty()) { + return input; + } + + std::vector result(input.size()); + const size_t keyLen = key_.size(); + + for (size_t i = 0; i < input.size(); ++i) { + result[i] = input[i] ^ static_cast(key_[i % keyLen]); + } + + return result; +} + +std::vector Decryptor::decryptAES256(const std::vector& input) { + return decryptXOR(input); +} + +// --------------------------------------------------------------------------- +// Decompressor 实现 +// --------------------------------------------------------------------------- + +Decompressor::Decompressor(Compression algo) + : algo_(algo) { +} + +std::vector Decompressor::process(const std::vector& input) { + if (input.empty() || algo_ == Compression::None) { + return processNext(input); + } + + std::vector 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 Decompressor::decompressZstd(const std::vector& 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 result(static_cast(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 Decompressor::decompressLZ4(const std::vector& input) { +#ifdef E2D_USE_LZ4 + if (input.size() < sizeof(u32)) { + return {}; + } + + u32 originalSize; + std::memcpy(&originalSize, input.data(), sizeof(u32)); + + std::vector result(originalSize); + + int const decompressedSize = LZ4_decompress_safe( + reinterpret_cast(input.data() + sizeof(u32)), + reinterpret_cast(result.data()), + static_cast(input.size() - sizeof(u32)), + static_cast(originalSize) + ); + + if (decompressedSize < 0) { + return {}; + } + + return result; +#else + return input; +#endif +} + +std::vector Decompressor::decompressZlib(const std::vector& input) { +#ifdef E2D_USE_ZLIB + std::vector 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(input.size()); + strm.next_in = const_cast(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 Encryptor::process(const std::vector& input) { + if (input.empty() || type_ == Decryptor::Type::None) { + return processNext(input); + } + + std::vector 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 Encryptor::encryptXOR(const std::vector& input) { + if (key_.empty()) { + return input; + } + + std::vector result(input.size()); + const size_t keyLen = key_.size(); + + for (size_t i = 0; i < input.size(); ++i) { + result[i] = input[i] ^ static_cast(key_[i % keyLen]); + } + + return result; +} + +std::vector Encryptor::encryptAES256(const std::vector& input) { + return encryptXOR(input); +} + +// --------------------------------------------------------------------------- +// Compressor 实现 +// --------------------------------------------------------------------------- + +Compressor::Compressor(Compression algo, int level) + : algo_(algo), level_(level) { +} + +std::vector Compressor::process(const std::vector& input) { + if (input.empty() || algo_ == Compression::None) { + return processNext(input); + } + + std::vector 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 Compressor::compressZstd(const std::vector& input) { +#ifdef E2D_USE_ZSTD + size_t const bound = ZSTD_compressBound(input.size()); + std::vector 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 Compressor::compressLZ4(const std::vector& input) { +#ifdef E2D_USE_LZ4 + int const bound = LZ4_compressBound(static_cast(input.size())); + std::vector result(sizeof(u32) + bound); + + u32 originalSize = static_cast(input.size()); + std::memcpy(result.data(), &originalSize, sizeof(u32)); + + int const compressedSize = LZ4_compress_default( + reinterpret_cast(input.data()), + reinterpret_cast(result.data() + sizeof(u32)), + static_cast(input.size()), + bound + ); + + if (compressedSize <= 0) { + return {}; + } + + result.resize(sizeof(u32) + compressedSize); + return result; +#else + return input; +#endif +} + +std::vector Compressor::compressZlib(const std::vector& input) { +#ifdef E2D_USE_ZLIB + std::vector 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(input.size()); + strm.next_in = const_cast(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(key, type)); + return *this; +} + +DataPipe& DataPipe::decompress(Compression algo) { + processors_.push_back(std::make_unique(algo)); + return *this; +} + +DataPipe& DataPipe::encrypt(const std::string& key, Decryptor::Type type) { + processors_.push_back(std::make_unique(key, type)); + return *this; +} + +DataPipe& DataPipe::compress(Compression algo, int level) { + processors_.push_back(std::make_unique(algo, level)); + return *this; +} + +DataPipe& DataPipe::add(Unique processor) { + processors_.push_back(std::move(processor)); + return *this; +} + +std::vector DataPipe::process(const std::vector& input) { + if (processors_.empty()) { + return input; + } + + std::vector result = input; + for (auto& processor : processors_) { + result = processor->process(result); + } + + return result; +} + +void DataPipe::clear() { + processors_.clear(); +} + +// --------------------------------------------------------------------------- +// 工具函数实现 +// --------------------------------------------------------------------------- + +std::vector computeChecksum(const std::vector& data) { + std::vector result(32, 0); + + u64 hash1 = 14695981039346656037ULL; + u64 hash2 = 14695981039346656037ULL; + + for (size_t i = 0; i < data.size(); ++i) { + hash1 ^= static_cast(data[i]); + hash1 *= 1099511628211ULL; + + hash2 ^= static_cast(data[i]) << ((i % 8) * 8); + hash2 *= 1099511628211ULL; + } + + for (size_t i = 0; i < 8; ++i) { + result[i] = static_cast((hash1 >> (i * 8)) & 0xFF); + result[i + 8] = static_cast((hash2 >> (i * 8)) & 0xFF); + } + + for (size_t i = 16; i < 32; ++i) { + result[i] = static_cast((hash1 ^ hash2) >> ((i - 16) * 8) & 0xFF); + } + + return result; +} + +bool verifyChecksum(const std::vector& data, const std::vector& checksum) { + if (checksum.size() != 32) { + return false; + } + + auto computed = computeChecksum(data); + return std::equal(computed.begin(), computed.end(), checksum.begin()); +} + +} diff --git a/Extra2D/src/services/asset_service.cpp b/Extra2D/src/services/asset_service.cpp new file mode 100644 index 0000000..8f726b1 --- /dev/null +++ b/Extra2D/src/services/asset_service.cpp @@ -0,0 +1,318 @@ +#include "extra2d/services/asset_service.h" + +#include +#include + +namespace extra2d { + +// --------------------------------------------------------------------------- +// AssetService 实现 +// --------------------------------------------------------------------------- + +AssetService::AssetService() : cache_(ptr::makeUnique()) {} + +AssetService::~AssetService() { shutdown(); } + +bool AssetService::init() { + if (initialized()) { + return true; + } + + setState(ServiceState::Initializing); + + registerLoader(AssetLoaderFactory::createTextureLoader()); + registerLoader(AssetLoaderFactory::createFontLoader()); + registerLoader(AssetLoaderFactory::createShaderLoader()); + registerLoader(AssetLoaderFactory::createAudioLoader()); + registerLoader(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 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 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 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() { cache_->purge(); } + +void AssetService::clear() { + std::unique_lock 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> callbacks; + { + std::lock_guard 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 lock(mutex_); + auto it = assets_.find(id); + if (it != assets_.end()) { + cache_->hit(); + return AssetHandleBase(id, it->second.asset); + } + } + + cache_->miss(); + + Ref asset = loadFromFile(id, type); + if (!asset) { + asset = loadFromPack(id, type); + } + + if (!asset) { + return AssetHandleBase(); + } + + { + std::unique_lock 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 callback) { + { + std::shared_lock 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 lock(mutex_); + states_[id] = AssetState::Loading; + } + + { + std::lock_guard 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 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 lock(mutex_); + if (assets_.find(id) != assets_.end()) { + return; + } + } + + { + std::lock_guard lock(taskMutex_); + taskQueue_.push({id, type, nullptr}); + } + taskCv_.notify_one(); +} + +void AssetService::registerLoaderImpl(std::type_index type, + Unique loader) { + std::unique_lock lock(mutex_); + loaders_[type] = std::move(loader); +} + +void AssetService::workerFunc() { + while (running_) { + LoadTask task; + { + std::unique_lock 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 = loadFromFile(task.id, task.type); + if (!asset) { + asset = loadFromPack(task.id, task.type); + } + + if (asset) { + std::unique_lock lock(mutex_); + assets_[task.id] = {asset, task.type}; + states_[task.id] = AssetState::Loaded; + cache_->add(asset); + } else { + std::unique_lock lock(mutex_); + states_[task.id] = AssetState::Failed; + } + + if (task.callback) { + std::lock_guard lock(callbackMutex_); + callbackQueue_.push( + [task, asset]() { task.callback(AssetHandleBase(task.id, asset)); }); + } + } +} + +Ref 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 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