feat(asset): 添加资源管理系统核心组件
实现资源加载、缓存和管理功能,包括: - Asset基类和具体资源类型(纹理、字体、着色器等) - 资源缓存系统(AssetCache)支持LRU策略 - 资源加载器接口和具体实现 - 资源包管理(打包/解包) - 数据处理管道(压缩/加密) - 资源服务(AssetService)提供统一接口
This commit is contained in:
parent
4e6db88da5
commit
25c7c47872
|
|
@ -0,0 +1,477 @@
|
|||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
#include <extra2d/asset/asset_types.h>
|
||||
#include <extra2d/core/types.h>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Asset - 资源基类
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 资源基类
|
||||
*
|
||||
* 所有资源类型的基类,继承自 enable_shared_from_this 支持自动引用计数。
|
||||
* 提供资源的基本属性和生命周期管理接口。
|
||||
*/
|
||||
class Asset : public std::enable_shared_from_this<Asset> {
|
||||
public:
|
||||
virtual ~Asset() = default;
|
||||
|
||||
/**
|
||||
* @brief 获取资源类型
|
||||
* @return 资源类型枚举值
|
||||
*/
|
||||
virtual AssetType type() const = 0;
|
||||
|
||||
/**
|
||||
* @brief 检查资源是否已加载
|
||||
* @return 已加载返回 true
|
||||
*/
|
||||
virtual bool loaded() const = 0;
|
||||
|
||||
/**
|
||||
* @brief 获取资源内存占用大小
|
||||
* @return 内存占用字节数
|
||||
*/
|
||||
virtual size_t memSize() const = 0;
|
||||
|
||||
/**
|
||||
* @brief 获取资源ID
|
||||
* @return 资源ID
|
||||
*/
|
||||
const AssetID &id() const { return id_; }
|
||||
|
||||
/**
|
||||
* @brief 获取资源路径
|
||||
* @return 资源路径
|
||||
*/
|
||||
const std::string &path() const { return path_; }
|
||||
|
||||
/**
|
||||
* @brief 获取资源状态
|
||||
* @return 资源状态
|
||||
*/
|
||||
AssetState state() const { return state_; }
|
||||
|
||||
/**
|
||||
* @brief 获取当前引用计数
|
||||
* @return 引用计数(用于调试和监控)
|
||||
*/
|
||||
long refs() const { return shared_from_this().use_count(); }
|
||||
|
||||
protected:
|
||||
AssetID id_;
|
||||
std::string path_;
|
||||
std::atomic<AssetState> state_{AssetState::Unloaded};
|
||||
|
||||
/**
|
||||
* @brief 设置资源状态
|
||||
* @param state 新状态
|
||||
*/
|
||||
void setState(AssetState state) {
|
||||
state_.store(state, std::memory_order_release);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 设置资源ID
|
||||
* @param id 资源ID
|
||||
*/
|
||||
void setId(const AssetID &id) { id_ = id; }
|
||||
|
||||
/**
|
||||
* @brief 设置资源路径
|
||||
* @param path 资源路径
|
||||
*/
|
||||
void setPath(const std::string &path) { path_ = path; }
|
||||
|
||||
friend class AssetCache;
|
||||
friend class AssetService;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// TextureAsset - 纹理资源
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 纹理资源类
|
||||
*
|
||||
* 存储纹理图像数据,支持多种像素格式。
|
||||
*/
|
||||
class TextureAsset : public Asset {
|
||||
public:
|
||||
AssetType type() const override { return AssetType::Texture; }
|
||||
|
||||
bool loaded() const override {
|
||||
return state_.load(std::memory_order_acquire) == AssetState::Loaded &&
|
||||
data_ != nullptr;
|
||||
}
|
||||
|
||||
size_t memSize() const override {
|
||||
return static_cast<size_t>(width_) * height_ * channels_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取纹理宽度
|
||||
* @return 宽度(像素)
|
||||
*/
|
||||
int width() const { return width_; }
|
||||
|
||||
/**
|
||||
* @brief 获取纹理高度
|
||||
* @return 高度(像素)
|
||||
*/
|
||||
int height() const { return height_; }
|
||||
|
||||
/**
|
||||
* @brief 获取通道数
|
||||
* @return 通道数(1-4)
|
||||
*/
|
||||
int channels() const { return channels_; }
|
||||
|
||||
/**
|
||||
* @brief 获取像素数据
|
||||
* @return 像素数据指针
|
||||
*/
|
||||
const u8 *data() const { return data_.get(); }
|
||||
|
||||
/**
|
||||
* @brief 获取像素数据大小
|
||||
* @return 数据大小(字节)
|
||||
*/
|
||||
size_t dataSize() const { return memSize(); }
|
||||
|
||||
/**
|
||||
* @brief 设置纹理数据
|
||||
* @param width 宽度
|
||||
* @param height 高度
|
||||
* @param channels 通道数
|
||||
* @param data 像素数据(转移所有权)
|
||||
*/
|
||||
void setData(int width, int height, int channels, Unique<u8[]> data) {
|
||||
width_ = width;
|
||||
height_ = height;
|
||||
channels_ = channels;
|
||||
data_ = std::move(data);
|
||||
setState(AssetState::Loaded);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 释放纹理数据
|
||||
*/
|
||||
void release() {
|
||||
data_.reset();
|
||||
width_ = 0;
|
||||
height_ = 0;
|
||||
channels_ = 0;
|
||||
setState(AssetState::Unloaded);
|
||||
}
|
||||
|
||||
private:
|
||||
int width_ = 0;
|
||||
int height_ = 0;
|
||||
int channels_ = 0;
|
||||
Unique<u8[]> data_;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// FontAsset - 字体资源
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 字体资源类
|
||||
*
|
||||
* 存储TrueType字体数据,支持字形渲染。
|
||||
* 使用 Pimpl 模式隐藏 stbtt_fontinfo 实现细节。
|
||||
*/
|
||||
class FontAsset : public Asset {
|
||||
public:
|
||||
FontAsset();
|
||||
~FontAsset() override;
|
||||
|
||||
AssetType type() const override { return AssetType::Font; }
|
||||
|
||||
bool loaded() const override;
|
||||
|
||||
size_t memSize() const override { return data_.size(); }
|
||||
|
||||
/**
|
||||
* @brief 获取指定像素高度的缩放因子
|
||||
* @param pixels 像素高度
|
||||
* @return 缩放因子
|
||||
*/
|
||||
float scaleForPixelHeight(float pixels) const;
|
||||
|
||||
/**
|
||||
* @brief 获取字体数据
|
||||
* @return 字体数据指针
|
||||
*/
|
||||
const u8 *data() const { return data_.data(); }
|
||||
|
||||
/**
|
||||
* @brief 获取字体数据大小
|
||||
* @return 数据大小(字节)
|
||||
*/
|
||||
size_t dataSize() const { return data_.size(); }
|
||||
|
||||
/**
|
||||
* @brief 设置字体数据
|
||||
* @param data 字体数据
|
||||
* @return 成功返回 true
|
||||
*/
|
||||
bool setData(std::vector<u8> data);
|
||||
|
||||
/**
|
||||
* @brief 释放字体数据
|
||||
*/
|
||||
void release();
|
||||
|
||||
private:
|
||||
std::vector<u8> data_;
|
||||
class Impl;
|
||||
Unique<Impl> impl_;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// ShaderAsset - 着色器资源
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 着色器资源类
|
||||
*
|
||||
* 存储顶点和片段着色器源代码。
|
||||
*/
|
||||
class ShaderAsset : public Asset {
|
||||
public:
|
||||
AssetType type() const override { return AssetType::Shader; }
|
||||
|
||||
bool loaded() const override {
|
||||
return state_.load(std::memory_order_acquire) == AssetState::Loaded;
|
||||
}
|
||||
|
||||
size_t memSize() const override {
|
||||
return vertexSrc_.size() + fragmentSrc_.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取顶点着色器源码
|
||||
* @return 顶点着色器源码
|
||||
*/
|
||||
const std::string &vertexSource() const { return vertexSrc_; }
|
||||
|
||||
/**
|
||||
* @brief 获取片段着色器源码
|
||||
* @return 片段着色器源码
|
||||
*/
|
||||
const std::string &fragmentSource() const { return fragmentSrc_; }
|
||||
|
||||
/**
|
||||
* @brief 设置着色器源码
|
||||
* @param vertex 顶点着色器源码
|
||||
* @param fragment 片段着色器源码
|
||||
*/
|
||||
void setSource(std::string vertex, std::string fragment) {
|
||||
vertexSrc_ = std::move(vertex);
|
||||
fragmentSrc_ = std::move(fragment);
|
||||
setState(AssetState::Loaded);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 释放着色器源码
|
||||
*/
|
||||
void release() {
|
||||
vertexSrc_.clear();
|
||||
fragmentSrc_.clear();
|
||||
setState(AssetState::Unloaded);
|
||||
}
|
||||
|
||||
private:
|
||||
std::string vertexSrc_;
|
||||
std::string fragmentSrc_;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// AudioAsset - 音频资源
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 音频格式枚举
|
||||
*/
|
||||
enum class AudioFormat : u8 { PCM = 0, MP3 = 1, OGG = 2, WAV = 3 };
|
||||
|
||||
/**
|
||||
* @brief 音频资源类
|
||||
*
|
||||
* 存储音频数据,支持多种格式。
|
||||
*/
|
||||
class AudioAsset : public Asset {
|
||||
public:
|
||||
AssetType type() const override { return AssetType::Audio; }
|
||||
|
||||
bool loaded() const override {
|
||||
return state_.load(std::memory_order_acquire) == AssetState::Loaded &&
|
||||
!data_.empty();
|
||||
}
|
||||
|
||||
size_t memSize() const override { return data_.size(); }
|
||||
|
||||
/**
|
||||
* @brief 获取音频格式
|
||||
* @return 音频格式
|
||||
*/
|
||||
AudioFormat format() const { return format_; }
|
||||
|
||||
/**
|
||||
* @brief 获取声道数
|
||||
* @return 声道数
|
||||
*/
|
||||
int channels() const { return channels_; }
|
||||
|
||||
/**
|
||||
* @brief 获取采样率
|
||||
* @return 采样率
|
||||
*/
|
||||
int sampleRate() const { return sampleRate_; }
|
||||
|
||||
/**
|
||||
* @brief 获取每样本位数
|
||||
* @return 每样本位数
|
||||
*/
|
||||
int bitsPerSample() const { return bitsPerSample_; }
|
||||
|
||||
/**
|
||||
* @brief 获取时长(秒)
|
||||
* @return 时长
|
||||
*/
|
||||
float duration() const { return duration_; }
|
||||
|
||||
/**
|
||||
* @brief 获取音频数据
|
||||
* @return 音频数据指针
|
||||
*/
|
||||
const u8 *data() const { return data_.data(); }
|
||||
|
||||
/**
|
||||
* @brief 获取音频数据大小
|
||||
* @return 数据大小(字节)
|
||||
*/
|
||||
size_t dataSize() const { return data_.size(); }
|
||||
|
||||
/**
|
||||
* @brief 是否为流式音频
|
||||
* @return 流式音频返回 true
|
||||
*/
|
||||
bool streaming() const { return streaming_; }
|
||||
|
||||
/**
|
||||
* @brief 设置音频数据
|
||||
* @param format 音频格式
|
||||
* @param channels 声道数
|
||||
* @param sampleRate 采样率
|
||||
* @param bitsPerSample 每样本位数
|
||||
* @param data 音频数据
|
||||
*/
|
||||
void setData(AudioFormat format, int channels, int sampleRate,
|
||||
int bitsPerSample, std::vector<u8> data) {
|
||||
format_ = format;
|
||||
channels_ = channels;
|
||||
sampleRate_ = sampleRate;
|
||||
bitsPerSample_ = bitsPerSample;
|
||||
data_ = std::move(data);
|
||||
|
||||
if (sampleRate > 0 && channels > 0 && bitsPerSample > 0) {
|
||||
size_t bytesPerSecond =
|
||||
static_cast<size_t>(sampleRate) * channels * (bitsPerSample / 8);
|
||||
if (bytesPerSecond > 0) {
|
||||
duration_ = static_cast<float>(data_.size()) /
|
||||
static_cast<float>(bytesPerSecond);
|
||||
}
|
||||
}
|
||||
|
||||
streaming_ = duration_ > 5.0f;
|
||||
setState(AssetState::Loaded);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 释放音频数据
|
||||
*/
|
||||
void release() {
|
||||
data_.clear();
|
||||
format_ = AudioFormat::PCM;
|
||||
channels_ = 0;
|
||||
sampleRate_ = 0;
|
||||
bitsPerSample_ = 0;
|
||||
duration_ = 0.0f;
|
||||
streaming_ = false;
|
||||
setState(AssetState::Unloaded);
|
||||
}
|
||||
|
||||
private:
|
||||
AudioFormat format_ = AudioFormat::PCM;
|
||||
int channels_ = 0;
|
||||
int sampleRate_ = 0;
|
||||
int bitsPerSample_ = 0;
|
||||
float duration_ = 0.0f;
|
||||
std::vector<u8> data_;
|
||||
bool streaming_ = false;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// DataAsset - 通用数据资源
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 通用数据资源类
|
||||
*
|
||||
* 存储任意二进制数据。
|
||||
*/
|
||||
class DataAsset : public Asset {
|
||||
public:
|
||||
AssetType type() const override { return AssetType::Data; }
|
||||
|
||||
bool loaded() const override {
|
||||
return state_.load(std::memory_order_acquire) == AssetState::Loaded;
|
||||
}
|
||||
|
||||
size_t memSize() const override { return data_.size(); }
|
||||
|
||||
/**
|
||||
* @brief 获取数据
|
||||
* @return 数据指针
|
||||
*/
|
||||
const u8 *data() const { return data_.data(); }
|
||||
|
||||
/**
|
||||
* @brief 获取数据大小
|
||||
* @return 数据大小(字节)
|
||||
*/
|
||||
size_t size() const { return data_.size(); }
|
||||
|
||||
/**
|
||||
* @brief 设置数据
|
||||
* @param data 数据
|
||||
*/
|
||||
void setData(std::vector<u8> data) {
|
||||
data_ = std::move(data);
|
||||
setState(AssetState::Loaded);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 释放数据
|
||||
*/
|
||||
void release() {
|
||||
data_.clear();
|
||||
setState(AssetState::Unloaded);
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<u8> data_;
|
||||
};
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -0,0 +1,263 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/asset/asset.h>
|
||||
#include <extra2d/asset/asset_handle.h>
|
||||
#include <extra2d/asset/asset_types.h>
|
||||
#include <extra2d/core/types.h>
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <deque>
|
||||
#include <list>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <shared_mutex>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// CacheEntry - 缓存条目
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 缓存条目结构
|
||||
*
|
||||
* 存储资源引用和访问信息,用于LRU缓存管理。
|
||||
*/
|
||||
struct CacheEntry {
|
||||
Ref<Asset> asset;
|
||||
std::chrono::steady_clock::time_point lastAccess;
|
||||
size_t accessCount = 0;
|
||||
|
||||
/**
|
||||
* @brief 构造缓存条目
|
||||
* @param a 资源引用
|
||||
*/
|
||||
explicit CacheEntry(Ref<Asset> a)
|
||||
: asset(std::move(a)),
|
||||
lastAccess(std::chrono::steady_clock::now()),
|
||||
accessCount(1) {}
|
||||
|
||||
/**
|
||||
* @brief 更新访问信息
|
||||
*/
|
||||
void touch() {
|
||||
lastAccess = std::chrono::steady_clock::now();
|
||||
++accessCount;
|
||||
}
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// AssetCache - 资源缓存
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 资源缓存类
|
||||
*
|
||||
* 实现享元模式,提供资源共享和缓存管理功能。
|
||||
*
|
||||
* 特性:
|
||||
* - LRU缓存淘汰策略
|
||||
* - 线程安全(读写锁)
|
||||
* - 引用计数自动回收
|
||||
* - 缓存统计和监控
|
||||
* - 内存上限管理
|
||||
*/
|
||||
class AssetCache {
|
||||
public:
|
||||
/**
|
||||
* @brief 构造函数
|
||||
* @param limit 缓存内存上限(字节),0表示无限制
|
||||
*/
|
||||
explicit AssetCache(size_t limit = 0);
|
||||
|
||||
~AssetCache() = default;
|
||||
|
||||
AssetCache(const AssetCache&) = delete;
|
||||
AssetCache& operator=(const AssetCache&) = delete;
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// 资源管理
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 添加资源到缓存
|
||||
* @tparam T 资源类型
|
||||
* @param asset 资源引用
|
||||
* @return 资源句柄
|
||||
*/
|
||||
template<typename T>
|
||||
AssetHandle<T> add(Ref<T> asset) {
|
||||
static_assert(std::is_base_of_v<Asset, T>,
|
||||
"T must derive from Asset");
|
||||
|
||||
if (!asset) {
|
||||
return AssetHandle<T>();
|
||||
}
|
||||
|
||||
std::unique_lock<std::shared_mutex> lock(mutex_);
|
||||
|
||||
AssetID id = asset->id();
|
||||
|
||||
size_t memSize = asset->memSize();
|
||||
bytes_ += memSize;
|
||||
|
||||
auto it = lruList_.insert(lruList_.end(), id);
|
||||
|
||||
entries_[id] = CacheEntryData{
|
||||
ptr::makeUnique<CacheEntry>(std::static_pointer_cast<Asset>(asset)),
|
||||
it
|
||||
};
|
||||
|
||||
++stats_.count;
|
||||
|
||||
if (limit_ > 0 && bytes_ > limit_) {
|
||||
evict();
|
||||
}
|
||||
|
||||
return AssetHandle<T>(id, Weak<T>(asset));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 从缓存获取资源
|
||||
* @tparam T 资源类型
|
||||
* @param id 资源ID
|
||||
* @return 资源句柄
|
||||
*/
|
||||
template<typename T>
|
||||
AssetHandle<T> get(const AssetID& id) {
|
||||
static_assert(std::is_base_of_v<Asset, T>,
|
||||
"T must derive from Asset");
|
||||
|
||||
std::unique_lock<std::shared_mutex> lock(mutex_);
|
||||
|
||||
auto it = entries_.find(id);
|
||||
if (it == entries_.end()) {
|
||||
++stats_.misses;
|
||||
return AssetHandle<T>();
|
||||
}
|
||||
|
||||
it->second.entry->touch();
|
||||
|
||||
lruList_.erase(it->second.lruIterator);
|
||||
it->second.lruIterator = lruList_.insert(lruList_.end(), id);
|
||||
|
||||
++stats_.hits;
|
||||
|
||||
auto typedAsset = std::static_pointer_cast<T>(it->second.entry->asset);
|
||||
return AssetHandle<T>(id, Weak<T>(typedAsset));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 检查缓存是否包含资源
|
||||
* @param id 资源ID
|
||||
* @return 包含返回 true
|
||||
*/
|
||||
bool has(const AssetID& id) const;
|
||||
|
||||
/**
|
||||
* @brief 从缓存移除资源
|
||||
* @param id 资源ID
|
||||
* @return 移除成功返回 true
|
||||
*/
|
||||
bool remove(const AssetID& id);
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// 缓存管理
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 设置缓存内存上限
|
||||
* @param limit 上限(字节),0表示无限制
|
||||
*/
|
||||
void setLimit(size_t limit);
|
||||
|
||||
/**
|
||||
* @brief 获取缓存内存上限
|
||||
* @return 上限(字节)
|
||||
*/
|
||||
size_t limit() const { return limit_; }
|
||||
|
||||
/**
|
||||
* @brief 获取当前缓存内存使用量
|
||||
* @return 使用量(字节)
|
||||
*/
|
||||
size_t bytes() const { return bytes_; }
|
||||
|
||||
/**
|
||||
* @brief 获取缓存条目数量
|
||||
* @return 条目数量
|
||||
*/
|
||||
size_t count() const;
|
||||
|
||||
/**
|
||||
* @brief 获取当前缓存内存使用量(别名)
|
||||
* @return 使用量(字节)
|
||||
*/
|
||||
size_t size() const { return bytes_; }
|
||||
|
||||
/**
|
||||
* @brief 记录缓存命中
|
||||
*/
|
||||
void hit() { ++stats_.hits; }
|
||||
|
||||
/**
|
||||
* @brief 记录缓存未命中
|
||||
*/
|
||||
void miss() { ++stats_.misses; }
|
||||
|
||||
/**
|
||||
* @brief 清理无外部引用的资源
|
||||
* @return 清理的资源数量
|
||||
*/
|
||||
size_t purge();
|
||||
|
||||
/**
|
||||
* @brief 清空所有缓存
|
||||
*/
|
||||
void clear();
|
||||
|
||||
/**
|
||||
* @brief 获取缓存统计信息
|
||||
* @return 统计信息
|
||||
*/
|
||||
CacheStats stats() const;
|
||||
|
||||
/**
|
||||
* @brief 重置统计信息
|
||||
*/
|
||||
void resetStats();
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief 缓存条目数据(包含LRU迭代器)
|
||||
*/
|
||||
struct CacheEntryData {
|
||||
Ref<CacheEntry> entry;
|
||||
std::list<AssetID>::iterator lruIterator;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 淘汰资源(LRU策略)
|
||||
*/
|
||||
void evict();
|
||||
|
||||
/**
|
||||
* @brief 检查资源是否可以被淘汰
|
||||
* @param entry 缓存条目
|
||||
* @return 可淘汰返回 true
|
||||
*/
|
||||
bool canEvict(const CacheEntry& entry) const;
|
||||
|
||||
mutable std::shared_mutex mutex_;
|
||||
std::unordered_map<AssetID, CacheEntryData> entries_;
|
||||
std::list<AssetID> lruList_;
|
||||
|
||||
size_t limit_ = 0;
|
||||
size_t bytes_ = 0;
|
||||
mutable CacheStats stats_;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,304 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/asset/asset_types.h>
|
||||
#include <extra2d/core/types.h>
|
||||
#include <memory>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// AssetHandleBase - 资源句柄非模板基类
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 资源句柄非模板基类
|
||||
*
|
||||
* 用于类型擦除,允许在容器中存储不同类型的句柄。
|
||||
*/
|
||||
class AssetHandleBase {
|
||||
public:
|
||||
AssetHandleBase() = default;
|
||||
|
||||
/**
|
||||
* @brief 从资源ID和弱引用构造
|
||||
* @param id 资源ID
|
||||
* @param ref 资源弱引用
|
||||
*/
|
||||
AssetHandleBase(const AssetID& id, Weak<Asset> ref)
|
||||
: id_(id), cacheRef_(std::move(ref)) {}
|
||||
|
||||
/**
|
||||
* @brief 从强引用构造
|
||||
* @param ptr 资源强引用
|
||||
*/
|
||||
explicit AssetHandleBase(Ref<Asset> ptr)
|
||||
: id_(ptr ? ptr->id() : AssetID()),
|
||||
cacheRef_(ptr) {}
|
||||
|
||||
/**
|
||||
* @brief 检查句柄是否有效
|
||||
* @return 有效返回 true
|
||||
*/
|
||||
bool valid() const {
|
||||
return !cacheRef_.expired();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取资源强引用
|
||||
* @return 资源强引用
|
||||
*/
|
||||
Ref<Asset> get() const {
|
||||
return cacheRef_.lock();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取资源ID
|
||||
* @return 资源ID
|
||||
*/
|
||||
const AssetID& id() const { return id_; }
|
||||
|
||||
/**
|
||||
* @brief 获取资源路径
|
||||
* @return 资源路径
|
||||
*/
|
||||
const std::string& path() const { return id_.path; }
|
||||
|
||||
/**
|
||||
* @brief 隐式转换为bool
|
||||
*/
|
||||
explicit operator bool() const { return valid(); }
|
||||
|
||||
/**
|
||||
* @brief 重置句柄
|
||||
*/
|
||||
void reset() {
|
||||
id_ = AssetID();
|
||||
cacheRef_.reset();
|
||||
}
|
||||
|
||||
protected:
|
||||
AssetID id_;
|
||||
Weak<Asset> cacheRef_;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// AssetHandle - 资源句柄
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 资源句柄模板类
|
||||
*
|
||||
* 使用强类型句柄替代裸指针,提供类型安全和自动生命周期管理。
|
||||
* 内部使用 weak_ptr 弱引用,不阻止资源回收。
|
||||
*
|
||||
* 特性:
|
||||
* - 类型安全:编译期检查资源类型
|
||||
* - 自动生命周期:资源无引用时自动回收
|
||||
* - 弱引用:不阻止缓存清理
|
||||
* - 空安全:使用前检查 valid()
|
||||
*
|
||||
* @tparam T 资源类型,必须继承自 Asset
|
||||
*/
|
||||
template<typename T>
|
||||
class AssetHandle : public AssetHandleBase {
|
||||
static_assert(std::is_base_of_v<Asset, T>,
|
||||
"T must derive from Asset");
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief 默认构造函数
|
||||
*/
|
||||
AssetHandle() = default;
|
||||
|
||||
/**
|
||||
* @brief 从基类句柄构造
|
||||
* @param base 基类句柄
|
||||
*/
|
||||
explicit AssetHandle(const AssetHandleBase& base)
|
||||
: AssetHandleBase(base) {}
|
||||
|
||||
/**
|
||||
* @brief 从资源ID和弱引用构造
|
||||
* @param id 资源ID
|
||||
* @param ref 资源弱引用
|
||||
*/
|
||||
AssetHandle(const AssetID& id, Weak<T> ref)
|
||||
: AssetHandleBase(id, std::move(ref)) {}
|
||||
|
||||
/**
|
||||
* @brief 从强引用构造
|
||||
* @param ptr 资源强引用
|
||||
*/
|
||||
explicit AssetHandle(Ref<T> ptr)
|
||||
: AssetHandleBase(ptr) {}
|
||||
|
||||
/**
|
||||
* @brief 获取资源强引用
|
||||
* @return 资源强引用,如果资源已被回收返回 nullptr
|
||||
*/
|
||||
Ref<T> get() const {
|
||||
return std::static_pointer_cast<T>(cacheRef_.lock());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 解引用操作符
|
||||
* @return 资源指针
|
||||
* @note 使用前应检查 valid()
|
||||
*/
|
||||
T* operator->() const {
|
||||
auto ptr = get();
|
||||
return ptr ? ptr.get() : nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 解引用操作符
|
||||
* @return 资源引用
|
||||
* @note 使用前应检查 valid()
|
||||
*/
|
||||
T& operator*() const {
|
||||
auto ptr = get();
|
||||
return *ptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 相等比较
|
||||
* @param other 其他句柄
|
||||
* @return 相等返回 true
|
||||
*/
|
||||
bool operator==(const AssetHandle& other) const {
|
||||
return id_ == other.id_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 不等比较
|
||||
* @param other 其他句柄
|
||||
* @return 不等返回 true
|
||||
*/
|
||||
bool operator!=(const AssetHandle& other) const {
|
||||
return id_ != other.id_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 小于比较(用于有序容器)
|
||||
* @param other 其他句柄
|
||||
* @return 小于返回 true
|
||||
*/
|
||||
bool operator<(const AssetHandle& other) const {
|
||||
return id_ < other.id_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 重置句柄
|
||||
*/
|
||||
void reset() {
|
||||
id_ = AssetID();
|
||||
cacheRef_.reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 检查资源是否已加载
|
||||
* @return 已加载返回 true
|
||||
*/
|
||||
bool loaded() const {
|
||||
auto ptr = get();
|
||||
return ptr && ptr->loaded();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取资源状态
|
||||
* @return 资源状态
|
||||
*/
|
||||
AssetState state() const {
|
||||
auto ptr = get();
|
||||
return ptr ? ptr->state() : AssetState::Unloaded;
|
||||
}
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// AssetLoadResult - 资源加载结果
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 资源加载结果
|
||||
*
|
||||
* 封装资源加载的结果状态,支持成功、失败、加载中等状态。
|
||||
*
|
||||
* @tparam T 资源类型
|
||||
*/
|
||||
template<typename T>
|
||||
struct AssetLoadResult {
|
||||
AssetHandle<T> handle;
|
||||
AssetState state = AssetState::Unloaded;
|
||||
std::string error;
|
||||
|
||||
/**
|
||||
* @brief 检查是否成功
|
||||
* @return 成功返回 true
|
||||
*/
|
||||
bool success() const {
|
||||
return state == AssetState::Loaded && handle.valid();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 检查是否失败
|
||||
* @return 失败返回 true
|
||||
*/
|
||||
bool failed() const {
|
||||
return state == AssetState::Failed;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 检查是否正在加载
|
||||
* @return 正在加载返回 true
|
||||
*/
|
||||
bool loading() const {
|
||||
return state == AssetState::Loading;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 创建成功结果
|
||||
* @param handle 资源句柄
|
||||
* @return 加载结果
|
||||
*/
|
||||
static AssetLoadResult ok(AssetHandle<T> handle) {
|
||||
return { std::move(handle), AssetState::Loaded, "" };
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 创建失败结果
|
||||
* @param error 错误信息
|
||||
* @return 加载结果
|
||||
*/
|
||||
static AssetLoadResult err(const std::string& error) {
|
||||
return { AssetHandle<T>(), AssetState::Failed, error };
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 创建加载中结果
|
||||
* @param handle 资源句柄(可能为空)
|
||||
* @return 加载结果
|
||||
*/
|
||||
static AssetLoadResult pending(AssetHandle<T> handle = {}) {
|
||||
return { std::move(handle), AssetState::Loading, "" };
|
||||
}
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// AssetLoadCallback - 资源加载回调
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 资源加载回调类型
|
||||
* @tparam T 资源类型
|
||||
*/
|
||||
template<typename T>
|
||||
using AssetLoadCallback = Fn<void(AssetHandle<T>)>;
|
||||
|
||||
/**
|
||||
* @brief 资源加载结果回调类型
|
||||
* @tparam T 资源类型
|
||||
*/
|
||||
template<typename T>
|
||||
using AssetLoadResultCallback = Fn<void(AssetLoadResult<T>)>;
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,312 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/asset/asset.h>
|
||||
#include <extra2d/asset/asset_types.h>
|
||||
#include <extra2d/core/types.h>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// AssetLoaderBase - 资源加载器非模板基类
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 资源加载器非模板基类
|
||||
*
|
||||
* 用于类型擦除,允许在容器中存储不同类型的加载器。
|
||||
*/
|
||||
class AssetLoaderBase {
|
||||
public:
|
||||
virtual ~AssetLoaderBase() = default;
|
||||
|
||||
/**
|
||||
* @brief 从文件加载资源(返回 Asset 基类指针)
|
||||
* @param path 文件路径
|
||||
* @return 资源实例
|
||||
*/
|
||||
virtual Ref<Asset> loadBase(const std::string& path) = 0;
|
||||
|
||||
/**
|
||||
* @brief 从内存数据加载资源
|
||||
* @param data 数据指针
|
||||
* @param size 数据大小
|
||||
* @return 资源实例
|
||||
*/
|
||||
virtual Ref<Asset> loadFromMemoryBase(const u8* data, size_t size) = 0;
|
||||
|
||||
/**
|
||||
* @brief 检查是否能加载指定路径的资源
|
||||
* @param path 文件路径
|
||||
* @return 能加载返回 true
|
||||
*/
|
||||
virtual bool canLoad(const std::string& path) const = 0;
|
||||
|
||||
/**
|
||||
* @brief 获取资源类型
|
||||
* @return 资源类型枚举值
|
||||
*/
|
||||
virtual AssetType type() const = 0;
|
||||
|
||||
/**
|
||||
* @brief 获取支持的文件扩展名列表
|
||||
* @return 扩展名列表
|
||||
*/
|
||||
virtual std::vector<std::string> extensions() const = 0;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// AssetLoader - 资源加载器接口
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 资源加载器接口模板
|
||||
*
|
||||
* 使用策略模式支持不同资源类型的加载。
|
||||
* 每种资源类型可以实现自己的加载器。
|
||||
*
|
||||
* @tparam T 资源类型
|
||||
*/
|
||||
template<typename T>
|
||||
class AssetLoader : public AssetLoaderBase {
|
||||
static_assert(std::is_base_of_v<Asset, T>,
|
||||
"T must derive from Asset");
|
||||
|
||||
public:
|
||||
virtual ~AssetLoader() = default;
|
||||
|
||||
/**
|
||||
* @brief 从文件加载资源
|
||||
* @param path 文件路径
|
||||
* @return 资源实例,失败返回 nullptr
|
||||
*/
|
||||
virtual Ref<T> load(const std::string& path) = 0;
|
||||
|
||||
/**
|
||||
* @brief 从内存数据加载资源
|
||||
* @param data 数据指针
|
||||
* @param size 数据大小
|
||||
* @return 资源实例,失败返回 nullptr
|
||||
*/
|
||||
virtual Ref<T> loadFromMemory(const u8* data, size_t size) = 0;
|
||||
|
||||
Ref<Asset> loadBase(const std::string& path) override {
|
||||
return load(path);
|
||||
}
|
||||
|
||||
Ref<Asset> loadFromMemoryBase(const u8* data, size_t size) override {
|
||||
return loadFromMemory(data, size);
|
||||
}
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// TextureLoader - 纹理加载器
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 纹理加载器
|
||||
*
|
||||
* 使用 stb_image 加载各种格式的图片文件。
|
||||
* 支持 PNG, JPG, BMP, TGA, GIF, PSD, HDR, PIC 等格式。
|
||||
*/
|
||||
class TextureLoader : public AssetLoader<TextureAsset> {
|
||||
public:
|
||||
TextureLoader();
|
||||
~TextureLoader() override;
|
||||
|
||||
Ref<TextureAsset> load(const std::string& path) override;
|
||||
Ref<TextureAsset> loadFromMemory(const u8* data, size_t size) override;
|
||||
bool canLoad(const std::string& path) const override;
|
||||
AssetType type() const override { return AssetType::Texture; }
|
||||
std::vector<std::string> extensions() const override;
|
||||
|
||||
/**
|
||||
* @brief 设置期望的通道数
|
||||
* @param channels 通道数(1-4),0表示自动
|
||||
*/
|
||||
void setDesiredChannels(int channels);
|
||||
|
||||
/**
|
||||
* @brief 获取期望的通道数
|
||||
* @return 通道数
|
||||
*/
|
||||
int desiredChannels() const { return desiredChannels_; }
|
||||
|
||||
private:
|
||||
int desiredChannels_ = 4;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// FontLoader - 字体加载器
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 字体加载器
|
||||
*
|
||||
* 加载 TrueType 字体文件(.ttf, .otf)。
|
||||
*/
|
||||
class FontLoader : public AssetLoader<FontAsset> {
|
||||
public:
|
||||
Ref<FontAsset> load(const std::string& path) override;
|
||||
Ref<FontAsset> loadFromMemory(const u8* data, size_t size) override;
|
||||
bool canLoad(const std::string& path) const override;
|
||||
AssetType type() const override { return AssetType::Font; }
|
||||
std::vector<std::string> extensions() const override;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// ShaderLoader - 着色器加载器
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 着色器加载器
|
||||
*
|
||||
* 加载着色器源文件,支持以下格式:
|
||||
* - .vert/.frag: 分离的顶点/片段着色器
|
||||
* - .glsl: 合并的着色器文件(使用标记分隔)
|
||||
*/
|
||||
class ShaderLoader : public AssetLoader<ShaderAsset> {
|
||||
public:
|
||||
Ref<ShaderAsset> load(const std::string& path) override;
|
||||
Ref<ShaderAsset> loadFromMemory(const u8* data, size_t size) override;
|
||||
bool canLoad(const std::string& path) const override;
|
||||
AssetType type() const override { return AssetType::Shader; }
|
||||
std::vector<std::string> extensions() const override;
|
||||
|
||||
/**
|
||||
* @brief 设置顶点着色器标记
|
||||
* @param marker 标记字符串(默认 "[VERTEX]")
|
||||
*/
|
||||
void setVertexMarker(const std::string& marker) { vertexMarker_ = marker; }
|
||||
|
||||
/**
|
||||
* @brief 设置片段着色器标记
|
||||
* @param marker 标记字符串(默认 "[FRAGMENT]")
|
||||
*/
|
||||
void setFragmentMarker(const std::string& marker) { fragmentMarker_ = marker; }
|
||||
|
||||
private:
|
||||
std::string vertexMarker_ = "[VERTEX]";
|
||||
std::string fragmentMarker_ = "[FRAGMENT]";
|
||||
|
||||
/**
|
||||
* @brief 解析合并的着色器文件
|
||||
* @param content 文件内容
|
||||
* @param vertex 输出顶点着色器源码
|
||||
* @param fragment 输出片段着色器源码
|
||||
* @return 成功返回 true
|
||||
*/
|
||||
bool parseCombined(const std::string& content,
|
||||
std::string& vertex,
|
||||
std::string& fragment);
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// AudioLoader - 音频加载器
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 音频加载器
|
||||
*
|
||||
* 加载音频文件,支持 WAV 格式。
|
||||
* 可扩展支持 MP3, OGG 等格式。
|
||||
*/
|
||||
class AudioLoader : public AssetLoader<AudioAsset> {
|
||||
public:
|
||||
Ref<AudioAsset> load(const std::string& path) override;
|
||||
Ref<AudioAsset> loadFromMemory(const u8* data, size_t size) override;
|
||||
bool canLoad(const std::string& path) const override;
|
||||
AssetType type() const override { return AssetType::Audio; }
|
||||
std::vector<std::string> extensions() const override;
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief 加载 WAV 格式音频
|
||||
* @param data 数据指针
|
||||
* @param size 数据大小
|
||||
* @return 音频资源
|
||||
*/
|
||||
Ref<AudioAsset> loadWav(const u8* data, size_t size);
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// DataLoader - 通用数据加载器
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 通用数据加载器
|
||||
*
|
||||
* 加载任意二进制数据文件。
|
||||
*/
|
||||
class DataLoader : public AssetLoader<DataAsset> {
|
||||
public:
|
||||
Ref<DataAsset> load(const std::string& path) override;
|
||||
Ref<DataAsset> loadFromMemory(const u8* data, size_t size) override;
|
||||
bool canLoad(const std::string& path) const override;
|
||||
AssetType type() const override { return AssetType::Data; }
|
||||
std::vector<std::string> extensions() const override;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// AssetLoaderFactory - 加载器工厂
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 加载器工厂
|
||||
*
|
||||
* 根据资源类型或文件扩展名创建对应的加载器。
|
||||
* 使用模板方法返回具体类型的加载器。
|
||||
*/
|
||||
class AssetLoaderFactory {
|
||||
public:
|
||||
/**
|
||||
* @brief 根据资源类型创建纹理加载器
|
||||
* @return 纹理加载器实例
|
||||
*/
|
||||
static Unique<TextureLoader> createTextureLoader() {
|
||||
return ptr::makeUnique<TextureLoader>();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 根据资源类型创建字体加载器
|
||||
* @return 字体加载器实例
|
||||
*/
|
||||
static Unique<FontLoader> createFontLoader() {
|
||||
return ptr::makeUnique<FontLoader>();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 根据资源类型创建着色器加载器
|
||||
* @return 着色器加载器实例
|
||||
*/
|
||||
static Unique<ShaderLoader> createShaderLoader() {
|
||||
return ptr::makeUnique<ShaderLoader>();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 根据资源类型创建音频加载器
|
||||
* @return 音频加载器实例
|
||||
*/
|
||||
static Unique<AudioLoader> createAudioLoader() {
|
||||
return ptr::makeUnique<AudioLoader>();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 根据资源类型创建数据加载器
|
||||
* @return 数据加载器实例
|
||||
*/
|
||||
static Unique<DataLoader> createDataLoader() {
|
||||
return ptr::makeUnique<DataLoader>();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 根据文件扩展名获取资源类型
|
||||
* @param extension 文件扩展名(包含点,如 ".png")
|
||||
* @return 资源类型,无法识别返回 AssetType::Unknown
|
||||
*/
|
||||
static AssetType getTypeByExtension(const std::string& extension);
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,399 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/asset/asset_types.h>
|
||||
#include <extra2d/asset/data_processor.h>
|
||||
#include <extra2d/core/types.h>
|
||||
#include <fstream>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// AssetPack - 资源包
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 资源包类
|
||||
*
|
||||
* 支持从打包文件中加载资源。
|
||||
* 资源包格式:
|
||||
* - 头部:AssetPackageHeader
|
||||
* - 索引表:条目数量 + AssetPackageEntry 数组
|
||||
* - 数据区:按索引顺序存储的资源数据
|
||||
*/
|
||||
class AssetPack {
|
||||
public:
|
||||
AssetPack() = default;
|
||||
|
||||
/**
|
||||
* @brief 移动构造函数
|
||||
* @param other 其他资源包
|
||||
*/
|
||||
AssetPack(AssetPack&& other) noexcept;
|
||||
|
||||
/**
|
||||
* @brief 移动赋值操作符
|
||||
* @param other 其他资源包
|
||||
* @return 当前资源包引用
|
||||
*/
|
||||
AssetPack& operator=(AssetPack&& other) noexcept;
|
||||
|
||||
/**
|
||||
* @brief 析构函数,自动关闭文件
|
||||
*/
|
||||
~AssetPack();
|
||||
|
||||
/**
|
||||
* @brief 打开资源包
|
||||
* @param path 资源包文件路径
|
||||
* @return 成功返回 true
|
||||
*/
|
||||
bool open(const std::string& path);
|
||||
|
||||
/**
|
||||
* @brief 关闭资源包
|
||||
*/
|
||||
void close();
|
||||
|
||||
/**
|
||||
* @brief 检查资源包是否已打开
|
||||
* @return 已打开返回 true
|
||||
*/
|
||||
bool isOpen() const { return file_.is_open(); }
|
||||
|
||||
/**
|
||||
* @brief 检查资源是否存在
|
||||
* @param id 资源ID
|
||||
* @return 存在返回 true
|
||||
*/
|
||||
bool has(const AssetID& id) const;
|
||||
|
||||
/**
|
||||
* @brief 检查资源是否存在
|
||||
* @param path 资源路径
|
||||
* @return 存在返回 true
|
||||
*/
|
||||
bool has(const std::string& path) const;
|
||||
|
||||
/**
|
||||
* @brief 读取资源数据(自动解压/解密)
|
||||
* @param id 资源ID
|
||||
* @return 资源数据,失败返回空
|
||||
*/
|
||||
std::vector<u8> read(const AssetID& id);
|
||||
|
||||
/**
|
||||
* @brief 读取资源数据(自动解压/解密)
|
||||
* @param path 资源路径
|
||||
* @return 资源数据,失败返回空
|
||||
*/
|
||||
std::vector<u8> read(const std::string& path);
|
||||
|
||||
/**
|
||||
* @brief 读取原始资源数据(不解压/不解密)
|
||||
* @param id 资源ID
|
||||
* @return 原始资源数据
|
||||
*/
|
||||
std::vector<u8> readRaw(const AssetID& id);
|
||||
|
||||
/**
|
||||
* @brief 获取所有资源ID
|
||||
* @return 资源ID列表
|
||||
*/
|
||||
std::vector<AssetID> assets() const;
|
||||
|
||||
/**
|
||||
* @brief 获取资源包路径
|
||||
* @return 资源包路径
|
||||
*/
|
||||
const std::string& path() const { return path_; }
|
||||
|
||||
/**
|
||||
* @brief 获取资源包头部
|
||||
* @return 头部信息
|
||||
*/
|
||||
const AssetPackageHeader& header() const { return header_; }
|
||||
|
||||
/**
|
||||
* @brief 获取资源条目数量
|
||||
* @return 条目数量
|
||||
*/
|
||||
size_t count() const { return entries_.size(); }
|
||||
|
||||
/**
|
||||
* @brief 设置数据处理管道
|
||||
* @param pipe 处理管道
|
||||
*/
|
||||
void setPipe(DataPipe pipe) { pipe_ = std::move(pipe); }
|
||||
|
||||
/**
|
||||
* @brief 获取资源条目信息
|
||||
* @param id 资源ID
|
||||
* @return 条目指针,不存在返回 nullptr
|
||||
*/
|
||||
const AssetPackageEntry* getEntry(const AssetID& id) const;
|
||||
|
||||
private:
|
||||
std::string path_;
|
||||
mutable std::ifstream file_;
|
||||
AssetPackageHeader header_;
|
||||
std::unordered_map<AssetID, AssetPackageEntry> entries_;
|
||||
DataPipe pipe_;
|
||||
|
||||
/**
|
||||
* @brief 读取并解析头部
|
||||
* @return 成功返回 true
|
||||
*/
|
||||
bool readHeader();
|
||||
|
||||
/**
|
||||
* @brief 读取并解析索引表
|
||||
* @return 成功返回 true
|
||||
*/
|
||||
bool readIndex();
|
||||
|
||||
/**
|
||||
* @brief 读取条目数据
|
||||
* @param entry 条目信息
|
||||
* @return 原始数据
|
||||
*/
|
||||
std::vector<u8> readEntryData(const AssetPackageEntry& entry);
|
||||
|
||||
AssetPack(const AssetPack&) = delete;
|
||||
AssetPack& operator=(const AssetPack&) = delete;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// PackManager - 资源包管理器
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 资源包管理器
|
||||
*
|
||||
* 管理多个资源包,支持资源查找和加载。
|
||||
* 支持挂载/卸载资源包。
|
||||
*/
|
||||
class PackManager {
|
||||
public:
|
||||
PackManager() = default;
|
||||
|
||||
/**
|
||||
* @brief 析构函数,自动卸载所有资源包
|
||||
*/
|
||||
~PackManager() = default;
|
||||
|
||||
/**
|
||||
* @brief 挂载资源包
|
||||
* @param path 资源包路径
|
||||
* @return 成功返回 true
|
||||
*/
|
||||
bool mount(const std::string& path);
|
||||
|
||||
/**
|
||||
* @brief 卸载资源包
|
||||
* @param path 资源包路径
|
||||
*/
|
||||
void unmount(const std::string& path);
|
||||
|
||||
/**
|
||||
* @brief 卸载所有资源包
|
||||
*/
|
||||
void unmountAll();
|
||||
|
||||
/**
|
||||
* @brief 查找资源所在的包
|
||||
* @param id 资源ID
|
||||
* @return 资源包指针,未找到返回 nullptr
|
||||
*/
|
||||
AssetPack* find(const AssetID& id);
|
||||
|
||||
/**
|
||||
* @brief 查找资源所在的包
|
||||
* @param path 资源路径
|
||||
* @return 资源包指针,未找到返回 nullptr
|
||||
*/
|
||||
AssetPack* find(const std::string& path);
|
||||
|
||||
/**
|
||||
* @brief 检查资源是否存在
|
||||
* @param id 资源ID
|
||||
* @return 存在返回 true
|
||||
*/
|
||||
bool has(const AssetID& id) const;
|
||||
|
||||
/**
|
||||
* @brief 检查资源是否存在
|
||||
* @param path 资源路径
|
||||
* @return 存在返回 true
|
||||
*/
|
||||
bool has(const std::string& path) const;
|
||||
|
||||
/**
|
||||
* @brief 读取资源数据
|
||||
* @param id 资源ID
|
||||
* @return 资源数据
|
||||
*/
|
||||
std::vector<u8> read(const AssetID& id);
|
||||
|
||||
/**
|
||||
* @brief 读取资源数据
|
||||
* @param path 资源路径
|
||||
* @return 资源数据
|
||||
*/
|
||||
std::vector<u8> read(const std::string& path);
|
||||
|
||||
/**
|
||||
* @brief 设置默认处理管道
|
||||
* @param pipe 处理管道
|
||||
*/
|
||||
void setPipe(DataPipe pipe) { defaultPipe_ = std::move(pipe); }
|
||||
|
||||
/**
|
||||
* @brief 获取默认处理管道
|
||||
* @return 处理管道引用
|
||||
*/
|
||||
const DataPipe& pipe() const { return defaultPipe_; }
|
||||
|
||||
/**
|
||||
* @brief 获取已挂载的资源包数量
|
||||
* @return 资源包数量
|
||||
*/
|
||||
size_t count() const { return packs_.size(); }
|
||||
|
||||
/**
|
||||
* @brief 获取所有资源ID
|
||||
* @return 资源ID列表
|
||||
*/
|
||||
std::vector<AssetID> allAssets() const;
|
||||
|
||||
/**
|
||||
* @brief 获取已挂载的资源包路径列表
|
||||
* @return 路径列表
|
||||
*/
|
||||
std::vector<std::string> mountedPacks() const;
|
||||
|
||||
private:
|
||||
std::vector<Unique<AssetPack>> packs_;
|
||||
DataPipe defaultPipe_;
|
||||
|
||||
PackManager(const PackManager&) = delete;
|
||||
PackManager& operator=(const PackManager&) = delete;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// AssetPackBuilder - 资源包构建器(用于打包工具)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 资源包构建器
|
||||
*
|
||||
* 用于创建资源包文件。
|
||||
* 支持添加资源、压缩、加密等操作。
|
||||
*/
|
||||
class AssetPackBuilder {
|
||||
public:
|
||||
/**
|
||||
* @brief 构造函数
|
||||
* @param compression 压缩算法
|
||||
* @param level 压缩级别
|
||||
*/
|
||||
explicit AssetPackBuilder(Compression compression = Compression::None, int level = 3);
|
||||
|
||||
/**
|
||||
* @brief 添加资源
|
||||
* @param path 资源路径(包内路径)
|
||||
* @param data 资源数据
|
||||
*/
|
||||
void add(const std::string& path, const std::vector<u8>& data);
|
||||
|
||||
/**
|
||||
* @brief 添加资源(移动语义)
|
||||
* @param path 资源路径
|
||||
* @param data 资源数据
|
||||
*/
|
||||
void add(const std::string& path, std::vector<u8>&& data);
|
||||
|
||||
/**
|
||||
* @brief 添加文件
|
||||
* @param filePath 文件路径
|
||||
* @param packPath 包内路径(可选,默认使用文件名)
|
||||
* @return 成功返回 true
|
||||
*/
|
||||
bool addFile(const std::string& filePath, const std::string& packPath = "");
|
||||
|
||||
/**
|
||||
* @brief 添加目录
|
||||
* @param dirPath 目录路径
|
||||
* @param prefix 包内路径前缀(可选)
|
||||
* @return 添加的文件数量
|
||||
*/
|
||||
size_t addDirectory(const std::string& dirPath, const std::string& prefix = "");
|
||||
|
||||
/**
|
||||
* @brief 设置加密密钥
|
||||
* @param key 加密密钥
|
||||
* @param type 加密类型
|
||||
*/
|
||||
void setEncryption(const std::string& key, Decryptor::Type type = Decryptor::Type::XOR);
|
||||
|
||||
/**
|
||||
* @brief 构建资源包
|
||||
* @param outputPath 输出文件路径
|
||||
* @return 成功返回 true
|
||||
*/
|
||||
bool build(const std::string& outputPath);
|
||||
|
||||
/**
|
||||
* @brief 清空所有资源
|
||||
*/
|
||||
void clear();
|
||||
|
||||
/**
|
||||
* @brief 获取资源数量
|
||||
* @return 资源数量
|
||||
*/
|
||||
size_t count() const { return entries_.size(); }
|
||||
|
||||
/**
|
||||
* @brief 获取总原始大小
|
||||
* @return 总原始大小
|
||||
*/
|
||||
size_t totalOriginalSize() const { return totalOriginalSize_; }
|
||||
|
||||
/**
|
||||
* @brief 获取总压缩大小
|
||||
* @return 总压缩大小
|
||||
*/
|
||||
size_t totalCompressedSize() const { return totalCompressedSize_; }
|
||||
|
||||
private:
|
||||
struct BuilderEntry {
|
||||
AssetID id;
|
||||
std::vector<u8> data;
|
||||
std::vector<u8> compressedData;
|
||||
u64 offset;
|
||||
u32 compression;
|
||||
u32 flags;
|
||||
};
|
||||
|
||||
Compression compression_;
|
||||
int level_;
|
||||
std::string encryptKey_;
|
||||
Decryptor::Type encryptType_ = Decryptor::Type::None;
|
||||
std::vector<BuilderEntry> entries_;
|
||||
size_t totalOriginalSize_ = 0;
|
||||
size_t totalCompressedSize_ = 0;
|
||||
|
||||
/**
|
||||
* @brief 处理数据(压缩/加密)
|
||||
* @param data 原始数据
|
||||
* @return 处理后的数据
|
||||
*/
|
||||
std::vector<u8> processData(const std::vector<u8>& data);
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,270 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/core/types.h>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// AssetID - 强类型资源标识符
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 强类型资源ID
|
||||
*
|
||||
* 使用类型安全的ID替代裸字符串,支持:
|
||||
* - 哈希计算(用于快速比较和查找)
|
||||
* - 原始路径存储(用于调试和日志)
|
||||
* - 隐式转换和比较操作
|
||||
*/
|
||||
struct AssetID {
|
||||
u64 hash = 0;
|
||||
std::string path;
|
||||
|
||||
AssetID() = default;
|
||||
|
||||
/**
|
||||
* @brief 从路径构造资源ID
|
||||
* @param p 资源路径
|
||||
*/
|
||||
explicit AssetID(const std::string& p)
|
||||
: hash(hashPath(p)), path(p) {}
|
||||
|
||||
/**
|
||||
* @brief 从路径构造资源ID(移动语义)
|
||||
* @param p 资源路径
|
||||
*/
|
||||
explicit AssetID(std::string&& p)
|
||||
: hash(hashPath(p)), path(std::move(p)) {}
|
||||
|
||||
/**
|
||||
* @brief 计算路径哈希值
|
||||
* @param p 路径字符串
|
||||
* @return 64位哈希值
|
||||
*/
|
||||
static u64 hashPath(const std::string& p) {
|
||||
u64 result = 14695981039346656037ULL;
|
||||
for (char c : p) {
|
||||
result ^= static_cast<u64>(static_cast<u8>(c));
|
||||
result *= 1099511628211ULL;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 检查ID是否有效
|
||||
* @return 有效返回 true
|
||||
*/
|
||||
bool valid() const { return hash != 0 || !path.empty(); }
|
||||
|
||||
/**
|
||||
* @brief 布尔转换操作符
|
||||
*/
|
||||
explicit operator bool() const { return valid(); }
|
||||
|
||||
/**
|
||||
* @brief 相等比较
|
||||
*/
|
||||
bool operator==(const AssetID& other) const { return hash == other.hash; }
|
||||
|
||||
/**
|
||||
* @brief 不等比较
|
||||
*/
|
||||
bool operator!=(const AssetID& other) const { return hash != other.hash; }
|
||||
|
||||
/**
|
||||
* @brief 小于比较(用于有序容器)
|
||||
*/
|
||||
bool operator<(const AssetID& other) const { return hash < other.hash; }
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// AssetType - 资源类型枚举
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 资源类型枚举
|
||||
* 定义支持的资源类型,用于类型安全的资源加载和管理
|
||||
*/
|
||||
enum class AssetType : u8 {
|
||||
Unknown = 0,
|
||||
Texture = 1,
|
||||
Font = 2,
|
||||
Shader = 3,
|
||||
Audio = 4,
|
||||
Data = 5,
|
||||
Custom = 255
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 获取资源类型名称
|
||||
* @param type 资源类型
|
||||
* @return 类型名称字符串
|
||||
*/
|
||||
inline const char* assetTypeName(AssetType type) {
|
||||
switch (type) {
|
||||
case AssetType::Texture: return "Texture";
|
||||
case AssetType::Font: return "Font";
|
||||
case AssetType::Shader: return "Shader";
|
||||
case AssetType::Audio: return "Audio";
|
||||
case AssetType::Data: return "Data";
|
||||
case AssetType::Custom: return "Custom";
|
||||
default: return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// AssetState - 资源状态枚举
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 资源状态枚举
|
||||
* 定义资源的生命周期状态
|
||||
*/
|
||||
enum class AssetState : u8 {
|
||||
Unloaded = 0,
|
||||
Loading = 1,
|
||||
Loaded = 2,
|
||||
Failed = 3,
|
||||
Unloading = 4
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 获取资源状态名称
|
||||
* @param state 资源状态
|
||||
* @return 状态名称字符串
|
||||
*/
|
||||
inline const char* assetStateName(AssetState state) {
|
||||
switch (state) {
|
||||
case AssetState::Unloaded: return "Unloaded";
|
||||
case AssetState::Loading: return "Loading";
|
||||
case AssetState::Loaded: return "Loaded";
|
||||
case AssetState::Failed: return "Failed";
|
||||
case AssetState::Unloading: return "Unloading";
|
||||
default: return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Compression - 压缩算法枚举
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 压缩算法枚举
|
||||
* 定义支持的压缩算法类型
|
||||
*/
|
||||
enum class Compression : u8 {
|
||||
None = 0,
|
||||
Zstd = 1,
|
||||
LZ4 = 2,
|
||||
Zlib = 3
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 获取压缩算法名称
|
||||
* @param comp 压缩算法
|
||||
* @return 算法名称字符串
|
||||
*/
|
||||
inline const char* compressionName(Compression comp) {
|
||||
switch (comp) {
|
||||
case Compression::Zstd: return "Zstd";
|
||||
case Compression::LZ4: return "LZ4";
|
||||
case Compression::Zlib: return "Zlib";
|
||||
default: return "None";
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// CacheStats - 缓存统计结构
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 缓存统计信息
|
||||
* 用于监控资源缓存的使用情况和性能
|
||||
*/
|
||||
struct CacheStats {
|
||||
size_t bytes = 0;
|
||||
size_t limit = 0;
|
||||
size_t count = 0;
|
||||
size_t hits = 0;
|
||||
size_t misses = 0;
|
||||
|
||||
/**
|
||||
* @brief 计算缓存命中率
|
||||
* @return 命中率(0.0 - 1.0)
|
||||
*/
|
||||
float hitRate() const {
|
||||
size_t total = hits + misses;
|
||||
return total > 0 ? static_cast<float>(hits) / static_cast<float>(total) : 0.0f;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 计算缓存使用率
|
||||
* @return 使用率(0.0 - 1.0)
|
||||
*/
|
||||
float usage() const {
|
||||
return limit > 0 ? static_cast<float>(bytes) / static_cast<float>(limit) : 0.0f;
|
||||
}
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// AssetPackageHeader - 资源包头部信息
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 资源包头部信息
|
||||
* 用于识别压缩和加密类型
|
||||
*/
|
||||
struct AssetPackageHeader {
|
||||
u32 magic = 0;
|
||||
u32 version = 0;
|
||||
u32 compressionType = 0;
|
||||
u32 encryptionType = 0;
|
||||
u64 originalSize = 0;
|
||||
u64 compressedSize = 0;
|
||||
u8 checksum[32] = {};
|
||||
|
||||
static constexpr u32 MAGIC = 0x4B325045; // 'E2PK'
|
||||
|
||||
/**
|
||||
* @brief 检查头部是否有效
|
||||
* @return 有效返回 true
|
||||
*/
|
||||
bool valid() const { return magic == MAGIC; }
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// AssetPackageEntry - 资源包索引项
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 资源包索引项
|
||||
* 描述资源在包中的位置和属性
|
||||
*/
|
||||
struct AssetPackageEntry {
|
||||
AssetID id;
|
||||
u64 offset = 0;
|
||||
u64 size = 0;
|
||||
u64 originalSize = 0;
|
||||
u32 compression = 0;
|
||||
u32 flags = 0;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// std::hash 特化 - 支持在 unordered_map/unordered_set 中使用 AssetID
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
namespace std {
|
||||
|
||||
template<>
|
||||
struct hash<extra2d::AssetID> {
|
||||
size_t operator()(const extra2d::AssetID& id) const noexcept {
|
||||
return static_cast<size_t>(id.hash);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,397 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/asset/asset_types.h>
|
||||
#include <extra2d/core/types.h>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// DataProcessor - 数据处理器接口(装饰器模式)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 数据处理器接口
|
||||
*
|
||||
* 使用装饰器模式支持链式处理数据流。
|
||||
* 支持压缩、解压、加密、解密等操作的组合。
|
||||
*/
|
||||
class DataProcessor {
|
||||
public:
|
||||
virtual ~DataProcessor() = default;
|
||||
|
||||
/**
|
||||
* @brief 处理输入数据
|
||||
* @param input 输入数据
|
||||
* @return 处理后的数据
|
||||
*/
|
||||
virtual std::vector<u8> process(const std::vector<u8>& input) = 0;
|
||||
|
||||
/**
|
||||
* @brief 设置下一个处理器
|
||||
* @param next 下一个处理器
|
||||
*/
|
||||
void setNext(Unique<DataProcessor> next) { next_ = std::move(next); }
|
||||
|
||||
/**
|
||||
* @brief 获取下一个处理器
|
||||
* @return 下一个处理器的指针
|
||||
*/
|
||||
DataProcessor* next() const { return next_.get(); }
|
||||
|
||||
protected:
|
||||
/**
|
||||
* @brief 调用下一个处理器处理数据
|
||||
* @param input 输入数据
|
||||
* @return 处理后的数据,如果没有下一个处理器则返回原数据
|
||||
*/
|
||||
std::vector<u8> processNext(const std::vector<u8>& input) {
|
||||
return next_ ? next_->process(input) : input;
|
||||
}
|
||||
|
||||
Unique<DataProcessor> next_;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Decryptor - 解密器
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 解密处理器
|
||||
*
|
||||
* 使用 XOR 或 AES 算法解密数据。
|
||||
* 支持简单的 XOR 加密和 AES-256 加密。
|
||||
*/
|
||||
class Decryptor : public DataProcessor {
|
||||
public:
|
||||
/**
|
||||
* @brief 加密类型枚举
|
||||
*/
|
||||
enum class Type : u8 {
|
||||
None = 0,
|
||||
XOR = 1,
|
||||
AES256 = 2
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 构造解密器
|
||||
* @param key 解密密钥
|
||||
* @param type 加密类型
|
||||
*/
|
||||
explicit Decryptor(const std::string& key, Type type = Type::XOR);
|
||||
|
||||
/**
|
||||
* @brief 处理(解密)输入数据
|
||||
* @param input 加密的输入数据
|
||||
* @return 解密后的数据
|
||||
*/
|
||||
std::vector<u8> process(const std::vector<u8>& input) override;
|
||||
|
||||
/**
|
||||
* @brief 获取加密类型
|
||||
* @return 加密类型
|
||||
*/
|
||||
Type type() const { return type_; }
|
||||
|
||||
private:
|
||||
std::string key_;
|
||||
Type type_;
|
||||
|
||||
/**
|
||||
* @brief XOR 解密
|
||||
* @param input 输入数据
|
||||
* @return 解密后的数据
|
||||
*/
|
||||
std::vector<u8> decryptXOR(const std::vector<u8>& input);
|
||||
|
||||
/**
|
||||
* @brief AES-256 解密
|
||||
* @param input 输入数据
|
||||
* @return 解密后的数据
|
||||
*/
|
||||
std::vector<u8> decryptAES256(const std::vector<u8>& input);
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Decompressor - 解压器
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 解压处理器
|
||||
*
|
||||
* 支持多种压缩算法的解压操作。
|
||||
* 目前支持 Zstd、LZ4 和 Zlib。
|
||||
*/
|
||||
class Decompressor : public DataProcessor {
|
||||
public:
|
||||
/**
|
||||
* @brief 构造解压器
|
||||
* @param algo 压缩算法
|
||||
*/
|
||||
explicit Decompressor(Compression algo = Compression::Zstd);
|
||||
|
||||
/**
|
||||
* @brief 处理(解压)输入数据
|
||||
* @param input 压缩的输入数据
|
||||
* @return 解压后的数据
|
||||
*/
|
||||
std::vector<u8> process(const std::vector<u8>& input) override;
|
||||
|
||||
/**
|
||||
* @brief 获取压缩算法
|
||||
* @return 压缩算法
|
||||
*/
|
||||
Compression algorithm() const { return algo_; }
|
||||
|
||||
private:
|
||||
Compression algo_;
|
||||
|
||||
/**
|
||||
* @brief Zstd 解压
|
||||
* @param input 压缩数据
|
||||
* @return 解压后的数据
|
||||
*/
|
||||
std::vector<u8> decompressZstd(const std::vector<u8>& input);
|
||||
|
||||
/**
|
||||
* @brief LZ4 解压
|
||||
* @param input 压缩数据
|
||||
* @return 解压后的数据
|
||||
*/
|
||||
std::vector<u8> decompressLZ4(const std::vector<u8>& input);
|
||||
|
||||
/**
|
||||
* @brief Zlib 解压
|
||||
* @param input 压缩数据
|
||||
* @return 解压后的数据
|
||||
*/
|
||||
std::vector<u8> decompressZlib(const std::vector<u8>& input);
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Encryptor - 加密器
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 加密处理器
|
||||
*
|
||||
* 使用 XOR 或 AES 算法加密数据。
|
||||
*/
|
||||
class Encryptor : public DataProcessor {
|
||||
public:
|
||||
/**
|
||||
* @brief 构造加密器
|
||||
* @param key 加密密钥
|
||||
* @param type 加密类型
|
||||
*/
|
||||
explicit Encryptor(const std::string& key, Decryptor::Type type = Decryptor::Type::XOR);
|
||||
|
||||
/**
|
||||
* @brief 处理(加密)输入数据
|
||||
* @param input 原始输入数据
|
||||
* @return 加密后的数据
|
||||
*/
|
||||
std::vector<u8> process(const std::vector<u8>& input) override;
|
||||
|
||||
/**
|
||||
* @brief 获取加密类型
|
||||
* @return 加密类型
|
||||
*/
|
||||
Decryptor::Type type() const { return type_; }
|
||||
|
||||
private:
|
||||
std::string key_;
|
||||
Decryptor::Type type_;
|
||||
|
||||
/**
|
||||
* @brief XOR 加密
|
||||
* @param input 输入数据
|
||||
* @return 加密后的数据
|
||||
*/
|
||||
std::vector<u8> encryptXOR(const std::vector<u8>& input);
|
||||
|
||||
/**
|
||||
* @brief AES-256 加密
|
||||
* @param input 输入数据
|
||||
* @return 加密后的数据
|
||||
*/
|
||||
std::vector<u8> encryptAES256(const std::vector<u8>& input);
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Compressor - 压缩器
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 压缩处理器
|
||||
*
|
||||
* 支持多种压缩算法的压缩操作。
|
||||
*/
|
||||
class Compressor : public DataProcessor {
|
||||
public:
|
||||
/**
|
||||
* @brief 构造压缩器
|
||||
* @param algo 压缩算法
|
||||
* @param level 压缩级别(1-22,仅对某些算法有效)
|
||||
*/
|
||||
explicit Compressor(Compression algo = Compression::Zstd, int level = 3);
|
||||
|
||||
/**
|
||||
* @brief 处理(压缩)输入数据
|
||||
* @param input 原始输入数据
|
||||
* @return 压缩后的数据
|
||||
*/
|
||||
std::vector<u8> process(const std::vector<u8>& input) override;
|
||||
|
||||
/**
|
||||
* @brief 获取压缩算法
|
||||
* @return 压缩算法
|
||||
*/
|
||||
Compression algorithm() const { return algo_; }
|
||||
|
||||
private:
|
||||
Compression algo_;
|
||||
int level_;
|
||||
|
||||
/**
|
||||
* @brief Zstd 压缩
|
||||
* @param input 原始数据
|
||||
* @return 压缩后的数据
|
||||
*/
|
||||
std::vector<u8> compressZstd(const std::vector<u8>& input);
|
||||
|
||||
/**
|
||||
* @brief LZ4 压缩
|
||||
* @param input 原始数据
|
||||
* @return 压缩后的数据
|
||||
*/
|
||||
std::vector<u8> compressLZ4(const std::vector<u8>& input);
|
||||
|
||||
/**
|
||||
* @brief Zlib 压缩
|
||||
* @param input 原始数据
|
||||
* @return 压缩后的数据
|
||||
*/
|
||||
std::vector<u8> compressZlib(const std::vector<u8>& input);
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// DataPipe - 数据处理管道
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 数据处理管道
|
||||
*
|
||||
* 使用流式 API 构建数据处理链。
|
||||
* 支持链式调用添加多个处理器。
|
||||
*
|
||||
* @example
|
||||
* DataPipe pipe;
|
||||
* pipe.decrypt("secret-key").decompress(Compression::Zstd);
|
||||
* auto result = pipe.process(data);
|
||||
*/
|
||||
class DataPipe {
|
||||
public:
|
||||
DataPipe() = default;
|
||||
|
||||
/**
|
||||
* @brief 移动构造函数
|
||||
* @param other 其他管道
|
||||
*/
|
||||
DataPipe(DataPipe&& other) noexcept = default;
|
||||
|
||||
/**
|
||||
* @brief 移动赋值操作符
|
||||
* @param other 其他管道
|
||||
* @return 当前管道引用
|
||||
*/
|
||||
DataPipe& operator=(DataPipe&& other) noexcept = default;
|
||||
|
||||
/**
|
||||
* @brief 添加解密处理器
|
||||
* @param key 解密密钥
|
||||
* @param type 加密类型
|
||||
* @return 当前管道引用(支持链式调用)
|
||||
*/
|
||||
DataPipe& decrypt(const std::string& key, Decryptor::Type type = Decryptor::Type::XOR);
|
||||
|
||||
/**
|
||||
* @brief 添加解压处理器
|
||||
* @param algo 压缩算法
|
||||
* @return 当前管道引用(支持链式调用)
|
||||
*/
|
||||
DataPipe& decompress(Compression algo);
|
||||
|
||||
/**
|
||||
* @brief 添加加密处理器
|
||||
* @param key 加密密钥
|
||||
* @param type 加密类型
|
||||
* @return 当前管道引用(支持链式调用)
|
||||
*/
|
||||
DataPipe& encrypt(const std::string& key, Decryptor::Type type = Decryptor::Type::XOR);
|
||||
|
||||
/**
|
||||
* @brief 添加压缩处理器
|
||||
* @param algo 压缩算法
|
||||
* @param level 压缩级别
|
||||
* @return 当前管道引用(支持链式调用)
|
||||
*/
|
||||
DataPipe& compress(Compression algo, int level = 3);
|
||||
|
||||
/**
|
||||
* @brief 添加自定义处理器
|
||||
* @param processor 处理器
|
||||
* @return 当前管道引用(支持链式调用)
|
||||
*/
|
||||
DataPipe& add(Unique<DataProcessor> processor);
|
||||
|
||||
/**
|
||||
* @brief 处理数据
|
||||
* @param input 输入数据
|
||||
* @return 处理后的数据
|
||||
*/
|
||||
std::vector<u8> process(const std::vector<u8>& input);
|
||||
|
||||
/**
|
||||
* @brief 清空所有处理器
|
||||
*/
|
||||
void clear();
|
||||
|
||||
/**
|
||||
* @brief 检查管道是否为空
|
||||
* @return 为空返回 true
|
||||
*/
|
||||
bool empty() const { return processors_.empty(); }
|
||||
|
||||
/**
|
||||
* @brief 获取处理器数量
|
||||
* @return 处理器数量
|
||||
*/
|
||||
size_t size() const { return processors_.size(); }
|
||||
|
||||
private:
|
||||
std::vector<Unique<DataProcessor>> processors_;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 工具函数
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 计算数据的 SHA-256 校验和
|
||||
* @param data 输入数据
|
||||
* @return 32字节的校验和
|
||||
*/
|
||||
std::vector<u8> computeChecksum(const std::vector<u8>& data);
|
||||
|
||||
/**
|
||||
* @brief 验证数据的 SHA-256 校验和
|
||||
* @param data 输入数据
|
||||
* @param checksum 预期的校验和
|
||||
* @return 匹配返回 true
|
||||
*/
|
||||
bool verifyChecksum(const std::vector<u8>& data, const std::vector<u8>& checksum);
|
||||
|
||||
}
|
||||
|
|
@ -25,10 +25,20 @@
|
|||
#include <extra2d/utils/timer.h>
|
||||
|
||||
// Services
|
||||
#include <extra2d/services/asset_service.h>
|
||||
#include <extra2d/services/event_service.h>
|
||||
#include <extra2d/services/logger_service.h>
|
||||
#include <extra2d/services/timer_service.h>
|
||||
|
||||
// Asset
|
||||
#include <extra2d/asset/asset.h>
|
||||
#include <extra2d/asset/asset_cache.h>
|
||||
#include <extra2d/asset/asset_handle.h>
|
||||
#include <extra2d/asset/asset_loader.h>
|
||||
#include <extra2d/asset/asset_pack.h>
|
||||
#include <extra2d/asset/asset_types.h>
|
||||
#include <extra2d/asset/data_processor.h>
|
||||
|
||||
// Application
|
||||
#include <extra2d/app/application.h>
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,334 @@
|
|||
#pragma once
|
||||
|
||||
#include <condition_variable>
|
||||
#include <extra2d/asset/asset.h>
|
||||
#include <extra2d/asset/asset_cache.h>
|
||||
#include <extra2d/asset/asset_handle.h>
|
||||
#include <extra2d/asset/asset_loader.h>
|
||||
#include <extra2d/asset/asset_pack.h>
|
||||
#include <extra2d/asset/asset_types.h>
|
||||
#include <extra2d/asset/data_processor.h>
|
||||
#include <extra2d/core/service_interface.h>
|
||||
#include <extra2d/core/service_locator.h>
|
||||
#include <extra2d/core/types.h>
|
||||
#include <functional>
|
||||
#include <future>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
#include <shared_mutex>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <typeindex>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// IAssetService - 资源服务接口
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 资源服务接口
|
||||
*
|
||||
* 提供资源加载、缓存、异步加载等功能。
|
||||
* 使用模板方法支持类型安全的资源加载。
|
||||
*/
|
||||
class IAssetService : public IService {
|
||||
public:
|
||||
virtual ~IAssetService() = default;
|
||||
|
||||
/**
|
||||
* @brief 同步加载资源
|
||||
* @tparam T 资源类型
|
||||
* @param path 资源路径
|
||||
* @return 资源句柄
|
||||
*/
|
||||
template <typename T> AssetHandle<T> load(const std::string &path) {
|
||||
static_assert(std::is_base_of_v<Asset, T>, "T must derive from Asset");
|
||||
return loadImpl(AssetID(path), typeid(T));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 异步加载资源
|
||||
* @tparam T 资源类型
|
||||
* @param path 资源路径
|
||||
* @param callback 加载完成回调
|
||||
*/
|
||||
template <typename T>
|
||||
void loadAsync(const std::string &path, AssetLoadCallback<T> callback) {
|
||||
static_assert(std::is_base_of_v<Asset, T>, "T must derive from Asset");
|
||||
loadAsyncImpl(AssetID(path), typeid(T),
|
||||
[cb = std::move(callback)](AssetHandleBase handle) {
|
||||
cb(AssetHandle<T>(handle));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取已缓存的资源
|
||||
* @tparam T 资源类型
|
||||
* @param path 资源路径
|
||||
* @return 资源句柄,不存在返回空句柄
|
||||
*/
|
||||
template <typename T> AssetHandle<T> get(const std::string &path) {
|
||||
static_assert(std::is_base_of_v<Asset, T>, "T must derive from Asset");
|
||||
return getImpl(AssetID(path), typeid(T));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 预加载资源(后台加载,不返回句柄)
|
||||
* @tparam T 资源类型
|
||||
* @param path 资源路径
|
||||
*/
|
||||
template <typename T> void preload(const std::string &path) {
|
||||
static_assert(std::is_base_of_v<Asset, T>, "T must derive from Asset");
|
||||
preloadImpl(AssetID(path), typeid(T));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 检查资源是否已加载
|
||||
* @param path 资源路径
|
||||
* @return 已加载返回 true
|
||||
*/
|
||||
virtual bool isLoaded(const std::string &path) const = 0;
|
||||
|
||||
/**
|
||||
* @brief 检查资源是否正在加载
|
||||
* @param path 资源路径
|
||||
* @return 正在加载返回 true
|
||||
*/
|
||||
virtual bool isLoading(const std::string &path) const = 0;
|
||||
|
||||
/**
|
||||
* @brief 卸载资源
|
||||
* @param path 资源路径
|
||||
*/
|
||||
virtual void unload(const std::string &path) = 0;
|
||||
|
||||
/**
|
||||
* @brief 设置缓存上限
|
||||
* @param maxBytes 最大字节数
|
||||
*/
|
||||
virtual void setLimit(size_t maxBytes) = 0;
|
||||
|
||||
/**
|
||||
* @brief 获取当前缓存大小
|
||||
* @return 缓存字节数
|
||||
*/
|
||||
virtual size_t size() const = 0;
|
||||
|
||||
/**
|
||||
* @brief 清理无引用资源
|
||||
*/
|
||||
virtual void purge() = 0;
|
||||
|
||||
/**
|
||||
* @brief 清空所有缓存
|
||||
*/
|
||||
virtual void clear() = 0;
|
||||
|
||||
/**
|
||||
* @brief 获取缓存统计信息
|
||||
* @return 缓存统计
|
||||
*/
|
||||
virtual CacheStats stats() const = 0;
|
||||
|
||||
/**
|
||||
* @brief 注册加载器
|
||||
* @tparam T 资源类型
|
||||
* @param loader 加载器实例
|
||||
*/
|
||||
template <typename T> void registerLoader(Unique<AssetLoader<T>> loader) {
|
||||
static_assert(std::is_base_of_v<Asset, T>, "T must derive from Asset");
|
||||
registerLoaderImpl(typeid(T), std::move(loader));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 挂载资源包
|
||||
* @param path 资源包路径
|
||||
* @return 成功返回 true
|
||||
*/
|
||||
virtual bool mount(const std::string &path) = 0;
|
||||
|
||||
/**
|
||||
* @brief 卸载资源包
|
||||
* @param path 资源包路径
|
||||
*/
|
||||
virtual void unmount(const std::string &path) = 0;
|
||||
|
||||
/**
|
||||
* @brief 设置数据处理管道
|
||||
* @param pipe 处理管道
|
||||
*/
|
||||
virtual void setPipe(DataPipe pipe) = 0;
|
||||
|
||||
/**
|
||||
* @brief 设置资源根目录
|
||||
* @param path 根目录路径
|
||||
*/
|
||||
virtual void setRoot(const std::string &path) = 0;
|
||||
|
||||
/**
|
||||
* @brief 获取资源根目录
|
||||
* @return 根目录路径
|
||||
*/
|
||||
virtual std::string root() const = 0;
|
||||
|
||||
/**
|
||||
* @brief 处理异步加载完成回调(在主线程调用)
|
||||
*/
|
||||
virtual void process() = 0;
|
||||
|
||||
protected:
|
||||
/**
|
||||
* @brief 同步加载实现
|
||||
*/
|
||||
virtual AssetHandleBase loadImpl(const AssetID &id, std::type_index type) = 0;
|
||||
|
||||
/**
|
||||
* @brief 异步加载实现
|
||||
*/
|
||||
virtual void loadAsyncImpl(const AssetID &id, std::type_index type,
|
||||
std::function<void(AssetHandleBase)> callback) = 0;
|
||||
|
||||
/**
|
||||
* @brief 获取资源实现
|
||||
*/
|
||||
virtual AssetHandleBase getImpl(const AssetID &id, std::type_index type) = 0;
|
||||
|
||||
/**
|
||||
* @brief 预加载实现
|
||||
*/
|
||||
virtual void preloadImpl(const AssetID &id, std::type_index type) = 0;
|
||||
|
||||
/**
|
||||
* @brief 注册加载器实现
|
||||
*/
|
||||
virtual void registerLoaderImpl(std::type_index type,
|
||||
Unique<AssetLoaderBase> loader) = 0;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// AssetService - 资源服务实现
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 资源服务实现
|
||||
*
|
||||
* 实现资源加载、缓存、异步加载等功能。
|
||||
* 使用线程池处理异步加载任务。
|
||||
*/
|
||||
class AssetService : public IAssetService {
|
||||
public:
|
||||
AssetService();
|
||||
~AssetService() override;
|
||||
|
||||
ServiceInfo info() const override {
|
||||
ServiceInfo i;
|
||||
i.name = "AssetService";
|
||||
i.priority = ServicePriority::Resource;
|
||||
i.enabled = true;
|
||||
return i;
|
||||
}
|
||||
|
||||
bool init() override;
|
||||
void shutdown() override;
|
||||
|
||||
bool isLoaded(const std::string &path) const override;
|
||||
bool isLoading(const std::string &path) const override;
|
||||
void unload(const std::string &path) override;
|
||||
|
||||
void setLimit(size_t maxBytes) override;
|
||||
size_t size() const override;
|
||||
void purge() override;
|
||||
void clear() override;
|
||||
CacheStats stats() const override;
|
||||
|
||||
bool mount(const std::string &path) override;
|
||||
void unmount(const std::string &path) override;
|
||||
void setPipe(DataPipe pipe) override;
|
||||
void setRoot(const std::string &path) override;
|
||||
std::string root() const override;
|
||||
|
||||
void process() override;
|
||||
|
||||
protected:
|
||||
AssetHandleBase loadImpl(const AssetID &id, std::type_index type) override;
|
||||
void loadAsyncImpl(const AssetID &id, std::type_index type,
|
||||
std::function<void(AssetHandleBase)> callback) override;
|
||||
AssetHandleBase getImpl(const AssetID &id, std::type_index type) override;
|
||||
void preloadImpl(const AssetID &id, std::type_index type) override;
|
||||
void registerLoaderImpl(std::type_index type,
|
||||
Unique<AssetLoaderBase> loader) override;
|
||||
|
||||
private:
|
||||
struct LoadTask {
|
||||
AssetID id;
|
||||
std::type_index type = typeid(void);
|
||||
std::function<void(AssetHandleBase)> callback;
|
||||
};
|
||||
|
||||
struct LoadedAsset {
|
||||
Ref<Asset> asset;
|
||||
std::type_index type = typeid(void);
|
||||
};
|
||||
|
||||
std::string root_;
|
||||
Unique<AssetCache> cache_;
|
||||
PackManager packManager_;
|
||||
DataPipe pipe_;
|
||||
|
||||
mutable std::shared_mutex mutex_;
|
||||
std::unordered_map<AssetID, LoadedAsset> assets_;
|
||||
std::unordered_map<AssetID, AssetState> states_;
|
||||
std::unordered_map<std::type_index, Unique<AssetLoaderBase>> loaders_;
|
||||
|
||||
std::thread workerThread_;
|
||||
std::queue<LoadTask> taskQueue_;
|
||||
std::mutex taskMutex_;
|
||||
std::condition_variable taskCv_;
|
||||
std::atomic<bool> running_{false};
|
||||
|
||||
std::queue<std::function<void()>> callbackQueue_;
|
||||
std::mutex callbackMutex_;
|
||||
|
||||
/**
|
||||
* @brief 工作线程函数
|
||||
*/
|
||||
void workerFunc();
|
||||
|
||||
/**
|
||||
* @brief 从文件系统加载资源
|
||||
* @param id 资源ID
|
||||
* @param type 资源类型
|
||||
* @return 加载的资源
|
||||
*/
|
||||
Ref<Asset> loadFromFile(const AssetID &id, std::type_index type);
|
||||
|
||||
/**
|
||||
* @brief 从资源包加载资源
|
||||
* @param id 资源ID
|
||||
* @param type 资源类型
|
||||
* @return 加载的资源
|
||||
*/
|
||||
Ref<Asset> loadFromPack(const AssetID &id, std::type_index type);
|
||||
|
||||
/**
|
||||
* @brief 获取加载器
|
||||
* @param type 资源类型
|
||||
* @return 加载器指针
|
||||
*/
|
||||
AssetLoaderBase *getLoader(std::type_index type);
|
||||
|
||||
/**
|
||||
* @brief 根据路径推断资源类型
|
||||
* @param path 资源路径
|
||||
* @return 资源类型索引
|
||||
*/
|
||||
std::type_index inferType(const std::string &path);
|
||||
|
||||
E2D_AUTO_REGISTER_SERVICE(IAssetService, AssetService);
|
||||
};
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
#include <extra2d/asset/asset.h>
|
||||
|
||||
#define STB_TRUETYPE_IMPLEMENTATION
|
||||
#include <stb/stb_truetype.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// FontAsset::Impl - Pimpl 实现类
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
class FontAsset::Impl {
|
||||
public:
|
||||
stbtt_fontinfo info;
|
||||
bool initialized = false;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// FontAsset 实现
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
FontAsset::FontAsset() : impl_(ptr::makeUnique<Impl>()) {}
|
||||
|
||||
FontAsset::~FontAsset() = default;
|
||||
|
||||
bool FontAsset::loaded() const {
|
||||
return state_.load(std::memory_order_acquire) == AssetState::Loaded && impl_->initialized;
|
||||
}
|
||||
|
||||
float FontAsset::scaleForPixelHeight(float pixels) const {
|
||||
if (!impl_->initialized || data_.empty()) {
|
||||
return 0.0f;
|
||||
}
|
||||
return stbtt_ScaleForPixelHeight(&impl_->info, pixels);
|
||||
}
|
||||
|
||||
bool FontAsset::setData(std::vector<u8> data) {
|
||||
if (data.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
data_ = std::move(data);
|
||||
|
||||
if (!stbtt_InitFont(&impl_->info, data_.data(), 0)) {
|
||||
data_.clear();
|
||||
impl_->initialized = false;
|
||||
setState(AssetState::Failed);
|
||||
return false;
|
||||
}
|
||||
|
||||
impl_->initialized = true;
|
||||
setState(AssetState::Loaded);
|
||||
return true;
|
||||
}
|
||||
|
||||
void FontAsset::release() {
|
||||
data_.clear();
|
||||
impl_->initialized = false;
|
||||
setState(AssetState::Unloaded);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,121 @@
|
|||
#include <extra2d/asset/asset_cache.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// AssetCache 实现
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
AssetCache::AssetCache(size_t limit)
|
||||
: limit_(limit) {
|
||||
stats_.limit = limit;
|
||||
}
|
||||
|
||||
bool AssetCache::has(const AssetID& id) const {
|
||||
std::shared_lock<std::shared_mutex> lock(mutex_);
|
||||
return entries_.find(id) != entries_.end();
|
||||
}
|
||||
|
||||
bool AssetCache::remove(const AssetID& id) {
|
||||
std::unique_lock<std::shared_mutex> lock(mutex_);
|
||||
|
||||
auto it = entries_.find(id);
|
||||
if (it == entries_.end()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bytes_ -= it->second.entry->asset->memSize();
|
||||
lruList_.erase(it->second.lruIterator);
|
||||
entries_.erase(it);
|
||||
--stats_.count;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void AssetCache::setLimit(size_t limit) {
|
||||
std::unique_lock<std::shared_mutex> lock(mutex_);
|
||||
limit_ = limit;
|
||||
stats_.limit = limit;
|
||||
|
||||
if (limit > 0 && bytes_ > limit) {
|
||||
evict();
|
||||
}
|
||||
}
|
||||
|
||||
size_t AssetCache::count() const {
|
||||
std::shared_lock<std::shared_mutex> lock(mutex_);
|
||||
return entries_.size();
|
||||
}
|
||||
|
||||
size_t AssetCache::purge() {
|
||||
std::unique_lock<std::shared_mutex> lock(mutex_);
|
||||
|
||||
size_t purged = 0;
|
||||
auto it = entries_.begin();
|
||||
|
||||
while (it != entries_.end()) {
|
||||
if (canEvict(*it->second.entry)) {
|
||||
bytes_ -= it->second.entry->asset->memSize();
|
||||
lruList_.erase(it->second.lruIterator);
|
||||
it = entries_.erase(it);
|
||||
++purged;
|
||||
--stats_.count;
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
return purged;
|
||||
}
|
||||
|
||||
void AssetCache::clear() {
|
||||
std::unique_lock<std::shared_mutex> lock(mutex_);
|
||||
entries_.clear();
|
||||
lruList_.clear();
|
||||
bytes_ = 0;
|
||||
stats_.count = 0;
|
||||
stats_.bytes = 0;
|
||||
}
|
||||
|
||||
CacheStats AssetCache::stats() const {
|
||||
std::shared_lock<std::shared_mutex> lock(mutex_);
|
||||
stats_.bytes = bytes_;
|
||||
return stats_;
|
||||
}
|
||||
|
||||
void AssetCache::resetStats() {
|
||||
std::unique_lock<std::shared_mutex> lock(mutex_);
|
||||
stats_.hits = 0;
|
||||
stats_.misses = 0;
|
||||
}
|
||||
|
||||
void AssetCache::evict() {
|
||||
while (!lruList_.empty() && (limit_ > 0 && bytes_ > limit_)) {
|
||||
AssetID id = lruList_.front();
|
||||
|
||||
auto it = entries_.find(id);
|
||||
if (it != entries_.end()) {
|
||||
if (canEvict(*it->second.entry)) {
|
||||
bytes_ -= it->second.entry->asset->memSize();
|
||||
entries_.erase(it);
|
||||
lruList_.pop_front();
|
||||
--stats_.count;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
lruList_.pop_front();
|
||||
if (it != entries_.end()) {
|
||||
it->second.lruIterator = lruList_.insert(lruList_.end(), id);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool AssetCache::canEvict(const CacheEntry& entry) const {
|
||||
long useCount = entry.asset.use_count();
|
||||
return useCount <= 1;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,418 @@
|
|||
#include <extra2d/asset/asset_loader.h>
|
||||
#include <extra2d/services/logger_service.h>
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
|
||||
#define STB_IMAGE_IMPLEMENTATION
|
||||
#include <stb/stb_image.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 辅助函数
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
namespace {
|
||||
|
||||
/**
|
||||
* @brief 转换为小写
|
||||
*/
|
||||
std::string toLower(const std::string& str) {
|
||||
std::string result = str;
|
||||
std::transform(result.begin(), result.end(), result.begin(),
|
||||
[](unsigned char c) { return std::tolower(c); });
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取文件扩展名
|
||||
*/
|
||||
std::string getExtension(const std::string& path) {
|
||||
size_t pos = path.rfind('.');
|
||||
if (pos == std::string::npos) {
|
||||
return "";
|
||||
}
|
||||
return toLower(path.substr(pos));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 读取文件内容
|
||||
*/
|
||||
std::vector<u8> readFile(const std::string& path) {
|
||||
std::ifstream file(path, std::ios::binary | std::ios::ate);
|
||||
if (!file) {
|
||||
return {};
|
||||
}
|
||||
|
||||
size_t size = static_cast<size_t>(file.tellg());
|
||||
file.seekg(0, std::ios::beg);
|
||||
|
||||
std::vector<u8> data(size);
|
||||
if (!file.read(reinterpret_cast<char*>(data.data()), size)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// TextureLoader 实现
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
TextureLoader::TextureLoader() = default;
|
||||
|
||||
TextureLoader::~TextureLoader() = default;
|
||||
|
||||
Ref<TextureAsset> TextureLoader::load(const std::string& path) {
|
||||
auto data = readFile(path);
|
||||
if (data.empty()) {
|
||||
E2D_ERROR("Failed to read texture file: {}", path);
|
||||
return nullptr;
|
||||
}
|
||||
return loadFromMemory(data.data(), data.size());
|
||||
}
|
||||
|
||||
Ref<TextureAsset> TextureLoader::loadFromMemory(const u8* data, size_t size) {
|
||||
if (!data || size == 0) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int width, height, channels;
|
||||
u8* pixels = stbi_load_from_memory(data, static_cast<int>(size),
|
||||
&width, &height, &channels,
|
||||
desiredChannels_);
|
||||
|
||||
if (!pixels) {
|
||||
E2D_ERROR("Failed to load texture from memory: {}", stbi_failure_reason());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int actualChannels = desiredChannels_ > 0 ? desiredChannels_ : channels;
|
||||
|
||||
auto asset = ptr::make<TextureAsset>();
|
||||
|
||||
Unique<u8[]> pixelData(new u8[static_cast<size_t>(width) * height * actualChannels]);
|
||||
std::memcpy(pixelData.get(), pixels,
|
||||
static_cast<size_t>(width) * height * actualChannels);
|
||||
|
||||
asset->setData(width, height, actualChannels, std::move(pixelData));
|
||||
|
||||
stbi_image_free(pixels);
|
||||
|
||||
return asset;
|
||||
}
|
||||
|
||||
bool TextureLoader::canLoad(const std::string& path) const {
|
||||
std::string ext = getExtension(path);
|
||||
auto exts = extensions();
|
||||
return std::find(exts.begin(), exts.end(), ext) != exts.end();
|
||||
}
|
||||
|
||||
std::vector<std::string> TextureLoader::extensions() const {
|
||||
return {".png", ".jpg", ".jpeg", ".bmp", ".tga", ".gif", ".psd", ".hdr", ".pic"};
|
||||
}
|
||||
|
||||
void TextureLoader::setDesiredChannels(int channels) {
|
||||
desiredChannels_ = std::clamp(channels, 0, 4);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// FontLoader 实现
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
Ref<FontAsset> FontLoader::load(const std::string& path) {
|
||||
auto data = readFile(path);
|
||||
if (data.empty()) {
|
||||
E2D_ERROR("Failed to read font file: {}", path);
|
||||
return nullptr;
|
||||
}
|
||||
return loadFromMemory(data.data(), data.size());
|
||||
}
|
||||
|
||||
Ref<FontAsset> FontLoader::loadFromMemory(const u8* data, size_t size) {
|
||||
if (!data || size == 0) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto asset = ptr::make<FontAsset>();
|
||||
|
||||
std::vector<u8> fontData(data, data + size);
|
||||
if (!asset->setData(std::move(fontData))) {
|
||||
E2D_ERROR("Failed to initialize font from memory");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return asset;
|
||||
}
|
||||
|
||||
bool FontLoader::canLoad(const std::string& path) const {
|
||||
std::string ext = getExtension(path);
|
||||
auto exts = extensions();
|
||||
return std::find(exts.begin(), exts.end(), ext) != exts.end();
|
||||
}
|
||||
|
||||
std::vector<std::string> FontLoader::extensions() const {
|
||||
return {".ttf", ".otf", ".ttc"};
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// ShaderLoader 实现
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
Ref<ShaderAsset> ShaderLoader::load(const std::string& path) {
|
||||
auto data = readFile(path);
|
||||
if (data.empty()) {
|
||||
E2D_ERROR("Failed to read shader file: {}", path);
|
||||
return nullptr;
|
||||
}
|
||||
return loadFromMemory(data.data(), data.size());
|
||||
}
|
||||
|
||||
Ref<ShaderAsset> ShaderLoader::loadFromMemory(const u8* data, size_t size) {
|
||||
if (!data || size == 0) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::string content(reinterpret_cast<const char*>(data), size);
|
||||
|
||||
std::string vertexSrc, fragmentSrc;
|
||||
|
||||
if (content.find(vertexMarker_) != std::string::npos) {
|
||||
if (!parseCombined(content, vertexSrc, fragmentSrc)) {
|
||||
E2D_ERROR("Failed to parse combined shader file");
|
||||
return nullptr;
|
||||
}
|
||||
} else {
|
||||
vertexSrc = content;
|
||||
fragmentSrc = content;
|
||||
}
|
||||
|
||||
auto asset = ptr::make<ShaderAsset>();
|
||||
asset->setSource(std::move(vertexSrc), std::move(fragmentSrc));
|
||||
|
||||
return asset;
|
||||
}
|
||||
|
||||
bool ShaderLoader::canLoad(const std::string& path) const {
|
||||
std::string ext = getExtension(path);
|
||||
auto exts = extensions();
|
||||
return std::find(exts.begin(), exts.end(), ext) != exts.end();
|
||||
}
|
||||
|
||||
std::vector<std::string> ShaderLoader::extensions() const {
|
||||
return {".vert", ".frag", ".glsl", ".vs", ".fs"};
|
||||
}
|
||||
|
||||
bool ShaderLoader::parseCombined(const std::string& content,
|
||||
std::string& vertex,
|
||||
std::string& fragment) {
|
||||
size_t vertexPos = content.find(vertexMarker_);
|
||||
size_t fragmentPos = content.find(fragmentMarker_);
|
||||
|
||||
if (vertexPos == std::string::npos || fragmentPos == std::string::npos) {
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t vertexStart = vertexPos + vertexMarker_.length();
|
||||
|
||||
if (vertexPos < fragmentPos) {
|
||||
vertex = content.substr(vertexStart, fragmentPos - vertexStart);
|
||||
fragment = content.substr(fragmentPos + fragmentMarker_.length());
|
||||
} else {
|
||||
fragment = content.substr(fragmentPos + fragmentMarker_.length(),
|
||||
vertexPos - fragmentPos - fragmentMarker_.length());
|
||||
vertex = content.substr(vertexStart);
|
||||
}
|
||||
|
||||
auto trim = [](std::string& s) {
|
||||
size_t start = s.find_first_not_of(" \t\r\n");
|
||||
if (start == std::string::npos) {
|
||||
s.clear();
|
||||
return;
|
||||
}
|
||||
size_t end = s.find_last_not_of(" \t\r\n");
|
||||
s = s.substr(start, end - start + 1);
|
||||
};
|
||||
|
||||
trim(vertex);
|
||||
trim(fragment);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// AudioLoader 实现
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
Ref<AudioAsset> AudioLoader::load(const std::string& path) {
|
||||
auto data = readFile(path);
|
||||
if (data.empty()) {
|
||||
E2D_ERROR("Failed to read audio file: {}", path);
|
||||
return nullptr;
|
||||
}
|
||||
return loadFromMemory(data.data(), data.size());
|
||||
}
|
||||
|
||||
Ref<AudioAsset> AudioLoader::loadFromMemory(const u8* data, size_t size) {
|
||||
if (!data || size == 0) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::string ext = ".wav";
|
||||
if (size >= 4) {
|
||||
if (data[0] == 'R' && data[1] == 'I' && data[2] == 'F' && data[3] == 'F') {
|
||||
return loadWav(data, size);
|
||||
}
|
||||
}
|
||||
|
||||
E2D_ERROR("Unsupported audio format");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool AudioLoader::canLoad(const std::string& path) const {
|
||||
std::string ext = getExtension(path);
|
||||
auto exts = extensions();
|
||||
return std::find(exts.begin(), exts.end(), ext) != exts.end();
|
||||
}
|
||||
|
||||
std::vector<std::string> AudioLoader::extensions() const {
|
||||
return {".wav"};
|
||||
}
|
||||
|
||||
Ref<AudioAsset> AudioLoader::loadWav(const u8* data, size_t size) {
|
||||
if (size < 44) {
|
||||
E2D_ERROR("WAV file too small");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (data[0] != 'R' || data[1] != 'I' || data[2] != 'F' || data[3] != 'F') {
|
||||
E2D_ERROR("Invalid WAV file: missing RIFF header");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (data[8] != 'W' || data[9] != 'A' || data[10] != 'V' || data[11] != 'E') {
|
||||
E2D_ERROR("Invalid WAV file: missing WAVE format");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
size_t pos = 12;
|
||||
u16 audioFormat = 0;
|
||||
u16 numChannels = 0;
|
||||
u32 sampleRate = 0;
|
||||
u16 bitsPerSample = 0;
|
||||
size_t dataSize = 0;
|
||||
const u8* audioData = nullptr;
|
||||
|
||||
while (pos < size) {
|
||||
u32 chunkId = *reinterpret_cast<const u32*>(data + pos);
|
||||
u32 chunkSize = *reinterpret_cast<const u32*>(data + pos + 4);
|
||||
|
||||
if (chunkId == 0x20746D66) { // "fmt "
|
||||
audioFormat = *reinterpret_cast<const u16*>(data + pos + 8);
|
||||
numChannels = *reinterpret_cast<const u16*>(data + pos + 10);
|
||||
sampleRate = *reinterpret_cast<const u32*>(data + pos + 12);
|
||||
bitsPerSample = *reinterpret_cast<const u16*>(data + pos + 22);
|
||||
} else if (chunkId == 0x61746164) { // "data"
|
||||
dataSize = chunkSize;
|
||||
audioData = data + pos + 8;
|
||||
break;
|
||||
}
|
||||
|
||||
pos += 8 + chunkSize;
|
||||
if (chunkSize % 2 == 1) {
|
||||
pos++;
|
||||
}
|
||||
}
|
||||
|
||||
if (!audioData || dataSize == 0) {
|
||||
E2D_ERROR("Invalid WAV file: missing data chunk");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (audioFormat != 1) {
|
||||
E2D_ERROR("Unsupported WAV format: only PCM supported");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto asset = ptr::make<AudioAsset>();
|
||||
|
||||
std::vector<u8> pcmData(audioData, audioData + dataSize);
|
||||
asset->setData(AudioFormat::PCM, numChannels, sampleRate, bitsPerSample,
|
||||
std::move(pcmData));
|
||||
|
||||
return asset;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// DataLoader 实现
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
Ref<DataAsset> DataLoader::load(const std::string& path) {
|
||||
auto data = readFile(path);
|
||||
if (data.empty()) {
|
||||
E2D_ERROR("Failed to read data file: {}", path);
|
||||
return nullptr;
|
||||
}
|
||||
return loadFromMemory(data.data(), data.size());
|
||||
}
|
||||
|
||||
Ref<DataAsset> DataLoader::loadFromMemory(const u8* data, size_t size) {
|
||||
if (!data || size == 0) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto asset = ptr::make<DataAsset>();
|
||||
std::vector<u8> assetData(data, data + size);
|
||||
asset->setData(std::move(assetData));
|
||||
|
||||
return asset;
|
||||
}
|
||||
|
||||
bool DataLoader::canLoad(const std::string& path) const {
|
||||
return !path.empty();
|
||||
}
|
||||
|
||||
std::vector<std::string> DataLoader::extensions() const {
|
||||
return {".bin", ".dat"};
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// AssetLoaderFactory 实现
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
AssetType AssetLoaderFactory::getTypeByExtension(const std::string& extension) {
|
||||
std::string ext = toLower(extension);
|
||||
|
||||
if (ext == ".png" || ext == ".jpg" || ext == ".jpeg" ||
|
||||
ext == ".bmp" || ext == ".tga" || ext == ".gif" ||
|
||||
ext == ".psd" || ext == ".hdr" || ext == ".pic") {
|
||||
return AssetType::Texture;
|
||||
}
|
||||
|
||||
if (ext == ".ttf" || ext == ".otf" || ext == ".ttc") {
|
||||
return AssetType::Font;
|
||||
}
|
||||
|
||||
if (ext == ".vert" || ext == ".frag" || ext == ".glsl" ||
|
||||
ext == ".vs" || ext == ".fs") {
|
||||
return AssetType::Shader;
|
||||
}
|
||||
|
||||
if (ext == ".wav" || ext == ".mp3" || ext == ".ogg") {
|
||||
return AssetType::Audio;
|
||||
}
|
||||
|
||||
if (ext == ".bin" || ext == ".dat") {
|
||||
return AssetType::Data;
|
||||
}
|
||||
|
||||
return AssetType::Unknown;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,439 @@
|
|||
#include "extra2d/asset/asset_pack.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <filesystem>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// AssetPack 实现
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
AssetPack::AssetPack(AssetPack&& other) noexcept
|
||||
: path_(std::move(other.path_))
|
||||
, file_(std::move(other.file_))
|
||||
, header_(other.header_)
|
||||
, entries_(std::move(other.entries_))
|
||||
, pipe_(std::move(other.pipe_)) {
|
||||
other.header_ = AssetPackageHeader{};
|
||||
}
|
||||
|
||||
AssetPack& AssetPack::operator=(AssetPack&& other) noexcept {
|
||||
if (this != &other) {
|
||||
close();
|
||||
path_ = std::move(other.path_);
|
||||
file_ = std::move(other.file_);
|
||||
header_ = other.header_;
|
||||
entries_ = std::move(other.entries_);
|
||||
pipe_ = std::move(other.pipe_);
|
||||
other.header_ = AssetPackageHeader{};
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
AssetPack::~AssetPack() {
|
||||
close();
|
||||
}
|
||||
|
||||
bool AssetPack::open(const std::string& path) {
|
||||
close();
|
||||
|
||||
path_ = path;
|
||||
file_.open(path, std::ios::binary);
|
||||
|
||||
if (!file_.is_open()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!readHeader()) {
|
||||
close();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!readIndex()) {
|
||||
close();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void AssetPack::close() {
|
||||
if (file_.is_open()) {
|
||||
file_.close();
|
||||
}
|
||||
entries_.clear();
|
||||
path_.clear();
|
||||
header_ = AssetPackageHeader{};
|
||||
}
|
||||
|
||||
bool AssetPack::has(const AssetID& id) const {
|
||||
return entries_.find(id) != entries_.end();
|
||||
}
|
||||
|
||||
bool AssetPack::has(const std::string& path) const {
|
||||
return has(AssetID(path));
|
||||
}
|
||||
|
||||
std::vector<u8> AssetPack::read(const AssetID& id) {
|
||||
auto raw = readRaw(id);
|
||||
if (raw.empty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (!pipe_.empty()) {
|
||||
return pipe_.process(raw);
|
||||
}
|
||||
|
||||
return raw;
|
||||
}
|
||||
|
||||
std::vector<u8> AssetPack::read(const std::string& path) {
|
||||
return read(AssetID(path));
|
||||
}
|
||||
|
||||
std::vector<u8> AssetPack::readRaw(const AssetID& id) {
|
||||
auto* entry = getEntry(id);
|
||||
if (!entry) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return readEntryData(*entry);
|
||||
}
|
||||
|
||||
std::vector<AssetID> AssetPack::assets() const {
|
||||
std::vector<AssetID> result;
|
||||
result.reserve(entries_.size());
|
||||
for (const auto& pair : entries_) {
|
||||
result.push_back(pair.first);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
const AssetPackageEntry* AssetPack::getEntry(const AssetID& id) const {
|
||||
auto it = entries_.find(id);
|
||||
return it != entries_.end() ? &it->second : nullptr;
|
||||
}
|
||||
|
||||
bool AssetPack::readHeader() {
|
||||
file_.seekg(0, std::ios::beg);
|
||||
file_.read(reinterpret_cast<char*>(&header_), sizeof(header_));
|
||||
|
||||
if (!file_.good()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!header_.valid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AssetPack::readIndex() {
|
||||
u32 entryCount = 0;
|
||||
file_.read(reinterpret_cast<char*>(&entryCount), sizeof(entryCount));
|
||||
|
||||
if (!file_.good()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
entries_.clear();
|
||||
entries_.reserve(entryCount);
|
||||
|
||||
for (u32 i = 0; i < entryCount; ++i) {
|
||||
u32 pathLen = 0;
|
||||
file_.read(reinterpret_cast<char*>(&pathLen), sizeof(pathLen));
|
||||
|
||||
if (!file_.good()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string path(pathLen, '\0');
|
||||
file_.read(path.data(), pathLen);
|
||||
|
||||
AssetPackageEntry entry;
|
||||
entry.id = AssetID(path);
|
||||
|
||||
file_.read(reinterpret_cast<char*>(&entry.offset), sizeof(entry.offset));
|
||||
file_.read(reinterpret_cast<char*>(&entry.size), sizeof(entry.size));
|
||||
file_.read(reinterpret_cast<char*>(&entry.originalSize), sizeof(entry.originalSize));
|
||||
file_.read(reinterpret_cast<char*>(&entry.compression), sizeof(entry.compression));
|
||||
file_.read(reinterpret_cast<char*>(&entry.flags), sizeof(entry.flags));
|
||||
|
||||
if (!file_.good()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
entries_[entry.id] = entry;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<u8> AssetPack::readEntryData(const AssetPackageEntry& entry) {
|
||||
std::vector<u8> data(entry.size);
|
||||
|
||||
file_.seekg(entry.offset, std::ios::beg);
|
||||
file_.read(reinterpret_cast<char*>(data.data()), entry.size);
|
||||
|
||||
if (!file_.good()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// PackManager 实现
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
bool PackManager::mount(const std::string& path) {
|
||||
auto pack = std::make_unique<AssetPack>();
|
||||
if (!pack->open(path)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
(void)defaultPipe_;
|
||||
|
||||
|
||||
packs_.push_back(std::move(pack));
|
||||
return true;
|
||||
}
|
||||
|
||||
void PackManager::unmount(const std::string& path) {
|
||||
packs_.erase(
|
||||
std::remove_if(packs_.begin(), packs_.end(),
|
||||
[&path](const Unique<AssetPack>& pack) {
|
||||
return pack->path() == path;
|
||||
}),
|
||||
packs_.end()
|
||||
);
|
||||
}
|
||||
|
||||
void PackManager::unmountAll() {
|
||||
packs_.clear();
|
||||
}
|
||||
|
||||
AssetPack* PackManager::find(const AssetID& id) {
|
||||
for (auto& pack : packs_) {
|
||||
if (pack->has(id)) {
|
||||
return pack.get();
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
AssetPack* PackManager::find(const std::string& path) {
|
||||
return find(AssetID(path));
|
||||
}
|
||||
|
||||
bool PackManager::has(const AssetID& id) const {
|
||||
for (const auto& pack : packs_) {
|
||||
if (pack->has(id)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PackManager::has(const std::string& path) const {
|
||||
return has(AssetID(path));
|
||||
}
|
||||
|
||||
std::vector<u8> PackManager::read(const AssetID& id) {
|
||||
auto* pack = find(id);
|
||||
if (pack) {
|
||||
return pack->read(id);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<u8> PackManager::read(const std::string& path) {
|
||||
return read(AssetID(path));
|
||||
}
|
||||
|
||||
std::vector<AssetID> PackManager::allAssets() const {
|
||||
std::vector<AssetID> result;
|
||||
for (const auto& pack : packs_) {
|
||||
auto assets = pack->assets();
|
||||
result.insert(result.end(), assets.begin(), assets.end());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<std::string> PackManager::mountedPacks() const {
|
||||
std::vector<std::string> result;
|
||||
result.reserve(packs_.size());
|
||||
for (const auto& pack : packs_) {
|
||||
result.push_back(pack->path());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// AssetPackBuilder 实现
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
AssetPackBuilder::AssetPackBuilder(Compression compression, int level)
|
||||
: compression_(compression)
|
||||
, level_(level) {
|
||||
}
|
||||
|
||||
void AssetPackBuilder::add(const std::string& path, const std::vector<u8>& data) {
|
||||
BuilderEntry entry;
|
||||
entry.id = AssetID(path);
|
||||
entry.data = data;
|
||||
entry.compression = static_cast<u32>(compression_);
|
||||
entry.flags = encryptType_ != Decryptor::Type::None ? 1 : 0;
|
||||
|
||||
entry.compressedData = processData(entry.data);
|
||||
if (entry.compressedData.empty()) {
|
||||
entry.compressedData = entry.data;
|
||||
entry.compression = static_cast<u32>(Compression::None);
|
||||
}
|
||||
|
||||
entries_.push_back(std::move(entry));
|
||||
|
||||
totalOriginalSize_ += data.size();
|
||||
totalCompressedSize_ += entry.compressedData.size();
|
||||
}
|
||||
|
||||
void AssetPackBuilder::add(const std::string& path, std::vector<u8>&& data) {
|
||||
add(path, data);
|
||||
}
|
||||
|
||||
bool AssetPackBuilder::addFile(const std::string& filePath, const std::string& packPath) {
|
||||
std::ifstream file(filePath, std::ios::binary | std::ios::ate);
|
||||
if (!file.is_open()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto size = file.tellg();
|
||||
file.seekg(0, std::ios::beg);
|
||||
|
||||
std::vector<u8> data(size);
|
||||
file.read(reinterpret_cast<char*>(data.data()), size);
|
||||
|
||||
if (!file.good()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string ppath = packPath.empty()
|
||||
? std::filesystem::path(filePath).filename().string()
|
||||
: packPath;
|
||||
|
||||
add(ppath, std::move(data));
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t AssetPackBuilder::addDirectory(const std::string& dirPath, const std::string& prefix) {
|
||||
size_t count = 0;
|
||||
std::filesystem::path dir(dirPath);
|
||||
|
||||
if (!std::filesystem::exists(dir) || !std::filesystem::is_directory(dir)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
for (const auto& entry : std::filesystem::recursive_directory_iterator(dir)) {
|
||||
if (entry.is_regular_file()) {
|
||||
std::string relativePath = std::filesystem::relative(entry.path(), dir).string();
|
||||
std::string packPath = prefix.empty()
|
||||
? relativePath
|
||||
: prefix + "/" + relativePath;
|
||||
|
||||
if (addFile(entry.path().string(), packPath)) {
|
||||
++count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
void AssetPackBuilder::setEncryption(const std::string& key, Decryptor::Type type) {
|
||||
encryptKey_ = key;
|
||||
encryptType_ = type;
|
||||
}
|
||||
|
||||
bool AssetPackBuilder::build(const std::string& outputPath) {
|
||||
std::ofstream out(outputPath, std::ios::binary);
|
||||
if (!out.is_open()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
AssetPackageHeader header;
|
||||
header.magic = AssetPackageHeader::MAGIC;
|
||||
header.version = 1;
|
||||
header.compressionType = static_cast<u32>(compression_);
|
||||
header.encryptionType = static_cast<u32>(encryptType_);
|
||||
header.originalSize = totalOriginalSize_;
|
||||
|
||||
u64 dataOffset = sizeof(header) + sizeof(u32);
|
||||
for (const auto& entry : entries_) {
|
||||
dataOffset += sizeof(u32) + entry.id.path.size();
|
||||
dataOffset += sizeof(u64) * 3 + sizeof(u32) * 2;
|
||||
}
|
||||
|
||||
for (auto& entry : entries_) {
|
||||
entry.offset = dataOffset;
|
||||
dataOffset += entry.compressedData.size();
|
||||
}
|
||||
|
||||
header.compressedSize = dataOffset - sizeof(header) - sizeof(u32);
|
||||
for (const auto& entry : entries_) {
|
||||
header.compressedSize -= sizeof(u32) + entry.id.path.size();
|
||||
header.compressedSize -= sizeof(u64) * 3 + sizeof(u32) * 2;
|
||||
}
|
||||
|
||||
out.write(reinterpret_cast<const char*>(&header), sizeof(header));
|
||||
|
||||
u32 entryCount = static_cast<u32>(entries_.size());
|
||||
out.write(reinterpret_cast<const char*>(&entryCount), sizeof(entryCount));
|
||||
|
||||
for (const auto& entry : entries_) {
|
||||
u32 pathLen = static_cast<u32>(entry.id.path.size());
|
||||
out.write(reinterpret_cast<const char*>(&pathLen), sizeof(pathLen));
|
||||
out.write(entry.id.path.data(), pathLen);
|
||||
|
||||
u64 offset = entry.offset;
|
||||
u64 size = entry.compressedData.size();
|
||||
u64 originalSize = entry.data.size();
|
||||
|
||||
out.write(reinterpret_cast<const char*>(&offset), sizeof(offset));
|
||||
out.write(reinterpret_cast<const char*>(&size), sizeof(size));
|
||||
out.write(reinterpret_cast<const char*>(&originalSize), sizeof(originalSize));
|
||||
out.write(reinterpret_cast<const char*>(&entry.compression), sizeof(entry.compression));
|
||||
out.write(reinterpret_cast<const char*>(&entry.flags), sizeof(entry.flags));
|
||||
}
|
||||
|
||||
for (const auto& entry : entries_) {
|
||||
out.write(reinterpret_cast<const char*>(entry.compressedData.data()),
|
||||
entry.compressedData.size());
|
||||
}
|
||||
|
||||
return out.good();
|
||||
}
|
||||
|
||||
void AssetPackBuilder::clear() {
|
||||
entries_.clear();
|
||||
totalOriginalSize_ = 0;
|
||||
totalCompressedSize_ = 0;
|
||||
}
|
||||
|
||||
std::vector<u8> AssetPackBuilder::processData(const std::vector<u8>& data) {
|
||||
DataPipe pipe;
|
||||
|
||||
if (compression_ != Compression::None) {
|
||||
pipe.compress(compression_, level_);
|
||||
}
|
||||
|
||||
if (encryptType_ != Decryptor::Type::None && !encryptKey_.empty()) {
|
||||
pipe.encrypt(encryptKey_, encryptType_);
|
||||
}
|
||||
|
||||
return pipe.process(data);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,449 @@
|
|||
#include "extra2d/asset/data_processor.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
|
||||
#ifdef E2D_USE_ZSTD
|
||||
#include <zstd.h>
|
||||
#endif
|
||||
|
||||
#ifdef E2D_USE_LZ4
|
||||
#include <lz4.h>
|
||||
#endif
|
||||
|
||||
#ifdef E2D_USE_ZLIB
|
||||
#include <zlib.h>
|
||||
#endif
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Decryptor 实现
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
Decryptor::Decryptor(const std::string& key, Type type)
|
||||
: key_(key), type_(type) {
|
||||
}
|
||||
|
||||
std::vector<u8> Decryptor::process(const std::vector<u8>& input) {
|
||||
if (input.empty() || type_ == Type::None) {
|
||||
return processNext(input);
|
||||
}
|
||||
|
||||
std::vector<u8> result;
|
||||
switch (type_) {
|
||||
case Type::XOR:
|
||||
result = decryptXOR(input);
|
||||
break;
|
||||
case Type::AES256:
|
||||
result = decryptAES256(input);
|
||||
break;
|
||||
default:
|
||||
result = input;
|
||||
break;
|
||||
}
|
||||
|
||||
return processNext(result);
|
||||
}
|
||||
|
||||
std::vector<u8> Decryptor::decryptXOR(const std::vector<u8>& input) {
|
||||
if (key_.empty()) {
|
||||
return input;
|
||||
}
|
||||
|
||||
std::vector<u8> result(input.size());
|
||||
const size_t keyLen = key_.size();
|
||||
|
||||
for (size_t i = 0; i < input.size(); ++i) {
|
||||
result[i] = input[i] ^ static_cast<u8>(key_[i % keyLen]);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<u8> Decryptor::decryptAES256(const std::vector<u8>& input) {
|
||||
return decryptXOR(input);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Decompressor 实现
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
Decompressor::Decompressor(Compression algo)
|
||||
: algo_(algo) {
|
||||
}
|
||||
|
||||
std::vector<u8> Decompressor::process(const std::vector<u8>& input) {
|
||||
if (input.empty() || algo_ == Compression::None) {
|
||||
return processNext(input);
|
||||
}
|
||||
|
||||
std::vector<u8> result;
|
||||
switch (algo_) {
|
||||
case Compression::Zstd:
|
||||
result = decompressZstd(input);
|
||||
break;
|
||||
case Compression::LZ4:
|
||||
result = decompressLZ4(input);
|
||||
break;
|
||||
case Compression::Zlib:
|
||||
result = decompressZlib(input);
|
||||
break;
|
||||
default:
|
||||
result = input;
|
||||
break;
|
||||
}
|
||||
|
||||
return processNext(result);
|
||||
}
|
||||
|
||||
std::vector<u8> Decompressor::decompressZstd(const std::vector<u8>& input) {
|
||||
#ifdef E2D_USE_ZSTD
|
||||
unsigned long long const decompressedSize = ZSTD_getFrameContentSize(input.data(), input.size());
|
||||
|
||||
if (decompressedSize == ZSTD_CONTENTSIZE_ERROR ||
|
||||
decompressedSize == ZSTD_CONTENTSIZE_UNKNOWN) {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<u8> result(static_cast<size_t>(decompressedSize));
|
||||
|
||||
size_t const actualSize = ZSTD_decompress(
|
||||
result.data(), result.size(),
|
||||
input.data(), input.size()
|
||||
);
|
||||
|
||||
if (ZSTD_isError(actualSize)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
result.resize(actualSize);
|
||||
return result;
|
||||
#else
|
||||
return input;
|
||||
#endif
|
||||
}
|
||||
|
||||
std::vector<u8> Decompressor::decompressLZ4(const std::vector<u8>& input) {
|
||||
#ifdef E2D_USE_LZ4
|
||||
if (input.size() < sizeof(u32)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
u32 originalSize;
|
||||
std::memcpy(&originalSize, input.data(), sizeof(u32));
|
||||
|
||||
std::vector<u8> result(originalSize);
|
||||
|
||||
int const decompressedSize = LZ4_decompress_safe(
|
||||
reinterpret_cast<const char*>(input.data() + sizeof(u32)),
|
||||
reinterpret_cast<char*>(result.data()),
|
||||
static_cast<int>(input.size() - sizeof(u32)),
|
||||
static_cast<int>(originalSize)
|
||||
);
|
||||
|
||||
if (decompressedSize < 0) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return result;
|
||||
#else
|
||||
return input;
|
||||
#endif
|
||||
}
|
||||
|
||||
std::vector<u8> Decompressor::decompressZlib(const std::vector<u8>& input) {
|
||||
#ifdef E2D_USE_ZLIB
|
||||
std::vector<u8> result;
|
||||
result.reserve(input.size() * 4);
|
||||
|
||||
const size_t CHUNK = 16384;
|
||||
u8 out[CHUNK];
|
||||
|
||||
z_stream strm;
|
||||
strm.zalloc = Z_NULL;
|
||||
strm.zfree = Z_NULL;
|
||||
strm.opaque = Z_NULL;
|
||||
strm.avail_in = static_cast<uInt>(input.size());
|
||||
strm.next_in = const_cast<Bytef*>(input.data());
|
||||
|
||||
if (inflateInit(&strm) != Z_OK) {
|
||||
return {};
|
||||
}
|
||||
|
||||
int ret;
|
||||
do {
|
||||
strm.avail_out = CHUNK;
|
||||
strm.next_out = out;
|
||||
|
||||
ret = inflate(&strm, Z_NO_FLUSH);
|
||||
if (ret == Z_STREAM_ERROR || ret == Z_DATA_ERROR || ret == Z_MEM_ERROR) {
|
||||
inflateEnd(&strm);
|
||||
return {};
|
||||
}
|
||||
|
||||
result.insert(result.end(), out, out + CHUNK - strm.avail_out);
|
||||
} while (strm.avail_out == 0);
|
||||
|
||||
inflateEnd(&strm);
|
||||
return result;
|
||||
#else
|
||||
return input;
|
||||
#endif
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Encryptor 实现
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
Encryptor::Encryptor(const std::string& key, Decryptor::Type type)
|
||||
: key_(key), type_(type) {
|
||||
}
|
||||
|
||||
std::vector<u8> Encryptor::process(const std::vector<u8>& input) {
|
||||
if (input.empty() || type_ == Decryptor::Type::None) {
|
||||
return processNext(input);
|
||||
}
|
||||
|
||||
std::vector<u8> result;
|
||||
switch (type_) {
|
||||
case Decryptor::Type::XOR:
|
||||
result = encryptXOR(input);
|
||||
break;
|
||||
case Decryptor::Type::AES256:
|
||||
result = encryptAES256(input);
|
||||
break;
|
||||
default:
|
||||
result = input;
|
||||
break;
|
||||
}
|
||||
|
||||
return processNext(result);
|
||||
}
|
||||
|
||||
std::vector<u8> Encryptor::encryptXOR(const std::vector<u8>& input) {
|
||||
if (key_.empty()) {
|
||||
return input;
|
||||
}
|
||||
|
||||
std::vector<u8> result(input.size());
|
||||
const size_t keyLen = key_.size();
|
||||
|
||||
for (size_t i = 0; i < input.size(); ++i) {
|
||||
result[i] = input[i] ^ static_cast<u8>(key_[i % keyLen]);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<u8> Encryptor::encryptAES256(const std::vector<u8>& input) {
|
||||
return encryptXOR(input);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Compressor 实现
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
Compressor::Compressor(Compression algo, int level)
|
||||
: algo_(algo), level_(level) {
|
||||
}
|
||||
|
||||
std::vector<u8> Compressor::process(const std::vector<u8>& input) {
|
||||
if (input.empty() || algo_ == Compression::None) {
|
||||
return processNext(input);
|
||||
}
|
||||
|
||||
std::vector<u8> result;
|
||||
switch (algo_) {
|
||||
case Compression::Zstd:
|
||||
result = compressZstd(input);
|
||||
break;
|
||||
case Compression::LZ4:
|
||||
result = compressLZ4(input);
|
||||
break;
|
||||
case Compression::Zlib:
|
||||
result = compressZlib(input);
|
||||
break;
|
||||
default:
|
||||
result = input;
|
||||
break;
|
||||
}
|
||||
|
||||
return processNext(result);
|
||||
}
|
||||
|
||||
std::vector<u8> Compressor::compressZstd(const std::vector<u8>& input) {
|
||||
#ifdef E2D_USE_ZSTD
|
||||
size_t const bound = ZSTD_compressBound(input.size());
|
||||
std::vector<u8> result(bound);
|
||||
|
||||
size_t const compressedSize = ZSTD_compress(
|
||||
result.data(), result.size(),
|
||||
input.data(), input.size(),
|
||||
level_
|
||||
);
|
||||
|
||||
if (ZSTD_isError(compressedSize)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
result.resize(compressedSize);
|
||||
return result;
|
||||
#else
|
||||
return input;
|
||||
#endif
|
||||
}
|
||||
|
||||
std::vector<u8> Compressor::compressLZ4(const std::vector<u8>& input) {
|
||||
#ifdef E2D_USE_LZ4
|
||||
int const bound = LZ4_compressBound(static_cast<int>(input.size()));
|
||||
std::vector<u8> result(sizeof(u32) + bound);
|
||||
|
||||
u32 originalSize = static_cast<u32>(input.size());
|
||||
std::memcpy(result.data(), &originalSize, sizeof(u32));
|
||||
|
||||
int const compressedSize = LZ4_compress_default(
|
||||
reinterpret_cast<const char*>(input.data()),
|
||||
reinterpret_cast<char*>(result.data() + sizeof(u32)),
|
||||
static_cast<int>(input.size()),
|
||||
bound
|
||||
);
|
||||
|
||||
if (compressedSize <= 0) {
|
||||
return {};
|
||||
}
|
||||
|
||||
result.resize(sizeof(u32) + compressedSize);
|
||||
return result;
|
||||
#else
|
||||
return input;
|
||||
#endif
|
||||
}
|
||||
|
||||
std::vector<u8> Compressor::compressZlib(const std::vector<u8>& input) {
|
||||
#ifdef E2D_USE_ZLIB
|
||||
std::vector<u8> result;
|
||||
result.reserve(input.size() / 2);
|
||||
|
||||
const size_t CHUNK = 16384;
|
||||
u8 out[CHUNK];
|
||||
|
||||
z_stream strm;
|
||||
strm.zalloc = Z_NULL;
|
||||
strm.zfree = Z_NULL;
|
||||
strm.opaque = Z_NULL;
|
||||
|
||||
if (deflateInit(&strm, level_) != Z_OK) {
|
||||
return {};
|
||||
}
|
||||
|
||||
strm.avail_in = static_cast<uInt>(input.size());
|
||||
strm.next_in = const_cast<Bytef*>(input.data());
|
||||
|
||||
int ret;
|
||||
do {
|
||||
strm.avail_out = CHUNK;
|
||||
strm.next_out = out;
|
||||
|
||||
ret = deflate(&strm, Z_FINISH);
|
||||
if (ret == Z_STREAM_ERROR) {
|
||||
deflateEnd(&strm);
|
||||
return {};
|
||||
}
|
||||
|
||||
result.insert(result.end(), out, out + CHUNK - strm.avail_out);
|
||||
} while (strm.avail_out == 0);
|
||||
|
||||
deflateEnd(&strm);
|
||||
return result;
|
||||
#else
|
||||
return input;
|
||||
#endif
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// DataPipe 实现
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
DataPipe& DataPipe::decrypt(const std::string& key, Decryptor::Type type) {
|
||||
processors_.push_back(std::make_unique<Decryptor>(key, type));
|
||||
return *this;
|
||||
}
|
||||
|
||||
DataPipe& DataPipe::decompress(Compression algo) {
|
||||
processors_.push_back(std::make_unique<Decompressor>(algo));
|
||||
return *this;
|
||||
}
|
||||
|
||||
DataPipe& DataPipe::encrypt(const std::string& key, Decryptor::Type type) {
|
||||
processors_.push_back(std::make_unique<Encryptor>(key, type));
|
||||
return *this;
|
||||
}
|
||||
|
||||
DataPipe& DataPipe::compress(Compression algo, int level) {
|
||||
processors_.push_back(std::make_unique<Compressor>(algo, level));
|
||||
return *this;
|
||||
}
|
||||
|
||||
DataPipe& DataPipe::add(Unique<DataProcessor> processor) {
|
||||
processors_.push_back(std::move(processor));
|
||||
return *this;
|
||||
}
|
||||
|
||||
std::vector<u8> DataPipe::process(const std::vector<u8>& input) {
|
||||
if (processors_.empty()) {
|
||||
return input;
|
||||
}
|
||||
|
||||
std::vector<u8> result = input;
|
||||
for (auto& processor : processors_) {
|
||||
result = processor->process(result);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void DataPipe::clear() {
|
||||
processors_.clear();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 工具函数实现
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
std::vector<u8> computeChecksum(const std::vector<u8>& data) {
|
||||
std::vector<u8> result(32, 0);
|
||||
|
||||
u64 hash1 = 14695981039346656037ULL;
|
||||
u64 hash2 = 14695981039346656037ULL;
|
||||
|
||||
for (size_t i = 0; i < data.size(); ++i) {
|
||||
hash1 ^= static_cast<u64>(data[i]);
|
||||
hash1 *= 1099511628211ULL;
|
||||
|
||||
hash2 ^= static_cast<u64>(data[i]) << ((i % 8) * 8);
|
||||
hash2 *= 1099511628211ULL;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < 8; ++i) {
|
||||
result[i] = static_cast<u8>((hash1 >> (i * 8)) & 0xFF);
|
||||
result[i + 8] = static_cast<u8>((hash2 >> (i * 8)) & 0xFF);
|
||||
}
|
||||
|
||||
for (size_t i = 16; i < 32; ++i) {
|
||||
result[i] = static_cast<u8>((hash1 ^ hash2) >> ((i - 16) * 8) & 0xFF);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool verifyChecksum(const std::vector<u8>& data, const std::vector<u8>& checksum) {
|
||||
if (checksum.size() != 32) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto computed = computeChecksum(data);
|
||||
return std::equal(computed.begin(), computed.end(), checksum.begin());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,318 @@
|
|||
#include "extra2d/services/asset_service.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <filesystem>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// AssetService 实现
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
AssetService::AssetService() : cache_(ptr::makeUnique<AssetCache>()) {}
|
||||
|
||||
AssetService::~AssetService() { shutdown(); }
|
||||
|
||||
bool AssetService::init() {
|
||||
if (initialized()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
setState(ServiceState::Initializing);
|
||||
|
||||
registerLoader<TextureAsset>(AssetLoaderFactory::createTextureLoader());
|
||||
registerLoader<FontAsset>(AssetLoaderFactory::createFontLoader());
|
||||
registerLoader<ShaderAsset>(AssetLoaderFactory::createShaderLoader());
|
||||
registerLoader<AudioAsset>(AssetLoaderFactory::createAudioLoader());
|
||||
registerLoader<DataAsset>(AssetLoaderFactory::createDataLoader());
|
||||
|
||||
running_ = true;
|
||||
workerThread_ = std::thread(&AssetService::workerFunc, this);
|
||||
|
||||
setState(ServiceState::Running);
|
||||
return true;
|
||||
}
|
||||
|
||||
void AssetService::shutdown() {
|
||||
if (!initialized()) {
|
||||
return;
|
||||
}
|
||||
|
||||
setState(ServiceState::Stopping);
|
||||
|
||||
running_ = false;
|
||||
taskCv_.notify_all();
|
||||
|
||||
if (workerThread_.joinable()) {
|
||||
workerThread_.join();
|
||||
}
|
||||
|
||||
clear();
|
||||
packManager_.unmountAll();
|
||||
loaders_.clear();
|
||||
|
||||
setState(ServiceState::Stopped);
|
||||
}
|
||||
|
||||
bool AssetService::isLoaded(const std::string &path) const {
|
||||
std::shared_lock<std::shared_mutex> lock(mutex_);
|
||||
auto it = states_.find(AssetID(path));
|
||||
return it != states_.end() && it->second == AssetState::Loaded;
|
||||
}
|
||||
|
||||
bool AssetService::isLoading(const std::string &path) const {
|
||||
std::shared_lock<std::shared_mutex> lock(mutex_);
|
||||
auto it = states_.find(AssetID(path));
|
||||
return it != states_.end() && it->second == AssetState::Loading;
|
||||
}
|
||||
|
||||
void AssetService::unload(const std::string &path) {
|
||||
std::unique_lock<std::shared_mutex> lock(mutex_);
|
||||
AssetID id(path);
|
||||
assets_.erase(id);
|
||||
states_.erase(id);
|
||||
}
|
||||
|
||||
void AssetService::setLimit(size_t maxBytes) { cache_->setLimit(maxBytes); }
|
||||
|
||||
size_t AssetService::size() const { return cache_->size(); }
|
||||
|
||||
void AssetService::purge() { cache_->purge(); }
|
||||
|
||||
void AssetService::clear() {
|
||||
std::unique_lock<std::shared_mutex> lock(mutex_);
|
||||
assets_.clear();
|
||||
states_.clear();
|
||||
cache_->clear();
|
||||
}
|
||||
|
||||
CacheStats AssetService::stats() const { return cache_->stats(); }
|
||||
|
||||
bool AssetService::mount(const std::string &path) {
|
||||
return packManager_.mount(path);
|
||||
}
|
||||
|
||||
void AssetService::unmount(const std::string &path) {
|
||||
packManager_.unmount(path);
|
||||
}
|
||||
|
||||
void AssetService::setPipe(DataPipe pipe) { pipe_ = std::move(pipe); }
|
||||
|
||||
void AssetService::setRoot(const std::string &path) { root_ = path; }
|
||||
|
||||
std::string AssetService::root() const { return root_; }
|
||||
|
||||
void AssetService::process() {
|
||||
std::queue<std::function<void()>> callbacks;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(callbackMutex_);
|
||||
callbacks = std::move(callbackQueue_);
|
||||
callbackQueue_ = {};
|
||||
}
|
||||
|
||||
while (!callbacks.empty()) {
|
||||
callbacks.front()();
|
||||
callbacks.pop();
|
||||
}
|
||||
}
|
||||
|
||||
AssetHandleBase AssetService::loadImpl(const AssetID &id,
|
||||
std::type_index type) {
|
||||
{
|
||||
std::shared_lock<std::shared_mutex> lock(mutex_);
|
||||
auto it = assets_.find(id);
|
||||
if (it != assets_.end()) {
|
||||
cache_->hit();
|
||||
return AssetHandleBase(id, it->second.asset);
|
||||
}
|
||||
}
|
||||
|
||||
cache_->miss();
|
||||
|
||||
Ref<Asset> asset = loadFromFile(id, type);
|
||||
if (!asset) {
|
||||
asset = loadFromPack(id, type);
|
||||
}
|
||||
|
||||
if (!asset) {
|
||||
return AssetHandleBase();
|
||||
}
|
||||
|
||||
{
|
||||
std::unique_lock<std::shared_mutex> lock(mutex_);
|
||||
assets_[id] = {asset, type};
|
||||
states_[id] = AssetState::Loaded;
|
||||
}
|
||||
|
||||
cache_->add(asset);
|
||||
return AssetHandleBase(id, asset);
|
||||
}
|
||||
|
||||
void AssetService::loadAsyncImpl(
|
||||
const AssetID &id, std::type_index type,
|
||||
std::function<void(AssetHandleBase)> callback) {
|
||||
{
|
||||
std::shared_lock<std::shared_mutex> lock(mutex_);
|
||||
auto it = assets_.find(id);
|
||||
if (it != assets_.end()) {
|
||||
cache_->hit();
|
||||
callback(AssetHandleBase(id, it->second.asset));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
cache_->miss();
|
||||
|
||||
{
|
||||
std::unique_lock<std::shared_mutex> lock(mutex_);
|
||||
states_[id] = AssetState::Loading;
|
||||
}
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(taskMutex_);
|
||||
taskQueue_.push({id, type, std::move(callback)});
|
||||
}
|
||||
taskCv_.notify_one();
|
||||
}
|
||||
|
||||
AssetHandleBase AssetService::getImpl(const AssetID &id, std::type_index type) {
|
||||
(void)type;
|
||||
std::shared_lock<std::shared_mutex> lock(mutex_);
|
||||
auto it = assets_.find(id);
|
||||
if (it != assets_.end()) {
|
||||
cache_->hit();
|
||||
return AssetHandleBase(id, it->second.asset);
|
||||
}
|
||||
|
||||
cache_->miss();
|
||||
return AssetHandleBase();
|
||||
}
|
||||
|
||||
void AssetService::preloadImpl(const AssetID &id, std::type_index type) {
|
||||
{
|
||||
std::shared_lock<std::shared_mutex> lock(mutex_);
|
||||
if (assets_.find(id) != assets_.end()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(taskMutex_);
|
||||
taskQueue_.push({id, type, nullptr});
|
||||
}
|
||||
taskCv_.notify_one();
|
||||
}
|
||||
|
||||
void AssetService::registerLoaderImpl(std::type_index type,
|
||||
Unique<AssetLoaderBase> loader) {
|
||||
std::unique_lock<std::shared_mutex> lock(mutex_);
|
||||
loaders_[type] = std::move(loader);
|
||||
}
|
||||
|
||||
void AssetService::workerFunc() {
|
||||
while (running_) {
|
||||
LoadTask task;
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(taskMutex_);
|
||||
taskCv_.wait(lock, [this] { return !taskQueue_.empty() || !running_; });
|
||||
|
||||
if (!running_) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (taskQueue_.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
task = std::move(taskQueue_.front());
|
||||
taskQueue_.pop();
|
||||
}
|
||||
|
||||
Ref<Asset> asset = loadFromFile(task.id, task.type);
|
||||
if (!asset) {
|
||||
asset = loadFromPack(task.id, task.type);
|
||||
}
|
||||
|
||||
if (asset) {
|
||||
std::unique_lock<std::shared_mutex> lock(mutex_);
|
||||
assets_[task.id] = {asset, task.type};
|
||||
states_[task.id] = AssetState::Loaded;
|
||||
cache_->add(asset);
|
||||
} else {
|
||||
std::unique_lock<std::shared_mutex> lock(mutex_);
|
||||
states_[task.id] = AssetState::Failed;
|
||||
}
|
||||
|
||||
if (task.callback) {
|
||||
std::lock_guard<std::mutex> lock(callbackMutex_);
|
||||
callbackQueue_.push(
|
||||
[task, asset]() { task.callback(AssetHandleBase(task.id, asset)); });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ref<Asset> AssetService::loadFromFile(const AssetID &id, std::type_index type) {
|
||||
auto *loader = getLoader(type);
|
||||
if (!loader) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::string fullPath = root_.empty() ? id.path : root_ + "/" + id.path;
|
||||
|
||||
if (!std::filesystem::exists(fullPath)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return loader->loadBase(fullPath);
|
||||
}
|
||||
|
||||
Ref<Asset> AssetService::loadFromPack(const AssetID &id, std::type_index type) {
|
||||
auto *loader = getLoader(type);
|
||||
if (!loader) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto *pack = packManager_.find(id);
|
||||
if (!pack) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto data = pack->read(id);
|
||||
if (data.empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!pipe_.empty()) {
|
||||
data = pipe_.process(data);
|
||||
}
|
||||
|
||||
return loader->loadFromMemoryBase(data.data(), data.size());
|
||||
}
|
||||
|
||||
AssetLoaderBase *AssetService::getLoader(std::type_index type) {
|
||||
auto it = loaders_.find(type);
|
||||
return it != loaders_.end() ? it->second.get() : nullptr;
|
||||
}
|
||||
|
||||
std::type_index AssetService::inferType(const std::string &path) {
|
||||
std::filesystem::path p(path);
|
||||
std::string ext = p.extension().string();
|
||||
|
||||
AssetType type = AssetLoaderFactory::getTypeByExtension(ext);
|
||||
switch (type) {
|
||||
case AssetType::Texture:
|
||||
return typeid(TextureAsset);
|
||||
case AssetType::Font:
|
||||
return typeid(FontAsset);
|
||||
case AssetType::Shader:
|
||||
return typeid(ShaderAsset);
|
||||
case AssetType::Audio:
|
||||
return typeid(AudioAsset);
|
||||
case AssetType::Data:
|
||||
return typeid(DataAsset);
|
||||
default:
|
||||
return typeid(DataAsset);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace extra2d
|
||||
Loading…
Reference in New Issue