feat(asset): 添加资源管理系统核心组件

实现资源加载、缓存和管理功能,包括:
- Asset基类和具体资源类型(纹理、字体、着色器等)
- 资源缓存系统(AssetCache)支持LRU策略
- 资源加载器接口和具体实现
- 资源包管理(打包/解包)
- 数据处理管道(压缩/加密)
- 资源服务(AssetService)提供统一接口
This commit is contained in:
ChestnutYueyue 2026-02-20 20:36:59 +08:00
parent 4e6db88da5
commit 25c7c47872
15 changed files with 4573 additions and 0 deletions

View File

@ -0,0 +1,477 @@
#pragma once
#include <atomic>
#include <cstdint>
#include <extra2d/asset/asset_types.h>
#include <extra2d/core/types.h>
#include <memory>
#include <string>
#include <vector>
namespace extra2d {
// ---------------------------------------------------------------------------
// Asset - 资源基类
// ---------------------------------------------------------------------------
/**
* @brief
*
* enable_shared_from_this
*
*/
class Asset : public std::enable_shared_from_this<Asset> {
public:
virtual ~Asset() = default;
/**
* @brief
* @return
*/
virtual AssetType type() const = 0;
/**
* @brief
* @return true
*/
virtual bool loaded() const = 0;
/**
* @brief
* @return
*/
virtual size_t memSize() const = 0;
/**
* @brief ID
* @return ID
*/
const AssetID &id() const { return id_; }
/**
* @brief
* @return
*/
const std::string &path() const { return path_; }
/**
* @brief
* @return
*/
AssetState state() const { return state_; }
/**
* @brief
* @return
*/
long refs() const { return shared_from_this().use_count(); }
protected:
AssetID id_;
std::string path_;
std::atomic<AssetState> state_{AssetState::Unloaded};
/**
* @brief
* @param state
*/
void setState(AssetState state) {
state_.store(state, std::memory_order_release);
}
/**
* @brief ID
* @param id ID
*/
void setId(const AssetID &id) { id_ = id; }
/**
* @brief
* @param path
*/
void setPath(const std::string &path) { path_ = path; }
friend class AssetCache;
friend class AssetService;
};
// ---------------------------------------------------------------------------
// TextureAsset - 纹理资源
// ---------------------------------------------------------------------------
/**
* @brief
*
*
*/
class TextureAsset : public Asset {
public:
AssetType type() const override { return AssetType::Texture; }
bool loaded() const override {
return state_.load(std::memory_order_acquire) == AssetState::Loaded &&
data_ != nullptr;
}
size_t memSize() const override {
return static_cast<size_t>(width_) * height_ * channels_;
}
/**
* @brief
* @return
*/
int width() const { return width_; }
/**
* @brief
* @return
*/
int height() const { return height_; }
/**
* @brief
* @return 1-4
*/
int channels() const { return channels_; }
/**
* @brief
* @return
*/
const u8 *data() const { return data_.get(); }
/**
* @brief
* @return
*/
size_t dataSize() const { return memSize(); }
/**
* @brief
* @param width
* @param height
* @param channels
* @param data
*/
void setData(int width, int height, int channels, Unique<u8[]> data) {
width_ = width;
height_ = height;
channels_ = channels;
data_ = std::move(data);
setState(AssetState::Loaded);
}
/**
* @brief
*/
void release() {
data_.reset();
width_ = 0;
height_ = 0;
channels_ = 0;
setState(AssetState::Unloaded);
}
private:
int width_ = 0;
int height_ = 0;
int channels_ = 0;
Unique<u8[]> data_;
};
// ---------------------------------------------------------------------------
// FontAsset - 字体资源
// ---------------------------------------------------------------------------
/**
* @brief
*
* TrueType字体数据
* 使 Pimpl stbtt_fontinfo
*/
class FontAsset : public Asset {
public:
FontAsset();
~FontAsset() override;
AssetType type() const override { return AssetType::Font; }
bool loaded() const override;
size_t memSize() const override { return data_.size(); }
/**
* @brief
* @param pixels
* @return
*/
float scaleForPixelHeight(float pixels) const;
/**
* @brief
* @return
*/
const u8 *data() const { return data_.data(); }
/**
* @brief
* @return
*/
size_t dataSize() const { return data_.size(); }
/**
* @brief
* @param data
* @return true
*/
bool setData(std::vector<u8> data);
/**
* @brief
*/
void release();
private:
std::vector<u8> data_;
class Impl;
Unique<Impl> impl_;
};
// ---------------------------------------------------------------------------
// ShaderAsset - 着色器资源
// ---------------------------------------------------------------------------
/**
* @brief
*
*
*/
class ShaderAsset : public Asset {
public:
AssetType type() const override { return AssetType::Shader; }
bool loaded() const override {
return state_.load(std::memory_order_acquire) == AssetState::Loaded;
}
size_t memSize() const override {
return vertexSrc_.size() + fragmentSrc_.size();
}
/**
* @brief
* @return
*/
const std::string &vertexSource() const { return vertexSrc_; }
/**
* @brief
* @return
*/
const std::string &fragmentSource() const { return fragmentSrc_; }
/**
* @brief
* @param vertex
* @param fragment
*/
void setSource(std::string vertex, std::string fragment) {
vertexSrc_ = std::move(vertex);
fragmentSrc_ = std::move(fragment);
setState(AssetState::Loaded);
}
/**
* @brief
*/
void release() {
vertexSrc_.clear();
fragmentSrc_.clear();
setState(AssetState::Unloaded);
}
private:
std::string vertexSrc_;
std::string fragmentSrc_;
};
// ---------------------------------------------------------------------------
// AudioAsset - 音频资源
// ---------------------------------------------------------------------------
/**
* @brief
*/
enum class AudioFormat : u8 { PCM = 0, MP3 = 1, OGG = 2, WAV = 3 };
/**
* @brief
*
*
*/
class AudioAsset : public Asset {
public:
AssetType type() const override { return AssetType::Audio; }
bool loaded() const override {
return state_.load(std::memory_order_acquire) == AssetState::Loaded &&
!data_.empty();
}
size_t memSize() const override { return data_.size(); }
/**
* @brief
* @return
*/
AudioFormat format() const { return format_; }
/**
* @brief
* @return
*/
int channels() const { return channels_; }
/**
* @brief
* @return
*/
int sampleRate() const { return sampleRate_; }
/**
* @brief
* @return
*/
int bitsPerSample() const { return bitsPerSample_; }
/**
* @brief
* @return
*/
float duration() const { return duration_; }
/**
* @brief
* @return
*/
const u8 *data() const { return data_.data(); }
/**
* @brief
* @return
*/
size_t dataSize() const { return data_.size(); }
/**
* @brief
* @return true
*/
bool streaming() const { return streaming_; }
/**
* @brief
* @param format
* @param channels
* @param sampleRate
* @param bitsPerSample
* @param data
*/
void setData(AudioFormat format, int channels, int sampleRate,
int bitsPerSample, std::vector<u8> data) {
format_ = format;
channels_ = channels;
sampleRate_ = sampleRate;
bitsPerSample_ = bitsPerSample;
data_ = std::move(data);
if (sampleRate > 0 && channels > 0 && bitsPerSample > 0) {
size_t bytesPerSecond =
static_cast<size_t>(sampleRate) * channels * (bitsPerSample / 8);
if (bytesPerSecond > 0) {
duration_ = static_cast<float>(data_.size()) /
static_cast<float>(bytesPerSecond);
}
}
streaming_ = duration_ > 5.0f;
setState(AssetState::Loaded);
}
/**
* @brief
*/
void release() {
data_.clear();
format_ = AudioFormat::PCM;
channels_ = 0;
sampleRate_ = 0;
bitsPerSample_ = 0;
duration_ = 0.0f;
streaming_ = false;
setState(AssetState::Unloaded);
}
private:
AudioFormat format_ = AudioFormat::PCM;
int channels_ = 0;
int sampleRate_ = 0;
int bitsPerSample_ = 0;
float duration_ = 0.0f;
std::vector<u8> data_;
bool streaming_ = false;
};
// ---------------------------------------------------------------------------
// DataAsset - 通用数据资源
// ---------------------------------------------------------------------------
/**
* @brief
*
*
*/
class DataAsset : public Asset {
public:
AssetType type() const override { return AssetType::Data; }
bool loaded() const override {
return state_.load(std::memory_order_acquire) == AssetState::Loaded;
}
size_t memSize() const override { return data_.size(); }
/**
* @brief
* @return
*/
const u8 *data() const { return data_.data(); }
/**
* @brief
* @return
*/
size_t size() const { return data_.size(); }
/**
* @brief
* @param data
*/
void setData(std::vector<u8> data) {
data_ = std::move(data);
setState(AssetState::Loaded);
}
/**
* @brief
*/
void release() {
data_.clear();
setState(AssetState::Unloaded);
}
private:
std::vector<u8> data_;
};
} // namespace extra2d

View File

@ -0,0 +1,263 @@
#pragma once
#include <extra2d/asset/asset.h>
#include <extra2d/asset/asset_handle.h>
#include <extra2d/asset/asset_types.h>
#include <extra2d/core/types.h>
#include <algorithm>
#include <chrono>
#include <cstdint>
#include <deque>
#include <list>
#include <memory>
#include <mutex>
#include <shared_mutex>
#include <unordered_map>
#include <vector>
namespace extra2d {
// ---------------------------------------------------------------------------
// CacheEntry - 缓存条目
// ---------------------------------------------------------------------------
/**
* @brief
*
* 访LRU缓存管理
*/
struct CacheEntry {
Ref<Asset> asset;
std::chrono::steady_clock::time_point lastAccess;
size_t accessCount = 0;
/**
* @brief
* @param a
*/
explicit CacheEntry(Ref<Asset> a)
: asset(std::move(a)),
lastAccess(std::chrono::steady_clock::now()),
accessCount(1) {}
/**
* @brief 访
*/
void touch() {
lastAccess = std::chrono::steady_clock::now();
++accessCount;
}
};
// ---------------------------------------------------------------------------
// AssetCache - 资源缓存
// ---------------------------------------------------------------------------
/**
* @brief
*
*
*
*
* - LRU缓存淘汰策略
* - 线
* -
* -
* -
*/
class AssetCache {
public:
/**
* @brief
* @param limit 0
*/
explicit AssetCache(size_t limit = 0);
~AssetCache() = default;
AssetCache(const AssetCache&) = delete;
AssetCache& operator=(const AssetCache&) = delete;
// -------------------------------------------------------------------------
// 资源管理
// -------------------------------------------------------------------------
/**
* @brief
* @tparam T
* @param asset
* @return
*/
template<typename T>
AssetHandle<T> add(Ref<T> asset) {
static_assert(std::is_base_of_v<Asset, T>,
"T must derive from Asset");
if (!asset) {
return AssetHandle<T>();
}
std::unique_lock<std::shared_mutex> lock(mutex_);
AssetID id = asset->id();
size_t memSize = asset->memSize();
bytes_ += memSize;
auto it = lruList_.insert(lruList_.end(), id);
entries_[id] = CacheEntryData{
ptr::makeUnique<CacheEntry>(std::static_pointer_cast<Asset>(asset)),
it
};
++stats_.count;
if (limit_ > 0 && bytes_ > limit_) {
evict();
}
return AssetHandle<T>(id, Weak<T>(asset));
}
/**
* @brief
* @tparam T
* @param id ID
* @return
*/
template<typename T>
AssetHandle<T> get(const AssetID& id) {
static_assert(std::is_base_of_v<Asset, T>,
"T must derive from Asset");
std::unique_lock<std::shared_mutex> lock(mutex_);
auto it = entries_.find(id);
if (it == entries_.end()) {
++stats_.misses;
return AssetHandle<T>();
}
it->second.entry->touch();
lruList_.erase(it->second.lruIterator);
it->second.lruIterator = lruList_.insert(lruList_.end(), id);
++stats_.hits;
auto typedAsset = std::static_pointer_cast<T>(it->second.entry->asset);
return AssetHandle<T>(id, Weak<T>(typedAsset));
}
/**
* @brief
* @param id ID
* @return true
*/
bool has(const AssetID& id) const;
/**
* @brief
* @param id ID
* @return true
*/
bool remove(const AssetID& id);
// -------------------------------------------------------------------------
// 缓存管理
// -------------------------------------------------------------------------
/**
* @brief
* @param limit 0
*/
void setLimit(size_t limit);
/**
* @brief
* @return
*/
size_t limit() const { return limit_; }
/**
* @brief 使
* @return 使
*/
size_t bytes() const { return bytes_; }
/**
* @brief
* @return
*/
size_t count() const;
/**
* @brief 使
* @return 使
*/
size_t size() const { return bytes_; }
/**
* @brief
*/
void hit() { ++stats_.hits; }
/**
* @brief
*/
void miss() { ++stats_.misses; }
/**
* @brief
* @return
*/
size_t purge();
/**
* @brief
*/
void clear();
/**
* @brief
* @return
*/
CacheStats stats() const;
/**
* @brief
*/
void resetStats();
private:
/**
* @brief LRU迭代器
*/
struct CacheEntryData {
Ref<CacheEntry> entry;
std::list<AssetID>::iterator lruIterator;
};
/**
* @brief LRU策略
*/
void evict();
/**
* @brief
* @param entry
* @return true
*/
bool canEvict(const CacheEntry& entry) const;
mutable std::shared_mutex mutex_;
std::unordered_map<AssetID, CacheEntryData> entries_;
std::list<AssetID> lruList_;
size_t limit_ = 0;
size_t bytes_ = 0;
mutable CacheStats stats_;
};
}

View File

@ -0,0 +1,304 @@
#pragma once
#include <extra2d/asset/asset_types.h>
#include <extra2d/core/types.h>
#include <memory>
namespace extra2d {
// ---------------------------------------------------------------------------
// AssetHandleBase - 资源句柄非模板基类
// ---------------------------------------------------------------------------
/**
* @brief
*
*
*/
class AssetHandleBase {
public:
AssetHandleBase() = default;
/**
* @brief ID和弱引用构造
* @param id ID
* @param ref
*/
AssetHandleBase(const AssetID& id, Weak<Asset> ref)
: id_(id), cacheRef_(std::move(ref)) {}
/**
* @brief
* @param ptr
*/
explicit AssetHandleBase(Ref<Asset> ptr)
: id_(ptr ? ptr->id() : AssetID()),
cacheRef_(ptr) {}
/**
* @brief
* @return true
*/
bool valid() const {
return !cacheRef_.expired();
}
/**
* @brief
* @return
*/
Ref<Asset> get() const {
return cacheRef_.lock();
}
/**
* @brief ID
* @return ID
*/
const AssetID& id() const { return id_; }
/**
* @brief
* @return
*/
const std::string& path() const { return id_.path; }
/**
* @brief bool
*/
explicit operator bool() const { return valid(); }
/**
* @brief
*/
void reset() {
id_ = AssetID();
cacheRef_.reset();
}
protected:
AssetID id_;
Weak<Asset> cacheRef_;
};
// ---------------------------------------------------------------------------
// AssetHandle - 资源句柄
// ---------------------------------------------------------------------------
/**
* @brief
*
* 使
* 使 weak_ptr
*
*
* -
* -
* -
* - 使 valid()
*
* @tparam T Asset
*/
template<typename T>
class AssetHandle : public AssetHandleBase {
static_assert(std::is_base_of_v<Asset, T>,
"T must derive from Asset");
public:
/**
* @brief
*/
AssetHandle() = default;
/**
* @brief
* @param base
*/
explicit AssetHandle(const AssetHandleBase& base)
: AssetHandleBase(base) {}
/**
* @brief ID和弱引用构造
* @param id ID
* @param ref
*/
AssetHandle(const AssetID& id, Weak<T> ref)
: AssetHandleBase(id, std::move(ref)) {}
/**
* @brief
* @param ptr
*/
explicit AssetHandle(Ref<T> ptr)
: AssetHandleBase(ptr) {}
/**
* @brief
* @return nullptr
*/
Ref<T> get() const {
return std::static_pointer_cast<T>(cacheRef_.lock());
}
/**
* @brief
* @return
* @note 使 valid()
*/
T* operator->() const {
auto ptr = get();
return ptr ? ptr.get() : nullptr;
}
/**
* @brief
* @return
* @note 使 valid()
*/
T& operator*() const {
auto ptr = get();
return *ptr;
}
/**
* @brief
* @param other
* @return true
*/
bool operator==(const AssetHandle& other) const {
return id_ == other.id_;
}
/**
* @brief
* @param other
* @return true
*/
bool operator!=(const AssetHandle& other) const {
return id_ != other.id_;
}
/**
* @brief
* @param other
* @return true
*/
bool operator<(const AssetHandle& other) const {
return id_ < other.id_;
}
/**
* @brief
*/
void reset() {
id_ = AssetID();
cacheRef_.reset();
}
/**
* @brief
* @return true
*/
bool loaded() const {
auto ptr = get();
return ptr && ptr->loaded();
}
/**
* @brief
* @return
*/
AssetState state() const {
auto ptr = get();
return ptr ? ptr->state() : AssetState::Unloaded;
}
};
// ---------------------------------------------------------------------------
// AssetLoadResult - 资源加载结果
// ---------------------------------------------------------------------------
/**
* @brief
*
*
*
* @tparam T
*/
template<typename T>
struct AssetLoadResult {
AssetHandle<T> handle;
AssetState state = AssetState::Unloaded;
std::string error;
/**
* @brief
* @return true
*/
bool success() const {
return state == AssetState::Loaded && handle.valid();
}
/**
* @brief
* @return true
*/
bool failed() const {
return state == AssetState::Failed;
}
/**
* @brief
* @return true
*/
bool loading() const {
return state == AssetState::Loading;
}
/**
* @brief
* @param handle
* @return
*/
static AssetLoadResult ok(AssetHandle<T> handle) {
return { std::move(handle), AssetState::Loaded, "" };
}
/**
* @brief
* @param error
* @return
*/
static AssetLoadResult err(const std::string& error) {
return { AssetHandle<T>(), AssetState::Failed, error };
}
/**
* @brief
* @param handle
* @return
*/
static AssetLoadResult pending(AssetHandle<T> handle = {}) {
return { std::move(handle), AssetState::Loading, "" };
}
};
// ---------------------------------------------------------------------------
// AssetLoadCallback - 资源加载回调
// ---------------------------------------------------------------------------
/**
* @brief
* @tparam T
*/
template<typename T>
using AssetLoadCallback = Fn<void(AssetHandle<T>)>;
/**
* @brief
* @tparam T
*/
template<typename T>
using AssetLoadResultCallback = Fn<void(AssetLoadResult<T>)>;
}

View File

@ -0,0 +1,312 @@
#pragma once
#include <extra2d/asset/asset.h>
#include <extra2d/asset/asset_types.h>
#include <extra2d/core/types.h>
#include <memory>
#include <string>
#include <vector>
namespace extra2d {
// ---------------------------------------------------------------------------
// AssetLoaderBase - 资源加载器非模板基类
// ---------------------------------------------------------------------------
/**
* @brief
*
*
*/
class AssetLoaderBase {
public:
virtual ~AssetLoaderBase() = default;
/**
* @brief Asset
* @param path
* @return
*/
virtual Ref<Asset> loadBase(const std::string& path) = 0;
/**
* @brief
* @param data
* @param size
* @return
*/
virtual Ref<Asset> loadFromMemoryBase(const u8* data, size_t size) = 0;
/**
* @brief
* @param path
* @return true
*/
virtual bool canLoad(const std::string& path) const = 0;
/**
* @brief
* @return
*/
virtual AssetType type() const = 0;
/**
* @brief
* @return
*/
virtual std::vector<std::string> extensions() const = 0;
};
// ---------------------------------------------------------------------------
// AssetLoader - 资源加载器接口
// ---------------------------------------------------------------------------
/**
* @brief
*
* 使
*
*
* @tparam T
*/
template<typename T>
class AssetLoader : public AssetLoaderBase {
static_assert(std::is_base_of_v<Asset, T>,
"T must derive from Asset");
public:
virtual ~AssetLoader() = default;
/**
* @brief
* @param path
* @return nullptr
*/
virtual Ref<T> load(const std::string& path) = 0;
/**
* @brief
* @param data
* @param size
* @return nullptr
*/
virtual Ref<T> loadFromMemory(const u8* data, size_t size) = 0;
Ref<Asset> loadBase(const std::string& path) override {
return load(path);
}
Ref<Asset> loadFromMemoryBase(const u8* data, size_t size) override {
return loadFromMemory(data, size);
}
};
// ---------------------------------------------------------------------------
// TextureLoader - 纹理加载器
// ---------------------------------------------------------------------------
/**
* @brief
*
* 使 stb_image
* PNG, JPG, BMP, TGA, GIF, PSD, HDR, PIC
*/
class TextureLoader : public AssetLoader<TextureAsset> {
public:
TextureLoader();
~TextureLoader() override;
Ref<TextureAsset> load(const std::string& path) override;
Ref<TextureAsset> loadFromMemory(const u8* data, size_t size) override;
bool canLoad(const std::string& path) const override;
AssetType type() const override { return AssetType::Texture; }
std::vector<std::string> extensions() const override;
/**
* @brief
* @param channels 1-40
*/
void setDesiredChannels(int channels);
/**
* @brief
* @return
*/
int desiredChannels() const { return desiredChannels_; }
private:
int desiredChannels_ = 4;
};
// ---------------------------------------------------------------------------
// FontLoader - 字体加载器
// ---------------------------------------------------------------------------
/**
* @brief
*
* TrueType .ttf, .otf
*/
class FontLoader : public AssetLoader<FontAsset> {
public:
Ref<FontAsset> load(const std::string& path) override;
Ref<FontAsset> loadFromMemory(const u8* data, size_t size) override;
bool canLoad(const std::string& path) const override;
AssetType type() const override { return AssetType::Font; }
std::vector<std::string> extensions() const override;
};
// ---------------------------------------------------------------------------
// ShaderLoader - 着色器加载器
// ---------------------------------------------------------------------------
/**
* @brief
*
*
* - .vert/.frag: /
* - .glsl: 使
*/
class ShaderLoader : public AssetLoader<ShaderAsset> {
public:
Ref<ShaderAsset> load(const std::string& path) override;
Ref<ShaderAsset> loadFromMemory(const u8* data, size_t size) override;
bool canLoad(const std::string& path) const override;
AssetType type() const override { return AssetType::Shader; }
std::vector<std::string> extensions() const override;
/**
* @brief
* @param marker "[VERTEX]"
*/
void setVertexMarker(const std::string& marker) { vertexMarker_ = marker; }
/**
* @brief
* @param marker "[FRAGMENT]"
*/
void setFragmentMarker(const std::string& marker) { fragmentMarker_ = marker; }
private:
std::string vertexMarker_ = "[VERTEX]";
std::string fragmentMarker_ = "[FRAGMENT]";
/**
* @brief
* @param content
* @param vertex
* @param fragment
* @return true
*/
bool parseCombined(const std::string& content,
std::string& vertex,
std::string& fragment);
};
// ---------------------------------------------------------------------------
// AudioLoader - 音频加载器
// ---------------------------------------------------------------------------
/**
* @brief
*
* WAV
* MP3, OGG
*/
class AudioLoader : public AssetLoader<AudioAsset> {
public:
Ref<AudioAsset> load(const std::string& path) override;
Ref<AudioAsset> loadFromMemory(const u8* data, size_t size) override;
bool canLoad(const std::string& path) const override;
AssetType type() const override { return AssetType::Audio; }
std::vector<std::string> extensions() const override;
private:
/**
* @brief WAV
* @param data
* @param size
* @return
*/
Ref<AudioAsset> loadWav(const u8* data, size_t size);
};
// ---------------------------------------------------------------------------
// DataLoader - 通用数据加载器
// ---------------------------------------------------------------------------
/**
* @brief
*
*
*/
class DataLoader : public AssetLoader<DataAsset> {
public:
Ref<DataAsset> load(const std::string& path) override;
Ref<DataAsset> loadFromMemory(const u8* data, size_t size) override;
bool canLoad(const std::string& path) const override;
AssetType type() const override { return AssetType::Data; }
std::vector<std::string> extensions() const override;
};
// ---------------------------------------------------------------------------
// AssetLoaderFactory - 加载器工厂
// ---------------------------------------------------------------------------
/**
* @brief
*
*
* 使
*/
class AssetLoaderFactory {
public:
/**
* @brief
* @return
*/
static Unique<TextureLoader> createTextureLoader() {
return ptr::makeUnique<TextureLoader>();
}
/**
* @brief
* @return
*/
static Unique<FontLoader> createFontLoader() {
return ptr::makeUnique<FontLoader>();
}
/**
* @brief
* @return
*/
static Unique<ShaderLoader> createShaderLoader() {
return ptr::makeUnique<ShaderLoader>();
}
/**
* @brief
* @return
*/
static Unique<AudioLoader> createAudioLoader() {
return ptr::makeUnique<AudioLoader>();
}
/**
* @brief
* @return
*/
static Unique<DataLoader> createDataLoader() {
return ptr::makeUnique<DataLoader>();
}
/**
* @brief
* @param extension ".png"
* @return AssetType::Unknown
*/
static AssetType getTypeByExtension(const std::string& extension);
};
}

View File

@ -0,0 +1,399 @@
#pragma once
#include <extra2d/asset/asset_types.h>
#include <extra2d/asset/data_processor.h>
#include <extra2d/core/types.h>
#include <fstream>
#include <functional>
#include <memory>
#include <string>
#include <unordered_map>
#include <vector>
namespace extra2d {
// ---------------------------------------------------------------------------
// AssetPack - 资源包
// ---------------------------------------------------------------------------
/**
* @brief
*
*
*
* - AssetPackageHeader
* - + AssetPackageEntry
* -
*/
class AssetPack {
public:
AssetPack() = default;
/**
* @brief
* @param other
*/
AssetPack(AssetPack&& other) noexcept;
/**
* @brief
* @param other
* @return
*/
AssetPack& operator=(AssetPack&& other) noexcept;
/**
* @brief
*/
~AssetPack();
/**
* @brief
* @param path
* @return true
*/
bool open(const std::string& path);
/**
* @brief
*/
void close();
/**
* @brief
* @return true
*/
bool isOpen() const { return file_.is_open(); }
/**
* @brief
* @param id ID
* @return true
*/
bool has(const AssetID& id) const;
/**
* @brief
* @param path
* @return true
*/
bool has(const std::string& path) const;
/**
* @brief /
* @param id ID
* @return
*/
std::vector<u8> read(const AssetID& id);
/**
* @brief /
* @param path
* @return
*/
std::vector<u8> read(const std::string& path);
/**
* @brief /
* @param id ID
* @return
*/
std::vector<u8> readRaw(const AssetID& id);
/**
* @brief ID
* @return ID列表
*/
std::vector<AssetID> assets() const;
/**
* @brief
* @return
*/
const std::string& path() const { return path_; }
/**
* @brief
* @return
*/
const AssetPackageHeader& header() const { return header_; }
/**
* @brief
* @return
*/
size_t count() const { return entries_.size(); }
/**
* @brief
* @param pipe
*/
void setPipe(DataPipe pipe) { pipe_ = std::move(pipe); }
/**
* @brief
* @param id ID
* @return nullptr
*/
const AssetPackageEntry* getEntry(const AssetID& id) const;
private:
std::string path_;
mutable std::ifstream file_;
AssetPackageHeader header_;
std::unordered_map<AssetID, AssetPackageEntry> entries_;
DataPipe pipe_;
/**
* @brief
* @return true
*/
bool readHeader();
/**
* @brief
* @return true
*/
bool readIndex();
/**
* @brief
* @param entry
* @return
*/
std::vector<u8> readEntryData(const AssetPackageEntry& entry);
AssetPack(const AssetPack&) = delete;
AssetPack& operator=(const AssetPack&) = delete;
};
// ---------------------------------------------------------------------------
// PackManager - 资源包管理器
// ---------------------------------------------------------------------------
/**
* @brief
*
*
* /
*/
class PackManager {
public:
PackManager() = default;
/**
* @brief
*/
~PackManager() = default;
/**
* @brief
* @param path
* @return true
*/
bool mount(const std::string& path);
/**
* @brief
* @param path
*/
void unmount(const std::string& path);
/**
* @brief
*/
void unmountAll();
/**
* @brief
* @param id ID
* @return nullptr
*/
AssetPack* find(const AssetID& id);
/**
* @brief
* @param path
* @return nullptr
*/
AssetPack* find(const std::string& path);
/**
* @brief
* @param id ID
* @return true
*/
bool has(const AssetID& id) const;
/**
* @brief
* @param path
* @return true
*/
bool has(const std::string& path) const;
/**
* @brief
* @param id ID
* @return
*/
std::vector<u8> read(const AssetID& id);
/**
* @brief
* @param path
* @return
*/
std::vector<u8> read(const std::string& path);
/**
* @brief
* @param pipe
*/
void setPipe(DataPipe pipe) { defaultPipe_ = std::move(pipe); }
/**
* @brief
* @return
*/
const DataPipe& pipe() const { return defaultPipe_; }
/**
* @brief
* @return
*/
size_t count() const { return packs_.size(); }
/**
* @brief ID
* @return ID列表
*/
std::vector<AssetID> allAssets() const;
/**
* @brief
* @return
*/
std::vector<std::string> mountedPacks() const;
private:
std::vector<Unique<AssetPack>> packs_;
DataPipe defaultPipe_;
PackManager(const PackManager&) = delete;
PackManager& operator=(const PackManager&) = delete;
};
// ---------------------------------------------------------------------------
// AssetPackBuilder - 资源包构建器(用于打包工具)
// ---------------------------------------------------------------------------
/**
* @brief
*
*
*
*/
class AssetPackBuilder {
public:
/**
* @brief
* @param compression
* @param level
*/
explicit AssetPackBuilder(Compression compression = Compression::None, int level = 3);
/**
* @brief
* @param path
* @param data
*/
void add(const std::string& path, const std::vector<u8>& data);
/**
* @brief
* @param path
* @param data
*/
void add(const std::string& path, std::vector<u8>&& data);
/**
* @brief
* @param filePath
* @param packPath 使
* @return true
*/
bool addFile(const std::string& filePath, const std::string& packPath = "");
/**
* @brief
* @param dirPath
* @param prefix
* @return
*/
size_t addDirectory(const std::string& dirPath, const std::string& prefix = "");
/**
* @brief
* @param key
* @param type
*/
void setEncryption(const std::string& key, Decryptor::Type type = Decryptor::Type::XOR);
/**
* @brief
* @param outputPath
* @return true
*/
bool build(const std::string& outputPath);
/**
* @brief
*/
void clear();
/**
* @brief
* @return
*/
size_t count() const { return entries_.size(); }
/**
* @brief
* @return
*/
size_t totalOriginalSize() const { return totalOriginalSize_; }
/**
* @brief
* @return
*/
size_t totalCompressedSize() const { return totalCompressedSize_; }
private:
struct BuilderEntry {
AssetID id;
std::vector<u8> data;
std::vector<u8> compressedData;
u64 offset;
u32 compression;
u32 flags;
};
Compression compression_;
int level_;
std::string encryptKey_;
Decryptor::Type encryptType_ = Decryptor::Type::None;
std::vector<BuilderEntry> entries_;
size_t totalOriginalSize_ = 0;
size_t totalCompressedSize_ = 0;
/**
* @brief /
* @param data
* @return
*/
std::vector<u8> processData(const std::vector<u8>& data);
};
}

View File

@ -0,0 +1,270 @@
#pragma once
#include <extra2d/core/types.h>
#include <cstdint>
#include <functional>
#include <string>
namespace extra2d {
// ---------------------------------------------------------------------------
// AssetID - 强类型资源标识符
// ---------------------------------------------------------------------------
/**
* @brief ID
*
* 使ID替代裸字符串
* -
* -
* -
*/
struct AssetID {
u64 hash = 0;
std::string path;
AssetID() = default;
/**
* @brief ID
* @param p
*/
explicit AssetID(const std::string& p)
: hash(hashPath(p)), path(p) {}
/**
* @brief ID
* @param p
*/
explicit AssetID(std::string&& p)
: hash(hashPath(p)), path(std::move(p)) {}
/**
* @brief
* @param p
* @return 64
*/
static u64 hashPath(const std::string& p) {
u64 result = 14695981039346656037ULL;
for (char c : p) {
result ^= static_cast<u64>(static_cast<u8>(c));
result *= 1099511628211ULL;
}
return result;
}
/**
* @brief ID是否有效
* @return true
*/
bool valid() const { return hash != 0 || !path.empty(); }
/**
* @brief
*/
explicit operator bool() const { return valid(); }
/**
* @brief
*/
bool operator==(const AssetID& other) const { return hash == other.hash; }
/**
* @brief
*/
bool operator!=(const AssetID& other) const { return hash != other.hash; }
/**
* @brief
*/
bool operator<(const AssetID& other) const { return hash < other.hash; }
};
// ---------------------------------------------------------------------------
// AssetType - 资源类型枚举
// ---------------------------------------------------------------------------
/**
* @brief
*
*/
enum class AssetType : u8 {
Unknown = 0,
Texture = 1,
Font = 2,
Shader = 3,
Audio = 4,
Data = 5,
Custom = 255
};
/**
* @brief
* @param type
* @return
*/
inline const char* assetTypeName(AssetType type) {
switch (type) {
case AssetType::Texture: return "Texture";
case AssetType::Font: return "Font";
case AssetType::Shader: return "Shader";
case AssetType::Audio: return "Audio";
case AssetType::Data: return "Data";
case AssetType::Custom: return "Custom";
default: return "Unknown";
}
}
// ---------------------------------------------------------------------------
// AssetState - 资源状态枚举
// ---------------------------------------------------------------------------
/**
* @brief
*
*/
enum class AssetState : u8 {
Unloaded = 0,
Loading = 1,
Loaded = 2,
Failed = 3,
Unloading = 4
};
/**
* @brief
* @param state
* @return
*/
inline const char* assetStateName(AssetState state) {
switch (state) {
case AssetState::Unloaded: return "Unloaded";
case AssetState::Loading: return "Loading";
case AssetState::Loaded: return "Loaded";
case AssetState::Failed: return "Failed";
case AssetState::Unloading: return "Unloading";
default: return "Unknown";
}
}
// ---------------------------------------------------------------------------
// Compression - 压缩算法枚举
// ---------------------------------------------------------------------------
/**
* @brief
*
*/
enum class Compression : u8 {
None = 0,
Zstd = 1,
LZ4 = 2,
Zlib = 3
};
/**
* @brief
* @param comp
* @return
*/
inline const char* compressionName(Compression comp) {
switch (comp) {
case Compression::Zstd: return "Zstd";
case Compression::LZ4: return "LZ4";
case Compression::Zlib: return "Zlib";
default: return "None";
}
}
// ---------------------------------------------------------------------------
// CacheStats - 缓存统计结构
// ---------------------------------------------------------------------------
/**
* @brief
* 使
*/
struct CacheStats {
size_t bytes = 0;
size_t limit = 0;
size_t count = 0;
size_t hits = 0;
size_t misses = 0;
/**
* @brief
* @return 0.0 - 1.0
*/
float hitRate() const {
size_t total = hits + misses;
return total > 0 ? static_cast<float>(hits) / static_cast<float>(total) : 0.0f;
}
/**
* @brief 使
* @return 使0.0 - 1.0
*/
float usage() const {
return limit > 0 ? static_cast<float>(bytes) / static_cast<float>(limit) : 0.0f;
}
};
// ---------------------------------------------------------------------------
// AssetPackageHeader - 资源包头部信息
// ---------------------------------------------------------------------------
/**
* @brief
*
*/
struct AssetPackageHeader {
u32 magic = 0;
u32 version = 0;
u32 compressionType = 0;
u32 encryptionType = 0;
u64 originalSize = 0;
u64 compressedSize = 0;
u8 checksum[32] = {};
static constexpr u32 MAGIC = 0x4B325045; // 'E2PK'
/**
* @brief
* @return true
*/
bool valid() const { return magic == MAGIC; }
};
// ---------------------------------------------------------------------------
// AssetPackageEntry - 资源包索引项
// ---------------------------------------------------------------------------
/**
* @brief
*
*/
struct AssetPackageEntry {
AssetID id;
u64 offset = 0;
u64 size = 0;
u64 originalSize = 0;
u32 compression = 0;
u32 flags = 0;
};
}
// ---------------------------------------------------------------------------
// std::hash 特化 - 支持在 unordered_map/unordered_set 中使用 AssetID
// ---------------------------------------------------------------------------
namespace std {
template<>
struct hash<extra2d::AssetID> {
size_t operator()(const extra2d::AssetID& id) const noexcept {
return static_cast<size_t>(id.hash);
}
};
}

View File

@ -0,0 +1,397 @@
#pragma once
#include <extra2d/asset/asset_types.h>
#include <extra2d/core/types.h>
#include <functional>
#include <memory>
#include <vector>
namespace extra2d {
// ---------------------------------------------------------------------------
// DataProcessor - 数据处理器接口(装饰器模式)
// ---------------------------------------------------------------------------
/**
* @brief
*
* 使
*
*/
class DataProcessor {
public:
virtual ~DataProcessor() = default;
/**
* @brief
* @param input
* @return
*/
virtual std::vector<u8> process(const std::vector<u8>& input) = 0;
/**
* @brief
* @param next
*/
void setNext(Unique<DataProcessor> next) { next_ = std::move(next); }
/**
* @brief
* @return
*/
DataProcessor* next() const { return next_.get(); }
protected:
/**
* @brief
* @param input
* @return
*/
std::vector<u8> processNext(const std::vector<u8>& input) {
return next_ ? next_->process(input) : input;
}
Unique<DataProcessor> next_;
};
// ---------------------------------------------------------------------------
// Decryptor - 解密器
// ---------------------------------------------------------------------------
/**
* @brief
*
* 使 XOR AES
* XOR AES-256
*/
class Decryptor : public DataProcessor {
public:
/**
* @brief
*/
enum class Type : u8 {
None = 0,
XOR = 1,
AES256 = 2
};
/**
* @brief
* @param key
* @param type
*/
explicit Decryptor(const std::string& key, Type type = Type::XOR);
/**
* @brief
* @param input
* @return
*/
std::vector<u8> process(const std::vector<u8>& input) override;
/**
* @brief
* @return
*/
Type type() const { return type_; }
private:
std::string key_;
Type type_;
/**
* @brief XOR
* @param input
* @return
*/
std::vector<u8> decryptXOR(const std::vector<u8>& input);
/**
* @brief AES-256
* @param input
* @return
*/
std::vector<u8> decryptAES256(const std::vector<u8>& input);
};
// ---------------------------------------------------------------------------
// Decompressor - 解压器
// ---------------------------------------------------------------------------
/**
* @brief
*
*
* ZstdLZ4 Zlib
*/
class Decompressor : public DataProcessor {
public:
/**
* @brief
* @param algo
*/
explicit Decompressor(Compression algo = Compression::Zstd);
/**
* @brief
* @param input
* @return
*/
std::vector<u8> process(const std::vector<u8>& input) override;
/**
* @brief
* @return
*/
Compression algorithm() const { return algo_; }
private:
Compression algo_;
/**
* @brief Zstd
* @param input
* @return
*/
std::vector<u8> decompressZstd(const std::vector<u8>& input);
/**
* @brief LZ4
* @param input
* @return
*/
std::vector<u8> decompressLZ4(const std::vector<u8>& input);
/**
* @brief Zlib
* @param input
* @return
*/
std::vector<u8> decompressZlib(const std::vector<u8>& input);
};
// ---------------------------------------------------------------------------
// Encryptor - 加密器
// ---------------------------------------------------------------------------
/**
* @brief
*
* 使 XOR AES
*/
class Encryptor : public DataProcessor {
public:
/**
* @brief
* @param key
* @param type
*/
explicit Encryptor(const std::string& key, Decryptor::Type type = Decryptor::Type::XOR);
/**
* @brief
* @param input
* @return
*/
std::vector<u8> process(const std::vector<u8>& input) override;
/**
* @brief
* @return
*/
Decryptor::Type type() const { return type_; }
private:
std::string key_;
Decryptor::Type type_;
/**
* @brief XOR
* @param input
* @return
*/
std::vector<u8> encryptXOR(const std::vector<u8>& input);
/**
* @brief AES-256
* @param input
* @return
*/
std::vector<u8> encryptAES256(const std::vector<u8>& input);
};
// ---------------------------------------------------------------------------
// Compressor - 压缩器
// ---------------------------------------------------------------------------
/**
* @brief
*
*
*/
class Compressor : public DataProcessor {
public:
/**
* @brief
* @param algo
* @param level 1-22
*/
explicit Compressor(Compression algo = Compression::Zstd, int level = 3);
/**
* @brief
* @param input
* @return
*/
std::vector<u8> process(const std::vector<u8>& input) override;
/**
* @brief
* @return
*/
Compression algorithm() const { return algo_; }
private:
Compression algo_;
int level_;
/**
* @brief Zstd
* @param input
* @return
*/
std::vector<u8> compressZstd(const std::vector<u8>& input);
/**
* @brief LZ4
* @param input
* @return
*/
std::vector<u8> compressLZ4(const std::vector<u8>& input);
/**
* @brief Zlib
* @param input
* @return
*/
std::vector<u8> compressZlib(const std::vector<u8>& input);
};
// ---------------------------------------------------------------------------
// DataPipe - 数据处理管道
// ---------------------------------------------------------------------------
/**
* @brief
*
* 使 API
*
*
* @example
* DataPipe pipe;
* pipe.decrypt("secret-key").decompress(Compression::Zstd);
* auto result = pipe.process(data);
*/
class DataPipe {
public:
DataPipe() = default;
/**
* @brief
* @param other
*/
DataPipe(DataPipe&& other) noexcept = default;
/**
* @brief
* @param other
* @return
*/
DataPipe& operator=(DataPipe&& other) noexcept = default;
/**
* @brief
* @param key
* @param type
* @return
*/
DataPipe& decrypt(const std::string& key, Decryptor::Type type = Decryptor::Type::XOR);
/**
* @brief
* @param algo
* @return
*/
DataPipe& decompress(Compression algo);
/**
* @brief
* @param key
* @param type
* @return
*/
DataPipe& encrypt(const std::string& key, Decryptor::Type type = Decryptor::Type::XOR);
/**
* @brief
* @param algo
* @param level
* @return
*/
DataPipe& compress(Compression algo, int level = 3);
/**
* @brief
* @param processor
* @return
*/
DataPipe& add(Unique<DataProcessor> processor);
/**
* @brief
* @param input
* @return
*/
std::vector<u8> process(const std::vector<u8>& input);
/**
* @brief
*/
void clear();
/**
* @brief
* @return true
*/
bool empty() const { return processors_.empty(); }
/**
* @brief
* @return
*/
size_t size() const { return processors_.size(); }
private:
std::vector<Unique<DataProcessor>> processors_;
};
// ---------------------------------------------------------------------------
// 工具函数
// ---------------------------------------------------------------------------
/**
* @brief SHA-256
* @param data
* @return 32
*/
std::vector<u8> computeChecksum(const std::vector<u8>& data);
/**
* @brief SHA-256
* @param data
* @param checksum
* @return true
*/
bool verifyChecksum(const std::vector<u8>& data, const std::vector<u8>& checksum);
}

View File

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

View File

@ -0,0 +1,334 @@
#pragma once
#include <condition_variable>
#include <extra2d/asset/asset.h>
#include <extra2d/asset/asset_cache.h>
#include <extra2d/asset/asset_handle.h>
#include <extra2d/asset/asset_loader.h>
#include <extra2d/asset/asset_pack.h>
#include <extra2d/asset/asset_types.h>
#include <extra2d/asset/data_processor.h>
#include <extra2d/core/service_interface.h>
#include <extra2d/core/service_locator.h>
#include <extra2d/core/types.h>
#include <functional>
#include <future>
#include <memory>
#include <mutex>
#include <queue>
#include <shared_mutex>
#include <string>
#include <thread>
#include <typeindex>
#include <unordered_map>
#include <vector>
namespace extra2d {
// ---------------------------------------------------------------------------
// IAssetService - 资源服务接口
// ---------------------------------------------------------------------------
/**
* @brief
*
*
* 使
*/
class IAssetService : public IService {
public:
virtual ~IAssetService() = default;
/**
* @brief
* @tparam T
* @param path
* @return
*/
template <typename T> AssetHandle<T> load(const std::string &path) {
static_assert(std::is_base_of_v<Asset, T>, "T must derive from Asset");
return 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

View File

@ -0,0 +1,62 @@
#include <extra2d/asset/asset.h>
#define STB_TRUETYPE_IMPLEMENTATION
#include <stb/stb_truetype.h>
namespace extra2d {
// ---------------------------------------------------------------------------
// FontAsset::Impl - Pimpl 实现类
// ---------------------------------------------------------------------------
class FontAsset::Impl {
public:
stbtt_fontinfo info;
bool initialized = false;
};
// ---------------------------------------------------------------------------
// FontAsset 实现
// ---------------------------------------------------------------------------
FontAsset::FontAsset() : impl_(ptr::makeUnique<Impl>()) {}
FontAsset::~FontAsset() = default;
bool FontAsset::loaded() const {
return state_.load(std::memory_order_acquire) == AssetState::Loaded && impl_->initialized;
}
float FontAsset::scaleForPixelHeight(float pixels) const {
if (!impl_->initialized || data_.empty()) {
return 0.0f;
}
return stbtt_ScaleForPixelHeight(&impl_->info, pixels);
}
bool FontAsset::setData(std::vector<u8> data) {
if (data.empty()) {
return false;
}
data_ = std::move(data);
if (!stbtt_InitFont(&impl_->info, data_.data(), 0)) {
data_.clear();
impl_->initialized = false;
setState(AssetState::Failed);
return false;
}
impl_->initialized = true;
setState(AssetState::Loaded);
return true;
}
void FontAsset::release() {
data_.clear();
impl_->initialized = false;
setState(AssetState::Unloaded);
}
}

View File

@ -0,0 +1,121 @@
#include <extra2d/asset/asset_cache.h>
namespace extra2d {
// ---------------------------------------------------------------------------
// AssetCache 实现
// ---------------------------------------------------------------------------
AssetCache::AssetCache(size_t limit)
: limit_(limit) {
stats_.limit = limit;
}
bool AssetCache::has(const AssetID& id) const {
std::shared_lock<std::shared_mutex> lock(mutex_);
return entries_.find(id) != entries_.end();
}
bool AssetCache::remove(const AssetID& id) {
std::unique_lock<std::shared_mutex> lock(mutex_);
auto it = entries_.find(id);
if (it == entries_.end()) {
return false;
}
bytes_ -= it->second.entry->asset->memSize();
lruList_.erase(it->second.lruIterator);
entries_.erase(it);
--stats_.count;
return true;
}
void AssetCache::setLimit(size_t limit) {
std::unique_lock<std::shared_mutex> lock(mutex_);
limit_ = limit;
stats_.limit = limit;
if (limit > 0 && bytes_ > limit) {
evict();
}
}
size_t AssetCache::count() const {
std::shared_lock<std::shared_mutex> lock(mutex_);
return entries_.size();
}
size_t AssetCache::purge() {
std::unique_lock<std::shared_mutex> lock(mutex_);
size_t purged = 0;
auto it = entries_.begin();
while (it != entries_.end()) {
if (canEvict(*it->second.entry)) {
bytes_ -= it->second.entry->asset->memSize();
lruList_.erase(it->second.lruIterator);
it = entries_.erase(it);
++purged;
--stats_.count;
} else {
++it;
}
}
return purged;
}
void AssetCache::clear() {
std::unique_lock<std::shared_mutex> lock(mutex_);
entries_.clear();
lruList_.clear();
bytes_ = 0;
stats_.count = 0;
stats_.bytes = 0;
}
CacheStats AssetCache::stats() const {
std::shared_lock<std::shared_mutex> lock(mutex_);
stats_.bytes = bytes_;
return stats_;
}
void AssetCache::resetStats() {
std::unique_lock<std::shared_mutex> lock(mutex_);
stats_.hits = 0;
stats_.misses = 0;
}
void AssetCache::evict() {
while (!lruList_.empty() && (limit_ > 0 && bytes_ > limit_)) {
AssetID id = lruList_.front();
auto it = entries_.find(id);
if (it != entries_.end()) {
if (canEvict(*it->second.entry)) {
bytes_ -= it->second.entry->asset->memSize();
entries_.erase(it);
lruList_.pop_front();
--stats_.count;
continue;
}
}
lruList_.pop_front();
if (it != entries_.end()) {
it->second.lruIterator = lruList_.insert(lruList_.end(), id);
}
break;
}
}
bool AssetCache::canEvict(const CacheEntry& entry) const {
long useCount = entry.asset.use_count();
return useCount <= 1;
}
}

View File

@ -0,0 +1,418 @@
#include <extra2d/asset/asset_loader.h>
#include <extra2d/services/logger_service.h>
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#include <fstream>
#include <sstream>
#define STB_IMAGE_IMPLEMENTATION
#include <stb/stb_image.h>
namespace extra2d {
// ---------------------------------------------------------------------------
// 辅助函数
// ---------------------------------------------------------------------------
namespace {
/**
* @brief
*/
std::string toLower(const std::string& str) {
std::string result = str;
std::transform(result.begin(), result.end(), result.begin(),
[](unsigned char c) { return std::tolower(c); });
return result;
}
/**
* @brief
*/
std::string getExtension(const std::string& path) {
size_t pos = path.rfind('.');
if (pos == std::string::npos) {
return "";
}
return toLower(path.substr(pos));
}
/**
* @brief
*/
std::vector<u8> readFile(const std::string& path) {
std::ifstream file(path, std::ios::binary | std::ios::ate);
if (!file) {
return {};
}
size_t size = static_cast<size_t>(file.tellg());
file.seekg(0, std::ios::beg);
std::vector<u8> data(size);
if (!file.read(reinterpret_cast<char*>(data.data()), size)) {
return {};
}
return data;
}
}
// ---------------------------------------------------------------------------
// TextureLoader 实现
// ---------------------------------------------------------------------------
TextureLoader::TextureLoader() = default;
TextureLoader::~TextureLoader() = default;
Ref<TextureAsset> TextureLoader::load(const std::string& path) {
auto data = readFile(path);
if (data.empty()) {
E2D_ERROR("Failed to read texture file: {}", path);
return nullptr;
}
return loadFromMemory(data.data(), data.size());
}
Ref<TextureAsset> TextureLoader::loadFromMemory(const u8* data, size_t size) {
if (!data || size == 0) {
return nullptr;
}
int width, height, channels;
u8* pixels = stbi_load_from_memory(data, static_cast<int>(size),
&width, &height, &channels,
desiredChannels_);
if (!pixels) {
E2D_ERROR("Failed to load texture from memory: {}", stbi_failure_reason());
return nullptr;
}
int actualChannels = desiredChannels_ > 0 ? desiredChannels_ : channels;
auto asset = ptr::make<TextureAsset>();
Unique<u8[]> pixelData(new u8[static_cast<size_t>(width) * height * actualChannels]);
std::memcpy(pixelData.get(), pixels,
static_cast<size_t>(width) * height * actualChannels);
asset->setData(width, height, actualChannels, std::move(pixelData));
stbi_image_free(pixels);
return asset;
}
bool TextureLoader::canLoad(const std::string& path) const {
std::string ext = getExtension(path);
auto exts = extensions();
return std::find(exts.begin(), exts.end(), ext) != exts.end();
}
std::vector<std::string> TextureLoader::extensions() const {
return {".png", ".jpg", ".jpeg", ".bmp", ".tga", ".gif", ".psd", ".hdr", ".pic"};
}
void TextureLoader::setDesiredChannels(int channels) {
desiredChannels_ = std::clamp(channels, 0, 4);
}
// ---------------------------------------------------------------------------
// FontLoader 实现
// ---------------------------------------------------------------------------
Ref<FontAsset> FontLoader::load(const std::string& path) {
auto data = readFile(path);
if (data.empty()) {
E2D_ERROR("Failed to read font file: {}", path);
return nullptr;
}
return loadFromMemory(data.data(), data.size());
}
Ref<FontAsset> FontLoader::loadFromMemory(const u8* data, size_t size) {
if (!data || size == 0) {
return nullptr;
}
auto asset = ptr::make<FontAsset>();
std::vector<u8> fontData(data, data + size);
if (!asset->setData(std::move(fontData))) {
E2D_ERROR("Failed to initialize font from memory");
return nullptr;
}
return asset;
}
bool FontLoader::canLoad(const std::string& path) const {
std::string ext = getExtension(path);
auto exts = extensions();
return std::find(exts.begin(), exts.end(), ext) != exts.end();
}
std::vector<std::string> FontLoader::extensions() const {
return {".ttf", ".otf", ".ttc"};
}
// ---------------------------------------------------------------------------
// ShaderLoader 实现
// ---------------------------------------------------------------------------
Ref<ShaderAsset> ShaderLoader::load(const std::string& path) {
auto data = readFile(path);
if (data.empty()) {
E2D_ERROR("Failed to read shader file: {}", path);
return nullptr;
}
return loadFromMemory(data.data(), data.size());
}
Ref<ShaderAsset> ShaderLoader::loadFromMemory(const u8* data, size_t size) {
if (!data || size == 0) {
return nullptr;
}
std::string content(reinterpret_cast<const char*>(data), size);
std::string vertexSrc, fragmentSrc;
if (content.find(vertexMarker_) != std::string::npos) {
if (!parseCombined(content, vertexSrc, fragmentSrc)) {
E2D_ERROR("Failed to parse combined shader file");
return nullptr;
}
} else {
vertexSrc = content;
fragmentSrc = content;
}
auto asset = ptr::make<ShaderAsset>();
asset->setSource(std::move(vertexSrc), std::move(fragmentSrc));
return asset;
}
bool ShaderLoader::canLoad(const std::string& path) const {
std::string ext = getExtension(path);
auto exts = extensions();
return std::find(exts.begin(), exts.end(), ext) != exts.end();
}
std::vector<std::string> ShaderLoader::extensions() const {
return {".vert", ".frag", ".glsl", ".vs", ".fs"};
}
bool ShaderLoader::parseCombined(const std::string& content,
std::string& vertex,
std::string& fragment) {
size_t vertexPos = content.find(vertexMarker_);
size_t fragmentPos = content.find(fragmentMarker_);
if (vertexPos == std::string::npos || fragmentPos == std::string::npos) {
return false;
}
size_t vertexStart = vertexPos + vertexMarker_.length();
if (vertexPos < fragmentPos) {
vertex = content.substr(vertexStart, fragmentPos - vertexStart);
fragment = content.substr(fragmentPos + fragmentMarker_.length());
} else {
fragment = content.substr(fragmentPos + fragmentMarker_.length(),
vertexPos - fragmentPos - fragmentMarker_.length());
vertex = content.substr(vertexStart);
}
auto trim = [](std::string& s) {
size_t start = s.find_first_not_of(" \t\r\n");
if (start == std::string::npos) {
s.clear();
return;
}
size_t end = s.find_last_not_of(" \t\r\n");
s = s.substr(start, end - start + 1);
};
trim(vertex);
trim(fragment);
return true;
}
// ---------------------------------------------------------------------------
// AudioLoader 实现
// ---------------------------------------------------------------------------
Ref<AudioAsset> AudioLoader::load(const std::string& path) {
auto data = readFile(path);
if (data.empty()) {
E2D_ERROR("Failed to read audio file: {}", path);
return nullptr;
}
return loadFromMemory(data.data(), data.size());
}
Ref<AudioAsset> AudioLoader::loadFromMemory(const u8* data, size_t size) {
if (!data || size == 0) {
return nullptr;
}
std::string ext = ".wav";
if (size >= 4) {
if (data[0] == 'R' && data[1] == 'I' && data[2] == 'F' && data[3] == 'F') {
return loadWav(data, size);
}
}
E2D_ERROR("Unsupported audio format");
return nullptr;
}
bool AudioLoader::canLoad(const std::string& path) const {
std::string ext = getExtension(path);
auto exts = extensions();
return std::find(exts.begin(), exts.end(), ext) != exts.end();
}
std::vector<std::string> AudioLoader::extensions() const {
return {".wav"};
}
Ref<AudioAsset> AudioLoader::loadWav(const u8* data, size_t size) {
if (size < 44) {
E2D_ERROR("WAV file too small");
return nullptr;
}
if (data[0] != 'R' || data[1] != 'I' || data[2] != 'F' || data[3] != 'F') {
E2D_ERROR("Invalid WAV file: missing RIFF header");
return nullptr;
}
if (data[8] != 'W' || data[9] != 'A' || data[10] != 'V' || data[11] != 'E') {
E2D_ERROR("Invalid WAV file: missing WAVE format");
return nullptr;
}
size_t pos = 12;
u16 audioFormat = 0;
u16 numChannels = 0;
u32 sampleRate = 0;
u16 bitsPerSample = 0;
size_t dataSize = 0;
const u8* audioData = nullptr;
while (pos < size) {
u32 chunkId = *reinterpret_cast<const u32*>(data + pos);
u32 chunkSize = *reinterpret_cast<const u32*>(data + pos + 4);
if (chunkId == 0x20746D66) { // "fmt "
audioFormat = *reinterpret_cast<const u16*>(data + pos + 8);
numChannels = *reinterpret_cast<const u16*>(data + pos + 10);
sampleRate = *reinterpret_cast<const u32*>(data + pos + 12);
bitsPerSample = *reinterpret_cast<const u16*>(data + pos + 22);
} else if (chunkId == 0x61746164) { // "data"
dataSize = chunkSize;
audioData = data + pos + 8;
break;
}
pos += 8 + chunkSize;
if (chunkSize % 2 == 1) {
pos++;
}
}
if (!audioData || dataSize == 0) {
E2D_ERROR("Invalid WAV file: missing data chunk");
return nullptr;
}
if (audioFormat != 1) {
E2D_ERROR("Unsupported WAV format: only PCM supported");
return nullptr;
}
auto asset = ptr::make<AudioAsset>();
std::vector<u8> pcmData(audioData, audioData + dataSize);
asset->setData(AudioFormat::PCM, numChannels, sampleRate, bitsPerSample,
std::move(pcmData));
return asset;
}
// ---------------------------------------------------------------------------
// DataLoader 实现
// ---------------------------------------------------------------------------
Ref<DataAsset> DataLoader::load(const std::string& path) {
auto data = readFile(path);
if (data.empty()) {
E2D_ERROR("Failed to read data file: {}", path);
return nullptr;
}
return loadFromMemory(data.data(), data.size());
}
Ref<DataAsset> DataLoader::loadFromMemory(const u8* data, size_t size) {
if (!data || size == 0) {
return nullptr;
}
auto asset = ptr::make<DataAsset>();
std::vector<u8> assetData(data, data + size);
asset->setData(std::move(assetData));
return asset;
}
bool DataLoader::canLoad(const std::string& path) const {
return !path.empty();
}
std::vector<std::string> DataLoader::extensions() const {
return {".bin", ".dat"};
}
// ---------------------------------------------------------------------------
// AssetLoaderFactory 实现
// ---------------------------------------------------------------------------
AssetType AssetLoaderFactory::getTypeByExtension(const std::string& extension) {
std::string ext = toLower(extension);
if (ext == ".png" || ext == ".jpg" || ext == ".jpeg" ||
ext == ".bmp" || ext == ".tga" || ext == ".gif" ||
ext == ".psd" || ext == ".hdr" || ext == ".pic") {
return AssetType::Texture;
}
if (ext == ".ttf" || ext == ".otf" || ext == ".ttc") {
return AssetType::Font;
}
if (ext == ".vert" || ext == ".frag" || ext == ".glsl" ||
ext == ".vs" || ext == ".fs") {
return AssetType::Shader;
}
if (ext == ".wav" || ext == ".mp3" || ext == ".ogg") {
return AssetType::Audio;
}
if (ext == ".bin" || ext == ".dat") {
return AssetType::Data;
}
return AssetType::Unknown;
}
}

View File

@ -0,0 +1,439 @@
#include "extra2d/asset/asset_pack.h"
#include <algorithm>
#include <cstring>
#include <filesystem>
namespace extra2d {
// ---------------------------------------------------------------------------
// AssetPack 实现
// ---------------------------------------------------------------------------
AssetPack::AssetPack(AssetPack&& other) noexcept
: path_(std::move(other.path_))
, file_(std::move(other.file_))
, header_(other.header_)
, entries_(std::move(other.entries_))
, pipe_(std::move(other.pipe_)) {
other.header_ = AssetPackageHeader{};
}
AssetPack& AssetPack::operator=(AssetPack&& other) noexcept {
if (this != &other) {
close();
path_ = std::move(other.path_);
file_ = std::move(other.file_);
header_ = other.header_;
entries_ = std::move(other.entries_);
pipe_ = std::move(other.pipe_);
other.header_ = AssetPackageHeader{};
}
return *this;
}
AssetPack::~AssetPack() {
close();
}
bool AssetPack::open(const std::string& path) {
close();
path_ = path;
file_.open(path, std::ios::binary);
if (!file_.is_open()) {
return false;
}
if (!readHeader()) {
close();
return false;
}
if (!readIndex()) {
close();
return false;
}
return true;
}
void AssetPack::close() {
if (file_.is_open()) {
file_.close();
}
entries_.clear();
path_.clear();
header_ = AssetPackageHeader{};
}
bool AssetPack::has(const AssetID& id) const {
return entries_.find(id) != entries_.end();
}
bool AssetPack::has(const std::string& path) const {
return has(AssetID(path));
}
std::vector<u8> AssetPack::read(const AssetID& id) {
auto raw = readRaw(id);
if (raw.empty()) {
return {};
}
if (!pipe_.empty()) {
return pipe_.process(raw);
}
return raw;
}
std::vector<u8> AssetPack::read(const std::string& path) {
return read(AssetID(path));
}
std::vector<u8> AssetPack::readRaw(const AssetID& id) {
auto* entry = getEntry(id);
if (!entry) {
return {};
}
return readEntryData(*entry);
}
std::vector<AssetID> AssetPack::assets() const {
std::vector<AssetID> result;
result.reserve(entries_.size());
for (const auto& pair : entries_) {
result.push_back(pair.first);
}
return result;
}
const AssetPackageEntry* AssetPack::getEntry(const AssetID& id) const {
auto it = entries_.find(id);
return it != entries_.end() ? &it->second : nullptr;
}
bool AssetPack::readHeader() {
file_.seekg(0, std::ios::beg);
file_.read(reinterpret_cast<char*>(&header_), sizeof(header_));
if (!file_.good()) {
return false;
}
if (!header_.valid()) {
return false;
}
return true;
}
bool AssetPack::readIndex() {
u32 entryCount = 0;
file_.read(reinterpret_cast<char*>(&entryCount), sizeof(entryCount));
if (!file_.good()) {
return false;
}
entries_.clear();
entries_.reserve(entryCount);
for (u32 i = 0; i < entryCount; ++i) {
u32 pathLen = 0;
file_.read(reinterpret_cast<char*>(&pathLen), sizeof(pathLen));
if (!file_.good()) {
return false;
}
std::string path(pathLen, '\0');
file_.read(path.data(), pathLen);
AssetPackageEntry entry;
entry.id = AssetID(path);
file_.read(reinterpret_cast<char*>(&entry.offset), sizeof(entry.offset));
file_.read(reinterpret_cast<char*>(&entry.size), sizeof(entry.size));
file_.read(reinterpret_cast<char*>(&entry.originalSize), sizeof(entry.originalSize));
file_.read(reinterpret_cast<char*>(&entry.compression), sizeof(entry.compression));
file_.read(reinterpret_cast<char*>(&entry.flags), sizeof(entry.flags));
if (!file_.good()) {
return false;
}
entries_[entry.id] = entry;
}
return true;
}
std::vector<u8> AssetPack::readEntryData(const AssetPackageEntry& entry) {
std::vector<u8> data(entry.size);
file_.seekg(entry.offset, std::ios::beg);
file_.read(reinterpret_cast<char*>(data.data()), entry.size);
if (!file_.good()) {
return {};
}
return data;
}
// ---------------------------------------------------------------------------
// PackManager 实现
// ---------------------------------------------------------------------------
bool PackManager::mount(const std::string& path) {
auto pack = std::make_unique<AssetPack>();
if (!pack->open(path)) {
return false;
}
(void)defaultPipe_;
packs_.push_back(std::move(pack));
return true;
}
void PackManager::unmount(const std::string& path) {
packs_.erase(
std::remove_if(packs_.begin(), packs_.end(),
[&path](const Unique<AssetPack>& pack) {
return pack->path() == path;
}),
packs_.end()
);
}
void PackManager::unmountAll() {
packs_.clear();
}
AssetPack* PackManager::find(const AssetID& id) {
for (auto& pack : packs_) {
if (pack->has(id)) {
return pack.get();
}
}
return nullptr;
}
AssetPack* PackManager::find(const std::string& path) {
return find(AssetID(path));
}
bool PackManager::has(const AssetID& id) const {
for (const auto& pack : packs_) {
if (pack->has(id)) {
return true;
}
}
return false;
}
bool PackManager::has(const std::string& path) const {
return has(AssetID(path));
}
std::vector<u8> PackManager::read(const AssetID& id) {
auto* pack = find(id);
if (pack) {
return pack->read(id);
}
return {};
}
std::vector<u8> PackManager::read(const std::string& path) {
return read(AssetID(path));
}
std::vector<AssetID> PackManager::allAssets() const {
std::vector<AssetID> result;
for (const auto& pack : packs_) {
auto assets = pack->assets();
result.insert(result.end(), assets.begin(), assets.end());
}
return result;
}
std::vector<std::string> PackManager::mountedPacks() const {
std::vector<std::string> result;
result.reserve(packs_.size());
for (const auto& pack : packs_) {
result.push_back(pack->path());
}
return result;
}
// ---------------------------------------------------------------------------
// AssetPackBuilder 实现
// ---------------------------------------------------------------------------
AssetPackBuilder::AssetPackBuilder(Compression compression, int level)
: compression_(compression)
, level_(level) {
}
void AssetPackBuilder::add(const std::string& path, const std::vector<u8>& data) {
BuilderEntry entry;
entry.id = AssetID(path);
entry.data = data;
entry.compression = static_cast<u32>(compression_);
entry.flags = encryptType_ != Decryptor::Type::None ? 1 : 0;
entry.compressedData = processData(entry.data);
if (entry.compressedData.empty()) {
entry.compressedData = entry.data;
entry.compression = static_cast<u32>(Compression::None);
}
entries_.push_back(std::move(entry));
totalOriginalSize_ += data.size();
totalCompressedSize_ += entry.compressedData.size();
}
void AssetPackBuilder::add(const std::string& path, std::vector<u8>&& data) {
add(path, data);
}
bool AssetPackBuilder::addFile(const std::string& filePath, const std::string& packPath) {
std::ifstream file(filePath, std::ios::binary | std::ios::ate);
if (!file.is_open()) {
return false;
}
auto size = file.tellg();
file.seekg(0, std::ios::beg);
std::vector<u8> data(size);
file.read(reinterpret_cast<char*>(data.data()), size);
if (!file.good()) {
return false;
}
std::string ppath = packPath.empty()
? std::filesystem::path(filePath).filename().string()
: packPath;
add(ppath, std::move(data));
return true;
}
size_t AssetPackBuilder::addDirectory(const std::string& dirPath, const std::string& prefix) {
size_t count = 0;
std::filesystem::path dir(dirPath);
if (!std::filesystem::exists(dir) || !std::filesystem::is_directory(dir)) {
return 0;
}
for (const auto& entry : std::filesystem::recursive_directory_iterator(dir)) {
if (entry.is_regular_file()) {
std::string relativePath = std::filesystem::relative(entry.path(), dir).string();
std::string packPath = prefix.empty()
? relativePath
: prefix + "/" + relativePath;
if (addFile(entry.path().string(), packPath)) {
++count;
}
}
}
return count;
}
void AssetPackBuilder::setEncryption(const std::string& key, Decryptor::Type type) {
encryptKey_ = key;
encryptType_ = type;
}
bool AssetPackBuilder::build(const std::string& outputPath) {
std::ofstream out(outputPath, std::ios::binary);
if (!out.is_open()) {
return false;
}
AssetPackageHeader header;
header.magic = AssetPackageHeader::MAGIC;
header.version = 1;
header.compressionType = static_cast<u32>(compression_);
header.encryptionType = static_cast<u32>(encryptType_);
header.originalSize = totalOriginalSize_;
u64 dataOffset = sizeof(header) + sizeof(u32);
for (const auto& entry : entries_) {
dataOffset += sizeof(u32) + entry.id.path.size();
dataOffset += sizeof(u64) * 3 + sizeof(u32) * 2;
}
for (auto& entry : entries_) {
entry.offset = dataOffset;
dataOffset += entry.compressedData.size();
}
header.compressedSize = dataOffset - sizeof(header) - sizeof(u32);
for (const auto& entry : entries_) {
header.compressedSize -= sizeof(u32) + entry.id.path.size();
header.compressedSize -= sizeof(u64) * 3 + sizeof(u32) * 2;
}
out.write(reinterpret_cast<const char*>(&header), sizeof(header));
u32 entryCount = static_cast<u32>(entries_.size());
out.write(reinterpret_cast<const char*>(&entryCount), sizeof(entryCount));
for (const auto& entry : entries_) {
u32 pathLen = static_cast<u32>(entry.id.path.size());
out.write(reinterpret_cast<const char*>(&pathLen), sizeof(pathLen));
out.write(entry.id.path.data(), pathLen);
u64 offset = entry.offset;
u64 size = entry.compressedData.size();
u64 originalSize = entry.data.size();
out.write(reinterpret_cast<const char*>(&offset), sizeof(offset));
out.write(reinterpret_cast<const char*>(&size), sizeof(size));
out.write(reinterpret_cast<const char*>(&originalSize), sizeof(originalSize));
out.write(reinterpret_cast<const char*>(&entry.compression), sizeof(entry.compression));
out.write(reinterpret_cast<const char*>(&entry.flags), sizeof(entry.flags));
}
for (const auto& entry : entries_) {
out.write(reinterpret_cast<const char*>(entry.compressedData.data()),
entry.compressedData.size());
}
return out.good();
}
void AssetPackBuilder::clear() {
entries_.clear();
totalOriginalSize_ = 0;
totalCompressedSize_ = 0;
}
std::vector<u8> AssetPackBuilder::processData(const std::vector<u8>& data) {
DataPipe pipe;
if (compression_ != Compression::None) {
pipe.compress(compression_, level_);
}
if (encryptType_ != Decryptor::Type::None && !encryptKey_.empty()) {
pipe.encrypt(encryptKey_, encryptType_);
}
return pipe.process(data);
}
}

View File

@ -0,0 +1,449 @@
#include "extra2d/asset/data_processor.h"
#include <algorithm>
#include <cstring>
#ifdef E2D_USE_ZSTD
#include <zstd.h>
#endif
#ifdef E2D_USE_LZ4
#include <lz4.h>
#endif
#ifdef E2D_USE_ZLIB
#include <zlib.h>
#endif
namespace extra2d {
// ---------------------------------------------------------------------------
// Decryptor 实现
// ---------------------------------------------------------------------------
Decryptor::Decryptor(const std::string& key, Type type)
: key_(key), type_(type) {
}
std::vector<u8> Decryptor::process(const std::vector<u8>& input) {
if (input.empty() || type_ == Type::None) {
return processNext(input);
}
std::vector<u8> result;
switch (type_) {
case Type::XOR:
result = decryptXOR(input);
break;
case Type::AES256:
result = decryptAES256(input);
break;
default:
result = input;
break;
}
return processNext(result);
}
std::vector<u8> Decryptor::decryptXOR(const std::vector<u8>& input) {
if (key_.empty()) {
return input;
}
std::vector<u8> result(input.size());
const size_t keyLen = key_.size();
for (size_t i = 0; i < input.size(); ++i) {
result[i] = input[i] ^ static_cast<u8>(key_[i % keyLen]);
}
return result;
}
std::vector<u8> Decryptor::decryptAES256(const std::vector<u8>& input) {
return decryptXOR(input);
}
// ---------------------------------------------------------------------------
// Decompressor 实现
// ---------------------------------------------------------------------------
Decompressor::Decompressor(Compression algo)
: algo_(algo) {
}
std::vector<u8> Decompressor::process(const std::vector<u8>& input) {
if (input.empty() || algo_ == Compression::None) {
return processNext(input);
}
std::vector<u8> result;
switch (algo_) {
case Compression::Zstd:
result = decompressZstd(input);
break;
case Compression::LZ4:
result = decompressLZ4(input);
break;
case Compression::Zlib:
result = decompressZlib(input);
break;
default:
result = input;
break;
}
return processNext(result);
}
std::vector<u8> Decompressor::decompressZstd(const std::vector<u8>& input) {
#ifdef E2D_USE_ZSTD
unsigned long long const decompressedSize = ZSTD_getFrameContentSize(input.data(), input.size());
if (decompressedSize == ZSTD_CONTENTSIZE_ERROR ||
decompressedSize == ZSTD_CONTENTSIZE_UNKNOWN) {
return {};
}
std::vector<u8> result(static_cast<size_t>(decompressedSize));
size_t const actualSize = ZSTD_decompress(
result.data(), result.size(),
input.data(), input.size()
);
if (ZSTD_isError(actualSize)) {
return {};
}
result.resize(actualSize);
return result;
#else
return input;
#endif
}
std::vector<u8> Decompressor::decompressLZ4(const std::vector<u8>& input) {
#ifdef E2D_USE_LZ4
if (input.size() < sizeof(u32)) {
return {};
}
u32 originalSize;
std::memcpy(&originalSize, input.data(), sizeof(u32));
std::vector<u8> result(originalSize);
int const decompressedSize = LZ4_decompress_safe(
reinterpret_cast<const char*>(input.data() + sizeof(u32)),
reinterpret_cast<char*>(result.data()),
static_cast<int>(input.size() - sizeof(u32)),
static_cast<int>(originalSize)
);
if (decompressedSize < 0) {
return {};
}
return result;
#else
return input;
#endif
}
std::vector<u8> Decompressor::decompressZlib(const std::vector<u8>& input) {
#ifdef E2D_USE_ZLIB
std::vector<u8> result;
result.reserve(input.size() * 4);
const size_t CHUNK = 16384;
u8 out[CHUNK];
z_stream strm;
strm.zalloc = Z_NULL;
strm.zfree = Z_NULL;
strm.opaque = Z_NULL;
strm.avail_in = static_cast<uInt>(input.size());
strm.next_in = const_cast<Bytef*>(input.data());
if (inflateInit(&strm) != Z_OK) {
return {};
}
int ret;
do {
strm.avail_out = CHUNK;
strm.next_out = out;
ret = inflate(&strm, Z_NO_FLUSH);
if (ret == Z_STREAM_ERROR || ret == Z_DATA_ERROR || ret == Z_MEM_ERROR) {
inflateEnd(&strm);
return {};
}
result.insert(result.end(), out, out + CHUNK - strm.avail_out);
} while (strm.avail_out == 0);
inflateEnd(&strm);
return result;
#else
return input;
#endif
}
// ---------------------------------------------------------------------------
// Encryptor 实现
// ---------------------------------------------------------------------------
Encryptor::Encryptor(const std::string& key, Decryptor::Type type)
: key_(key), type_(type) {
}
std::vector<u8> Encryptor::process(const std::vector<u8>& input) {
if (input.empty() || type_ == Decryptor::Type::None) {
return processNext(input);
}
std::vector<u8> result;
switch (type_) {
case Decryptor::Type::XOR:
result = encryptXOR(input);
break;
case Decryptor::Type::AES256:
result = encryptAES256(input);
break;
default:
result = input;
break;
}
return processNext(result);
}
std::vector<u8> Encryptor::encryptXOR(const std::vector<u8>& input) {
if (key_.empty()) {
return input;
}
std::vector<u8> result(input.size());
const size_t keyLen = key_.size();
for (size_t i = 0; i < input.size(); ++i) {
result[i] = input[i] ^ static_cast<u8>(key_[i % keyLen]);
}
return result;
}
std::vector<u8> Encryptor::encryptAES256(const std::vector<u8>& input) {
return encryptXOR(input);
}
// ---------------------------------------------------------------------------
// Compressor 实现
// ---------------------------------------------------------------------------
Compressor::Compressor(Compression algo, int level)
: algo_(algo), level_(level) {
}
std::vector<u8> Compressor::process(const std::vector<u8>& input) {
if (input.empty() || algo_ == Compression::None) {
return processNext(input);
}
std::vector<u8> result;
switch (algo_) {
case Compression::Zstd:
result = compressZstd(input);
break;
case Compression::LZ4:
result = compressLZ4(input);
break;
case Compression::Zlib:
result = compressZlib(input);
break;
default:
result = input;
break;
}
return processNext(result);
}
std::vector<u8> Compressor::compressZstd(const std::vector<u8>& input) {
#ifdef E2D_USE_ZSTD
size_t const bound = ZSTD_compressBound(input.size());
std::vector<u8> result(bound);
size_t const compressedSize = ZSTD_compress(
result.data(), result.size(),
input.data(), input.size(),
level_
);
if (ZSTD_isError(compressedSize)) {
return {};
}
result.resize(compressedSize);
return result;
#else
return input;
#endif
}
std::vector<u8> Compressor::compressLZ4(const std::vector<u8>& input) {
#ifdef E2D_USE_LZ4
int const bound = LZ4_compressBound(static_cast<int>(input.size()));
std::vector<u8> result(sizeof(u32) + bound);
u32 originalSize = static_cast<u32>(input.size());
std::memcpy(result.data(), &originalSize, sizeof(u32));
int const compressedSize = LZ4_compress_default(
reinterpret_cast<const char*>(input.data()),
reinterpret_cast<char*>(result.data() + sizeof(u32)),
static_cast<int>(input.size()),
bound
);
if (compressedSize <= 0) {
return {};
}
result.resize(sizeof(u32) + compressedSize);
return result;
#else
return input;
#endif
}
std::vector<u8> Compressor::compressZlib(const std::vector<u8>& input) {
#ifdef E2D_USE_ZLIB
std::vector<u8> result;
result.reserve(input.size() / 2);
const size_t CHUNK = 16384;
u8 out[CHUNK];
z_stream strm;
strm.zalloc = Z_NULL;
strm.zfree = Z_NULL;
strm.opaque = Z_NULL;
if (deflateInit(&strm, level_) != Z_OK) {
return {};
}
strm.avail_in = static_cast<uInt>(input.size());
strm.next_in = const_cast<Bytef*>(input.data());
int ret;
do {
strm.avail_out = CHUNK;
strm.next_out = out;
ret = deflate(&strm, Z_FINISH);
if (ret == Z_STREAM_ERROR) {
deflateEnd(&strm);
return {};
}
result.insert(result.end(), out, out + CHUNK - strm.avail_out);
} while (strm.avail_out == 0);
deflateEnd(&strm);
return result;
#else
return input;
#endif
}
// ---------------------------------------------------------------------------
// DataPipe 实现
// ---------------------------------------------------------------------------
DataPipe& DataPipe::decrypt(const std::string& key, Decryptor::Type type) {
processors_.push_back(std::make_unique<Decryptor>(key, type));
return *this;
}
DataPipe& DataPipe::decompress(Compression algo) {
processors_.push_back(std::make_unique<Decompressor>(algo));
return *this;
}
DataPipe& DataPipe::encrypt(const std::string& key, Decryptor::Type type) {
processors_.push_back(std::make_unique<Encryptor>(key, type));
return *this;
}
DataPipe& DataPipe::compress(Compression algo, int level) {
processors_.push_back(std::make_unique<Compressor>(algo, level));
return *this;
}
DataPipe& DataPipe::add(Unique<DataProcessor> processor) {
processors_.push_back(std::move(processor));
return *this;
}
std::vector<u8> DataPipe::process(const std::vector<u8>& input) {
if (processors_.empty()) {
return input;
}
std::vector<u8> result = input;
for (auto& processor : processors_) {
result = processor->process(result);
}
return result;
}
void DataPipe::clear() {
processors_.clear();
}
// ---------------------------------------------------------------------------
// 工具函数实现
// ---------------------------------------------------------------------------
std::vector<u8> computeChecksum(const std::vector<u8>& data) {
std::vector<u8> result(32, 0);
u64 hash1 = 14695981039346656037ULL;
u64 hash2 = 14695981039346656037ULL;
for (size_t i = 0; i < data.size(); ++i) {
hash1 ^= static_cast<u64>(data[i]);
hash1 *= 1099511628211ULL;
hash2 ^= static_cast<u64>(data[i]) << ((i % 8) * 8);
hash2 *= 1099511628211ULL;
}
for (size_t i = 0; i < 8; ++i) {
result[i] = static_cast<u8>((hash1 >> (i * 8)) & 0xFF);
result[i + 8] = static_cast<u8>((hash2 >> (i * 8)) & 0xFF);
}
for (size_t i = 16; i < 32; ++i) {
result[i] = static_cast<u8>((hash1 ^ hash2) >> ((i - 16) * 8) & 0xFF);
}
return result;
}
bool verifyChecksum(const std::vector<u8>& data, const std::vector<u8>& checksum) {
if (checksum.size() != 32) {
return false;
}
auto computed = computeChecksum(data);
return std::equal(computed.begin(), computed.end(), checksum.begin());
}
}

View File

@ -0,0 +1,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