feat: 实现资源服务系统及测试框架

新增资源服务系统核心功能,包括资源加载、缓存管理和异步处理机制。添加测试框架支持单元测试,包含以下主要变更:

- 实现 AssetService 核心功能,支持资源同步/异步加载
- 添加 AssetCache 实现 LRU 缓存策略
- 引入测试框架,包含测试用例注册和断言机制
- 实现资源打包工具 asset_packer
- 添加压缩库支持 (Zstd/LZ4/Zlib)
- 完善文档说明资源服务系统设计

测试用例覆盖资源缓存、数据处理器、资源包管理等核心功能。文档详细说明系统架构和使用方法。
This commit is contained in:
ChestnutYueyue 2026-02-20 22:15:01 +08:00
parent 25c7c47872
commit 6a12bb5e2e
13 changed files with 2823 additions and 6 deletions

528
Docs/asset_service.md Normal file
View File

@ -0,0 +1,528 @@
# Extra2D 资源服务系统
## 概述
Extra2D 资源服务系统提供了一套完整的资源管理解决方案,包括资源加载、缓存、打包和加密压缩功能。
### 主要特性
- **类型安全**:使用模板和强类型 ID 避免类型错误
- **自动缓存**LRU 缓存策略,自动管理内存
- **异步加载**:后台线程加载,不阻塞主线程
- **资源打包**:支持压缩和加密的资源包格式
- **多格式支持**:纹理、字体、着色器、音频等
## 模块结构
```
extra2d/asset/
├── asset_types.h # 资源类型定义
├── asset.h # 资源基类
├── asset_handle.h # 资源句柄
├── asset_cache.h # 资源缓存
├── asset_loader.h # 资源加载器
├── asset_pack.h # 资源包
├── data_processor.h # 数据处理器
└── asset_service.h # 资源服务
```
## 快速开始
### 1. 初始化资源服务
```cpp
#include <extra2d/extra2d.h>
using namespace extra2d;
// 创建资源服务
AssetService service;
service.init();
service.setRoot("assets");
// 注册加载器
service.registerLoader<TextureAsset>(AssetLoaderFactory::createTextureLoader());
service.registerLoader<FontAsset>(AssetLoaderFactory::createFontLoader());
service.registerLoader<ShaderAsset>(AssetLoaderFactory::createShaderLoader());
```
### 2. 同步加载资源
```cpp
// 加载纹理
AssetHandle<TextureAsset> texture = service.load<TextureAsset>("sprites/player.png");
if (texture.valid()) {
int width = texture->width();
int height = texture->height();
const u8* data = texture->data();
}
```
### 3. 异步加载资源
```cpp
// 异步加载
service.loadAsync<TextureAsset>("sprites/background.png",
[](AssetHandle<TextureAsset> handle) {
if (handle.valid()) {
// 资源加载完成,在主线程处理
}
});
// 在主线程处理回调
service.process();
```
### 4. 使用资源包
```cpp
// 挂载资源包
service.mount("data.pak");
// 从资源包加载
auto texture = service.load<TextureAsset>("textures/hero.png");
// 卸载资源包
service.unmount("data.pak");
```
### 5. 缓存管理
```cpp
// 设置缓存上限 (100MB)
service.setLimit(100 * 1024 * 1024);
// 获取缓存统计
CacheStats stats = service.stats();
std::cout << "缓存使用: " << stats.bytes << " 字节\n";
std::cout << "命中率: " << (stats.hitRate() * 100) << "%\n";
// 清理无引用资源
service.purge();
// 清空缓存
service.clear();
```
## API 参考
### AssetID
资源标识符,使用哈希值进行快速比较。
```cpp
struct AssetID {
u64 hash; // 哈希值
std::string path; // 原始路径
explicit AssetID(const std::string& path);
bool valid() const;
bool operator==(const AssetID& other) const;
};
```
### AssetHandle<T>
类型安全的资源句柄,使用弱引用避免阻止资源回收。
```cpp
template<typename T>
class AssetHandle {
public:
bool valid() const; // 检查是否有效
Ref<T> get() const; // 获取强引用
T* operator->() const; // 解引用
bool loaded() const; // 检查是否已加载
AssetState state() const; // 获取状态
};
```
### AssetCache
LRU 缓存管理器。
```cpp
class AssetCache {
public:
explicit AssetCache(size_t limit = 0);
template<typename T>
AssetHandle<T> add(Ref<T> asset);
template<typename T>
AssetHandle<T> get(const AssetID& id);
bool has(const AssetID& id) const;
bool remove(const AssetID& id);
void setLimit(size_t limit);
size_t bytes() const;
size_t count() const;
size_t purge(); // 清理无引用资源
void clear(); // 清空缓存
CacheStats stats() const;
};
```
### IAssetService
资源服务接口。
```cpp
class IAssetService : public IService {
public:
// 同步加载
template<typename T>
AssetHandle<T> load(const std::string& path);
// 异步加载
template<typename T>
void loadAsync(const std::string& path, AssetLoadCallback<T> callback);
// 获取已缓存资源
template<typename T>
AssetHandle<T> get(const std::string& path);
// 预加载
template<typename T>
void preload(const std::string& path);
// 状态查询
bool isLoaded(const std::string& path) const;
bool isLoading(const std::string& path) const;
// 资源管理
void unload(const std::string& path);
void setLimit(size_t maxBytes);
size_t size() const;
void purge();
void clear();
CacheStats stats() const;
// 加载器注册
template<typename T>
void registerLoader(Unique<AssetLoader<T>> loader);
// 资源包管理
bool mount(const std::string& path);
void unmount(const std::string& path);
// 数据处理管道
void setPipe(DataPipe pipe);
// 根目录
void setRoot(const std::string& path);
std::string root() const;
// 处理异步回调
void process();
};
```
### DataPipe
数据处理管道,支持链式调用。
```cpp
class DataPipe {
public:
DataPipe& decrypt(const std::string& key, Decryptor::Type type = Decryptor::Type::XOR);
DataPipe& decompress(Compression algo);
DataPipe& encrypt(const std::string& key, Decryptor::Type type = Decryptor::Type::XOR);
DataPipe& compress(Compression algo, int level = 3);
DataPipe& add(Unique<DataProcessor> processor);
std::vector<u8> process(const std::vector<u8>& input);
void clear();
bool empty() const;
size_t size() const;
};
```
### AssetPackBuilder
资源包构建器。
```cpp
class AssetPackBuilder {
public:
explicit AssetPackBuilder(Compression compression = Compression::None, int level = 3);
void add(const std::string& path, const std::vector<u8>& data);
void add(const std::string& path, std::vector<u8>&& data);
bool addFile(const std::string& filePath, const std::string& packPath = "");
size_t addDirectory(const std::string& dirPath, const std::string& prefix = "");
void setEncryption(const std::string& key, Decryptor::Type type = Decryptor::Type::XOR);
bool build(const std::string& outputPath);
void clear();
size_t count() const;
size_t totalOriginalSize() const;
size_t totalCompressedSize() const;
};
```
## 资源类型
### TextureAsset
纹理资源,使用 stb_image 加载。
```cpp
class TextureAsset : public Asset {
public:
AssetType type() const override { return AssetType::Texture; }
int width() const;
int height() const;
int channels() const;
const u8* data() const;
size_t dataSize() const;
};
```
### FontAsset
字体资源,支持 TrueType 字体。
```cpp
class FontAsset : public Asset {
public:
AssetType type() const override { return AssetType::Font; }
float scaleForPixelHeight(float pixels) const;
const u8* data() const;
size_t dataSize() const;
};
```
### ShaderAsset
着色器资源。
```cpp
class ShaderAsset : public Asset {
public:
AssetType type() const override { return AssetType::Shader; }
const std::string& vertexSource() const;
const std::string& fragmentSource() const;
};
```
### AudioAsset
音频资源。
```cpp
class AudioAsset : public Asset {
public:
AssetType type() const override { return AssetType::Audio; }
AudioFormat format() const;
int channels() const;
int sampleRate() const;
int bitsPerSample() const;
float duration() const;
const u8* data() const;
size_t dataSize() const;
bool streaming() const;
};
```
### DataAsset
通用二进制数据。
```cpp
class DataAsset : public Asset {
public:
AssetType type() const override { return AssetType::Data; }
const u8* data() const;
size_t size() const;
};
```
## 资源打包工具
### 安装
```bash
xmake build asset_packer
```
### 用法
```
Extra2D 资源打包工具 v1.0.0
用法:
asset_packer create <output.pack> [options] <inputs...>
asset_packer list <pack file>
asset_packer extract <pack file> <output dir>
命令:
create 创建资源包
list 列出资源包内容
extract 提取资源包内容
选项:
-c, --compression <algo> 压缩算法 (none, zstd, lz4, zlib),默认 zstd
-l, --level <level> 压缩级别 (1-22),默认 3
-e, --encrypt <key> 加密密钥
-t, --encrypt-type <type> 加密类型 (xor, aes),默认 xor
-v, --verbose 详细输出
-h, --help 显示帮助
```
### 示例
```bash
# 创建资源包(使用 Zstd 压缩)
asset_packer create game.pak -c zstd -v assets/
# 创建加密资源包
asset_packer create game.pak -c zstd -e "secret_key" -t xor -v assets/
# 列出资源包内容
asset_packer list game.pak
# 提取资源包
asset_packer extract game.pak extracted/
# 使用不同压缩算法
asset_packer create game.pak -c lz4 -v assets/
asset_packer create game.pak -c zlib -l 9 -v assets/
```
## 压缩算法对比
| 算法 | 压缩率 | 压缩速度 | 解压速度 | 适用场景 |
|------|--------|----------|----------|----------|
| Zstd | 高 | 快 | 很快 | 通用推荐 |
| LZ4 | 中 | 很快 | 很快 | 实时解压 |
| Zlib | 高 | 中 | 中 | 兼容性好 |
| None | - | - | - | 已压缩资源 |
## 设计模式
### 策略模式 (AssetLoader)
不同资源类型使用不同的加载策略。
```cpp
class AssetLoader<T> {
public:
virtual Ref<T> load(const std::string& path) = 0;
virtual Ref<T> loadFromMemory(const u8* data, size_t size) = 0;
virtual bool canLoad(const std::string& path) const = 0;
virtual AssetType type() const = 0;
virtual std::vector<std::string> extensions() const = 0;
};
```
### 享元模式 (AssetCache)
共享资源实例,减少内存占用。
### 装饰器模式 (DataProcessor)
链式处理数据流。
```cpp
DataPipe pipe;
pipe.encrypt("key")
.compress(Compression::Zstd);
```
### 服务定位器模式
全局访问资源服务。
```cpp
auto* service = ServiceLocator::get<IAssetService>();
```
## 线程安全
- `AssetCache` 使用读写锁 (`std::shared_mutex`)
- `AssetService` 使用读写锁保护资源映射
- 异步加载使用独立的工作线程
- 回调在主线程执行(需要调用 `process()`
## 最佳实践
### 1. 资源路径约定
```
assets/
├── textures/
│ ├── sprites/
│ └── backgrounds/
├── fonts/
├── shaders/
└── audio/
```
### 2. 预加载关键资源
```cpp
// 游戏启动时预加载
service.preload<TextureAsset>("textures/loading.png");
service.preload<FontAsset>("fonts/main.ttf");
```
### 3. 合理设置缓存上限
```cpp
// 根据目标平台设置
#ifdef MOBILE
service.setLimit(50 * 1024 * 1024); // 50MB
#else
service.setLimit(200 * 1024 * 1024); // 200MB
#endif
```
### 4. 定期清理缓存
```cpp
// 场景切换时清理
void onSceneChange() {
service.purge();
}
```
### 5. 使用资源包减少文件数量
```cpp
// 将小文件打包成资源包
// 减少文件 I/O 操作,提高加载速度
service.mount("textures.pak");
service.mount("audio.pak");
```
## 错误处理
```cpp
auto handle = service.load<TextureAsset>("missing.png");
if (!handle.valid()) {
// 资源加载失败
std::cerr << "Failed to load texture\n";
}
// 检查加载状态
if (handle.state() == AssetState::Failed) {
// 处理失败情况
}
```
## 版本历史
- **v1.0.0** - 初始版本
- 资源加载和缓存
- 资源打包和加密
- 异步加载支持
- 多种压缩算法

View File

@ -47,7 +47,7 @@ public:
*/
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));
return AssetHandle<T>(loadImpl(AssetID(path), typeid(T)));
}
/**
@ -73,7 +73,7 @@ public:
*/
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));
return AssetHandle<T>(getImpl(AssetID(path), typeid(T)));
}
/**

View File

@ -77,7 +77,22 @@ void AssetService::setLimit(size_t maxBytes) { cache_->setLimit(maxBytes); }
size_t AssetService::size() const { return cache_->size(); }
void AssetService::purge() { cache_->purge(); }
void AssetService::purge() {
std::unique_lock<std::shared_mutex> lock(mutex_);
std::vector<AssetID> toRemove;
for (const auto& [id, loaded] : assets_) {
if (loaded.asset.use_count() <= 2) {
toRemove.push_back(id);
}
}
for (const auto& id : toRemove) {
assets_.erase(id);
states_.erase(id);
}
cache_->purge();
}
void AssetService::clear() {
std::unique_lock<std::shared_mutex> lock(mutex_);

307
Tests/test_asset_cache.cpp Normal file
View File

@ -0,0 +1,307 @@
/**
* @file test_asset_cache.cpp
* @brief AssetCache
*
*
*/
#include "test_framework.h"
#include <extra2d/asset/asset_cache.h>
#include <extra2d/asset/asset.h>
using namespace extra2d;
using namespace extra2d::test;
// ---------------------------------------------------------------------------
// 测试辅助类
// ---------------------------------------------------------------------------
/**
* @brief
*/
class TestAsset : 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 dataSize_; }
void setData(size_t size) {
dataSize_ = size;
setState(AssetState::Loaded);
}
static Ref<TestAsset> create(const std::string& path, size_t size = 100) {
auto asset = std::make_shared<TestAsset>();
asset->setId(AssetID(path));
asset->setPath(path);
asset->setData(size);
return asset;
}
private:
size_t dataSize_ = 0;
};
// ---------------------------------------------------------------------------
// 基本功能测试
// ---------------------------------------------------------------------------
TEST(AssetCache, Create) {
AssetCache cache;
TEST_ASSERT_EQ(0u, cache.count());
TEST_ASSERT_EQ(0u, cache.bytes());
TEST_ASSERT_EQ(0u, cache.limit());
}
TEST(AssetCache, CreateWithLimit) {
AssetCache cache(1024 * 1024);
TEST_ASSERT_EQ(1024 * 1024, cache.limit());
}
TEST(AssetCache, AddAsset) {
AssetCache cache;
auto asset = TestAsset::create("test.asset", 100);
auto handle = cache.add(asset);
TEST_ASSERT_TRUE(handle.valid());
TEST_ASSERT_EQ(1u, cache.count());
TEST_ASSERT_EQ(100u, cache.bytes());
}
TEST(AssetCache, AddNullAsset) {
AssetCache cache;
Ref<TestAsset> nullAsset;
auto handle = cache.add(nullAsset);
TEST_ASSERT_FALSE(handle.valid());
TEST_ASSERT_EQ(0u, cache.count());
}
TEST(AssetCache, GetAsset) {
AssetCache cache;
auto asset = TestAsset::create("test.asset", 200);
cache.add(asset);
auto handle = cache.get<TestAsset>(AssetID("test.asset"));
TEST_ASSERT_TRUE(handle.valid());
TEST_ASSERT_TRUE(handle.get() != nullptr);
TEST_ASSERT_EQ(asset.get(), handle.get().get());
}
TEST(AssetCache, GetNonExistent) {
AssetCache cache;
auto handle = cache.get<TestAsset>(AssetID("nonexistent.asset"));
TEST_ASSERT_FALSE(handle.valid());
}
TEST(AssetCache, HasAsset) {
AssetCache cache;
auto asset = TestAsset::create("test.asset", 100);
cache.add(asset);
TEST_ASSERT_TRUE(cache.has(AssetID("test.asset")));
TEST_ASSERT_FALSE(cache.has(AssetID("other.asset")));
}
TEST(AssetCache, RemoveAsset) {
AssetCache cache;
auto asset = TestAsset::create("test.asset", 100);
cache.add(asset);
TEST_ASSERT_TRUE(cache.remove(AssetID("test.asset")));
TEST_ASSERT_EQ(0u, cache.count());
TEST_ASSERT_FALSE(cache.has(AssetID("test.asset")));
}
TEST(AssetCache, RemoveNonExistent) {
AssetCache cache;
TEST_ASSERT_FALSE(cache.remove(AssetID("nonexistent.asset")));
}
// ---------------------------------------------------------------------------
// 缓存统计测试
// ---------------------------------------------------------------------------
TEST(AssetCache, Stats_Initial) {
AssetCache cache;
auto stats = cache.stats();
TEST_ASSERT_EQ(0u, stats.hits);
TEST_ASSERT_EQ(0u, stats.misses);
TEST_ASSERT_EQ(0u, stats.count);
}
TEST(AssetCache, Stats_HitMiss) {
AssetCache cache;
auto asset = TestAsset::create("test.asset", 100);
cache.add(asset);
cache.get<TestAsset>(AssetID("test.asset"));
cache.get<TestAsset>(AssetID("nonexistent.asset"));
auto stats = cache.stats();
TEST_ASSERT_EQ(1u, stats.hits);
TEST_ASSERT_EQ(1u, stats.misses);
}
TEST(AssetCache, Stats_Reset) {
AssetCache cache;
auto asset = TestAsset::create("test.asset", 100);
cache.add(asset);
cache.get<TestAsset>(AssetID("test.asset"));
cache.get<TestAsset>(AssetID("nonexistent.asset"));
cache.resetStats();
auto stats = cache.stats();
TEST_ASSERT_EQ(0u, stats.hits);
TEST_ASSERT_EQ(0u, stats.misses);
}
// ---------------------------------------------------------------------------
// 内存限制测试
// ---------------------------------------------------------------------------
TEST(AssetCache, SetLimit) {
AssetCache cache;
cache.setLimit(500);
TEST_ASSERT_EQ(500u, cache.limit());
}
TEST(AssetCache, MemoryTracking) {
AssetCache cache;
auto asset1 = TestAsset::create("asset1", 100);
auto asset2 = TestAsset::create("asset2", 200);
cache.add(asset1);
cache.add(asset2);
TEST_ASSERT_EQ(300u, cache.bytes());
}
TEST(AssetCache, Clear) {
AssetCache cache;
auto asset1 = TestAsset::create("asset1", 100);
auto asset2 = TestAsset::create("asset2", 200);
cache.add(asset1);
cache.add(asset2);
cache.clear();
TEST_ASSERT_EQ(0u, cache.count());
TEST_ASSERT_EQ(0u, cache.bytes());
}
// ---------------------------------------------------------------------------
// LRU 测试
// ---------------------------------------------------------------------------
TEST(AssetCache, LRU_AccessOrder) {
AssetCache cache;
auto asset1 = TestAsset::create("asset1", 100);
auto asset2 = TestAsset::create("asset2", 100);
auto asset3 = TestAsset::create("asset3", 100);
cache.add(asset1);
cache.add(asset2);
cache.add(asset3);
cache.get<TestAsset>(AssetID("asset1"));
TEST_ASSERT_TRUE(cache.has(AssetID("asset1")));
TEST_ASSERT_TRUE(cache.has(AssetID("asset2")));
TEST_ASSERT_TRUE(cache.has(AssetID("asset3")));
}
// ---------------------------------------------------------------------------
// Purge 测试
// ---------------------------------------------------------------------------
TEST(AssetCache, Purge_NoReferences) {
AssetCache cache;
{
auto asset = TestAsset::create("test.asset", 100);
cache.add(asset);
}
size_t purged = cache.purge();
TEST_ASSERT_EQ(1u, purged);
TEST_ASSERT_EQ(0u, cache.count());
}
TEST(AssetCache, Purge_WithReferences) {
AssetCache cache;
auto asset = TestAsset::create("test.asset", 100);
auto handle = cache.add(asset);
size_t purged = cache.purge();
TEST_ASSERT_EQ(0u, purged);
TEST_ASSERT_EQ(1u, cache.count());
}
// ---------------------------------------------------------------------------
// 多资源测试
// ---------------------------------------------------------------------------
TEST(AssetCache, MultipleAssets) {
AssetCache cache;
for (int i = 0; i < 10; ++i) {
auto asset = TestAsset::create("asset" + std::to_string(i), 100);
cache.add(asset);
}
TEST_ASSERT_EQ(10u, cache.count());
TEST_ASSERT_EQ(1000u, cache.bytes());
for (int i = 0; i < 10; ++i) {
TEST_ASSERT_TRUE(cache.has(AssetID("asset" + std::to_string(i))));
}
}
TEST(AssetCache, RemoveMultiple) {
AssetCache cache;
for (int i = 0; i < 5; ++i) {
auto asset = TestAsset::create("asset" + std::to_string(i), 100);
cache.add(asset);
}
for (int i = 0; i < 3; ++i) {
cache.remove(AssetID("asset" + std::to_string(i)));
}
TEST_ASSERT_EQ(2u, cache.count());
TEST_ASSERT_EQ(200u, cache.bytes());
}

427
Tests/test_asset_pack.cpp Normal file
View File

@ -0,0 +1,427 @@
/**
* @file test_asset_pack.cpp
* @brief AssetPack
*
*
*/
#include "test_framework.h"
#include <extra2d/asset/asset_pack.h>
#include <filesystem>
#include <fstream>
using namespace extra2d;
using namespace extra2d::test;
// ---------------------------------------------------------------------------
// 测试辅助函数
// ---------------------------------------------------------------------------
namespace {
std::string testDir = "test_temp";
void setupTestDir() { std::filesystem::create_directories(testDir); }
void cleanupTestDir() { std::filesystem::remove_all(testDir); }
std::string getTestPath(const std::string &name) {
return testDir + "/" + name;
}
void writeTestFile(const std::string &path, const std::vector<u8> &data) {
std::ofstream file(path, std::ios::binary);
file.write(reinterpret_cast<const char *>(data.data()), data.size());
}
} // namespace
// ---------------------------------------------------------------------------
// AssetPackBuilder 测试
// ---------------------------------------------------------------------------
TEST(AssetPackBuilder, Create) {
AssetPackBuilder builder;
TEST_ASSERT_EQ(0u, builder.count());
TEST_ASSERT_EQ(0u, builder.totalOriginalSize());
TEST_ASSERT_EQ(0u, builder.totalCompressedSize());
}
TEST(AssetPackBuilder, AddData) {
AssetPackBuilder builder;
std::vector<u8> data = createTestData(1024);
builder.add("test.bin", data);
TEST_ASSERT_EQ(1u, builder.count());
TEST_ASSERT_EQ(1024u, builder.totalOriginalSize());
}
TEST(AssetPackBuilder, AddMultipleData) {
AssetPackBuilder builder;
builder.add("file1.bin", createTestData(100));
builder.add("file2.bin", createTestData(200));
builder.add("file3.bin", createTestData(300));
TEST_ASSERT_EQ(3u, builder.count());
TEST_ASSERT_EQ(600u, builder.totalOriginalSize());
}
TEST(AssetPackBuilder, AddMovedData) {
AssetPackBuilder builder;
std::vector<u8> data = createTestData(512);
builder.add("moved.bin", std::move(data));
TEST_ASSERT_EQ(1u, builder.count());
}
TEST(AssetPackBuilder, Clear) {
AssetPackBuilder builder;
builder.add("test.bin", createTestData(100));
builder.clear();
TEST_ASSERT_EQ(0u, builder.count());
TEST_ASSERT_EQ(0u, builder.totalOriginalSize());
}
TEST(AssetPackBuilder, BuildSimple) {
setupTestDir();
AssetPackBuilder builder;
builder.add("test.bin", createTestData(256));
std::string packPath = getTestPath("simple.pack");
bool result = builder.build(packPath);
TEST_ASSERT_TRUE(result);
TEST_ASSERT_TRUE(std::filesystem::exists(packPath));
cleanupTestDir();
}
TEST(AssetPackBuilder, BuildMultipleFiles) {
setupTestDir();
AssetPackBuilder builder;
builder.add("file1.bin", createTestData(100, 1));
builder.add("file2.bin", createTestData(200, 2));
builder.add("file3.bin", createTestData(300, 3));
std::string packPath = getTestPath("multi.pack");
bool result = builder.build(packPath);
TEST_ASSERT_TRUE(result);
TEST_ASSERT_TRUE(std::filesystem::exists(packPath));
cleanupTestDir();
}
TEST(AssetPackBuilder, SetEncryption) {
AssetPackBuilder builder;
builder.setEncryption("secret-key", Decryptor::Type::XOR);
builder.add("encrypted.bin", createTestData(128));
TEST_ASSERT_EQ(1u, builder.count());
}
// ---------------------------------------------------------------------------
// AssetPack 测试
// ---------------------------------------------------------------------------
TEST(AssetPack, Create) {
AssetPack pack;
TEST_ASSERT_FALSE(pack.isOpen());
TEST_ASSERT_EQ(0u, pack.count());
}
TEST(AssetPack, OpenNonExistent) {
AssetPack pack;
bool result = pack.open("nonexistent.pack");
TEST_ASSERT_FALSE(result);
TEST_ASSERT_FALSE(pack.isOpen());
}
TEST(AssetPack, OpenAndClose) {
setupTestDir();
AssetPackBuilder builder;
builder.add("test.bin", createTestData(100));
builder.build(getTestPath("test.pack"));
AssetPack pack;
bool opened = pack.open(getTestPath("test.pack"));
TEST_ASSERT_TRUE(opened);
TEST_ASSERT_TRUE(pack.isOpen());
pack.close();
TEST_ASSERT_FALSE(pack.isOpen());
cleanupTestDir();
}
TEST(AssetPack, ReadAsset) {
setupTestDir();
std::vector<u8> originalData = createTestData(512);
AssetPackBuilder builder;
builder.add("data.bin", originalData);
builder.build(getTestPath("read.pack"));
AssetPack pack;
pack.open(getTestPath("read.pack"));
std::vector<u8> readData = pack.read("data.bin");
TEST_ASSERT_TRUE(dataEquals(originalData, readData));
pack.close();
cleanupTestDir();
}
TEST(AssetPack, HasAsset) {
setupTestDir();
AssetPackBuilder builder;
builder.add("exists.bin", createTestData(100));
builder.build(getTestPath("has.pack"));
AssetPack pack;
pack.open(getTestPath("has.pack"));
TEST_ASSERT_TRUE(pack.has("exists.bin"));
TEST_ASSERT_FALSE(pack.has("not_exists.bin"));
pack.close();
cleanupTestDir();
}
TEST(AssetPack, GetAssets) {
setupTestDir();
AssetPackBuilder builder;
builder.add("file1.bin", createTestData(100));
builder.add("file2.bin", createTestData(100));
builder.add("file3.bin", createTestData(100));
builder.build(getTestPath("assets.pack"));
AssetPack pack;
pack.open(getTestPath("assets.pack"));
std::vector<AssetID> assets = pack.assets();
TEST_ASSERT_EQ(3u, assets.size());
pack.close();
cleanupTestDir();
}
TEST(AssetPack, MoveSemantics) {
setupTestDir();
AssetPackBuilder builder;
builder.add("test.bin", createTestData(100));
builder.build(getTestPath("move.pack"));
AssetPack pack1;
pack1.open(getTestPath("move.pack"));
AssetPack pack2 = std::move(pack1);
TEST_ASSERT_TRUE(pack2.isOpen());
TEST_ASSERT_FALSE(pack1.isOpen());
pack2.close();
cleanupTestDir();
}
// ---------------------------------------------------------------------------
// PackManager 测试
// ---------------------------------------------------------------------------
TEST(PackManager, Create) {
PackManager manager;
TEST_ASSERT_EQ(0u, manager.count());
}
TEST(PackManager, MountUnmount) {
setupTestDir();
AssetPackBuilder builder;
builder.add("test.bin", createTestData(100));
builder.build(getTestPath("manager.pack"));
PackManager manager;
bool mounted = manager.mount(getTestPath("manager.pack"));
TEST_ASSERT_TRUE(mounted);
TEST_ASSERT_EQ(1u, manager.count());
manager.unmount(getTestPath("manager.pack"));
TEST_ASSERT_EQ(0u, manager.count());
cleanupTestDir();
}
TEST(PackManager, MountNonExistent) {
PackManager manager;
bool mounted = manager.mount("nonexistent.pack");
TEST_ASSERT_FALSE(mounted);
TEST_ASSERT_EQ(0u, manager.count());
}
TEST(PackManager, HasAsset) {
setupTestDir();
AssetPackBuilder builder;
builder.add("resource.bin", createTestData(100));
builder.build(getTestPath("has.pack"));
PackManager manager;
manager.mount(getTestPath("has.pack"));
TEST_ASSERT_TRUE(manager.has("resource.bin"));
TEST_ASSERT_FALSE(manager.has("missing.bin"));
manager.unmountAll();
cleanupTestDir();
}
TEST(PackManager, ReadAsset) {
setupTestDir();
std::vector<u8> originalData = createTestData(256);
AssetPackBuilder builder;
builder.add("data.bin", originalData);
builder.build(getTestPath("read.pack"));
PackManager manager;
manager.mount(getTestPath("read.pack"));
std::vector<u8> readData = manager.read("data.bin");
TEST_ASSERT_TRUE(dataEquals(originalData, readData));
manager.unmountAll();
cleanupTestDir();
}
TEST(PackManager, MultiplePacks) {
setupTestDir();
AssetPackBuilder builder1;
builder1.add("pack1/file1.bin", createTestData(100));
builder1.build(getTestPath("pack1.pack"));
AssetPackBuilder builder2;
builder2.add("pack2/file2.bin", createTestData(200));
builder2.build(getTestPath("pack2.pack"));
PackManager manager;
manager.mount(getTestPath("pack1.pack"));
manager.mount(getTestPath("pack2.pack"));
TEST_ASSERT_EQ(2u, manager.count());
TEST_ASSERT_TRUE(manager.has("pack1/file1.bin"));
TEST_ASSERT_TRUE(manager.has("pack2/file2.bin"));
manager.unmountAll();
cleanupTestDir();
}
TEST(PackManager, UnmountAll) {
setupTestDir();
AssetPackBuilder builder;
builder.add("test.bin", createTestData(100));
builder.build(getTestPath("unmount.pack"));
PackManager manager;
manager.mount(getTestPath("unmount.pack"));
manager.unmountAll();
TEST_ASSERT_EQ(0u, manager.count());
cleanupTestDir();
}
TEST(PackManager, AllAssets) {
setupTestDir();
AssetPackBuilder builder;
builder.add("file1.bin", createTestData(100));
builder.add("file2.bin", createTestData(100));
builder.build(getTestPath("all.pack"));
PackManager manager;
manager.mount(getTestPath("all.pack"));
std::vector<AssetID> assets = manager.allAssets();
TEST_ASSERT_EQ(2u, assets.size());
manager.unmountAll();
cleanupTestDir();
}
TEST(PackManager, MountedPacks) {
setupTestDir();
AssetPackBuilder builder;
builder.add("test.bin", createTestData(100));
builder.build(getTestPath("mounted.pack"));
PackManager manager;
manager.mount(getTestPath("mounted.pack"));
std::vector<std::string> mounted = manager.mountedPacks();
TEST_ASSERT_EQ(1u, mounted.size());
manager.unmountAll();
cleanupTestDir();
}
// ---------------------------------------------------------------------------
// 加密资源包测试
// ---------------------------------------------------------------------------
TEST(AssetPack, EncryptedPack) {
setupTestDir();
std::vector<u8> originalData = createTestData(256);
std::string key = "encryption-key";
AssetPackBuilder builder(Compression::None);
builder.setEncryption(key, Decryptor::Type::XOR);
builder.add("encrypted.bin", originalData);
builder.build(getTestPath("encrypted.pack"));
AssetPack pack;
pack.open(getTestPath("encrypted.pack"));
DataPipe pipe;
pipe.decrypt(key, Decryptor::Type::XOR);
pack.setPipe(std::move(pipe));
std::vector<u8> readData = pack.read("encrypted.bin");
TEST_ASSERT_TRUE(dataEquals(originalData, readData));
pack.close();
cleanupTestDir();
}

View File

@ -0,0 +1,500 @@
/**
* @file test_asset_service.cpp
* @brief AssetService
*
*
*/
#include "test_framework.h"
#include <extra2d/services/asset_service.h>
#include <extra2d/asset/asset_loader.h>
#include <chrono>
#include <filesystem>
#include <fstream>
#include <thread>
using namespace extra2d;
using namespace extra2d::test;
// ---------------------------------------------------------------------------
// 测试辅助函数
// ---------------------------------------------------------------------------
namespace {
std::string testDir = "test_temp_service";
void setupTestDir() {
std::filesystem::create_directories(testDir);
}
void cleanupTestDir() {
std::filesystem::remove_all(testDir);
}
std::string getTestPath(const std::string& name) {
return testDir + "/" + name;
}
void writeTestFile(const std::string& path, const std::vector<u8>& data) {
std::ofstream file(path, std::ios::binary);
file.write(reinterpret_cast<const char*>(data.data()), data.size());
}
}
// ---------------------------------------------------------------------------
// 测试用资源类和加载器
// ---------------------------------------------------------------------------
/**
* @brief
*/
class TestResource : 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(); }
const std::vector<u8>& data() const { return data_; }
void setData(const std::vector<u8>& data) {
data_ = data;
setState(AssetState::Loaded);
}
void initId(const std::string& path) {
setId(AssetID(path));
setPath(path);
}
private:
std::vector<u8> data_;
};
/**
* @brief
*/
class TestResourceLoader : public AssetLoader<TestResource> {
public:
Ref<TestResource> load(const std::string& path) override {
std::ifstream file(path, std::ios::binary | std::ios::ate);
if (!file.is_open()) {
return nullptr;
}
size_t size = static_cast<size_t>(file.tellg());
file.seekg(0);
std::vector<u8> data(size);
file.read(reinterpret_cast<char*>(data.data()), size);
auto resource = std::make_shared<TestResource>();
resource->initId(path);
resource->setData(data);
return resource;
}
Ref<TestResource> loadFromMemory(const u8* data, size_t size) override {
auto resource = std::make_shared<TestResource>();
resource->setData(std::vector<u8>(data, data + size));
return resource;
}
bool canLoad(const std::string& path) const override {
return path.find(".test") != std::string::npos;
}
AssetType type() const override { return AssetType::Data; }
std::vector<std::string> extensions() const override {
return {".test"};
}
};
// ---------------------------------------------------------------------------
// AssetService 基本测试
// ---------------------------------------------------------------------------
TEST(AssetService, Create) {
AssetService service;
TEST_ASSERT_FALSE(service.isLoaded("nonexistent"));
TEST_ASSERT_FALSE(service.isLoading("nonexistent"));
TEST_ASSERT_EQ(0u, service.size());
}
TEST(AssetService, InitShutdown) {
AssetService service;
bool initResult = service.init();
TEST_ASSERT_TRUE(initResult);
service.shutdown();
}
TEST(AssetService, SetRoot) {
AssetService service;
service.init();
service.setRoot("assets");
TEST_ASSERT_EQ("assets", service.root());
service.shutdown();
}
TEST(AssetService, SetLimit) {
AssetService service;
service.init();
service.setLimit(1024 * 1024);
service.shutdown();
}
// ---------------------------------------------------------------------------
// 加载器注册测试
// ---------------------------------------------------------------------------
TEST(AssetService, RegisterLoader) {
AssetService service;
service.init();
service.registerLoader<TestResource>(std::make_unique<TestResourceLoader>());
service.shutdown();
}
// ---------------------------------------------------------------------------
// 资源加载测试
// ---------------------------------------------------------------------------
TEST(AssetService, LoadNonExistent) {
setupTestDir();
AssetService service;
service.init();
service.setRoot(testDir);
service.registerLoader<TestResource>(std::make_unique<TestResourceLoader>());
auto handle = service.load<TestResource>("nonexistent.test");
TEST_ASSERT_FALSE(handle.valid());
service.shutdown();
cleanupTestDir();
}
TEST(AssetService, LoadFromMemory) {
AssetService service;
service.init();
service.registerLoader<TestResource>(std::make_unique<TestResourceLoader>());
std::vector<u8> testData = createTestData(256);
service.shutdown();
}
TEST(AssetService, IsLoaded) {
setupTestDir();
std::string filePath = getTestPath("resource.test");
writeTestFile(filePath, createTestData(256));
AssetService service;
service.init();
service.setRoot(testDir);
service.registerLoader<TestResource>(std::make_unique<TestResourceLoader>());
TEST_ASSERT_FALSE(service.isLoaded("resource.test"));
auto handle = service.load<TestResource>("resource.test");
TEST_ASSERT_TRUE(service.isLoaded("resource.test"));
service.shutdown();
cleanupTestDir();
}
// ---------------------------------------------------------------------------
// 缓存测试
// ---------------------------------------------------------------------------
TEST(AssetService, CacheHit) {
setupTestDir();
std::string filePath = getTestPath("cached.test");
writeTestFile(filePath, createTestData(256));
AssetService service;
service.init();
service.setRoot(testDir);
service.registerLoader<TestResource>(std::make_unique<TestResourceLoader>());
auto handle1 = service.load<TestResource>("cached.test");
auto handle2 = service.get<TestResource>("cached.test");
TEST_ASSERT_TRUE(handle1.valid());
TEST_ASSERT_TRUE(handle2.valid());
TEST_ASSERT_EQ(handle1.get().get(), handle2.get().get());
auto stats = service.stats();
TEST_ASSERT_GE(stats.hits, 1u);
service.shutdown();
cleanupTestDir();
}
TEST(AssetService, CacheMiss) {
AssetService service;
service.init();
auto handle = service.get<TestResource>("not_in_cache.test");
TEST_ASSERT_FALSE(handle.valid());
auto stats = service.stats();
TEST_ASSERT_GE(stats.misses, 1u);
service.shutdown();
}
TEST(AssetService, Purge) {
setupTestDir();
std::string filePath = getTestPath("purge.test");
writeTestFile(filePath, createTestData(256));
AssetService service;
service.init();
service.setRoot(testDir);
service.registerLoader<TestResource>(std::make_unique<TestResourceLoader>());
{
auto handle = service.load<TestResource>("purge.test");
}
service.purge();
TEST_ASSERT_EQ(0u, service.size());
service.shutdown();
cleanupTestDir();
}
TEST(AssetService, Clear) {
setupTestDir();
std::string filePath = getTestPath("clear.test");
writeTestFile(filePath, createTestData(256));
AssetService service;
service.init();
service.setRoot(testDir);
service.registerLoader<TestResource>(std::make_unique<TestResourceLoader>());
auto handle = service.load<TestResource>("clear.test");
TEST_ASSERT_GT(service.size(), 0u);
service.clear();
TEST_ASSERT_EQ(0u, service.size());
service.shutdown();
cleanupTestDir();
}
// ---------------------------------------------------------------------------
// 卸载测试
// ---------------------------------------------------------------------------
TEST(AssetService, Unload) {
setupTestDir();
std::string filePath = getTestPath("unload.test");
writeTestFile(filePath, createTestData(256));
AssetService service;
service.init();
service.setRoot(testDir);
service.registerLoader<TestResource>(std::make_unique<TestResourceLoader>());
auto handle = service.load<TestResource>("unload.test");
TEST_ASSERT_TRUE(service.isLoaded("unload.test"));
service.unload("unload.test");
TEST_ASSERT_FALSE(service.isLoaded("unload.test"));
service.shutdown();
cleanupTestDir();
}
// ---------------------------------------------------------------------------
// 资源包挂载测试
// ---------------------------------------------------------------------------
TEST(AssetService, MountPack) {
setupTestDir();
AssetPackBuilder builder;
builder.add("packed.test", createTestData(256));
builder.build(getTestPath("service.pack"));
AssetService service;
service.init();
service.registerLoader<TestResource>(std::make_unique<TestResourceLoader>());
bool mounted = service.mount(getTestPath("service.pack"));
TEST_ASSERT_TRUE(mounted);
service.shutdown();
cleanupTestDir();
}
TEST(AssetService, UnmountPack) {
setupTestDir();
AssetPackBuilder builder;
builder.add("packed.test", createTestData(256));
builder.build(getTestPath("unmount.pack"));
AssetService service;
service.init();
service.registerLoader<TestResource>(std::make_unique<TestResourceLoader>());
service.mount(getTestPath("unmount.pack"));
service.unmount(getTestPath("unmount.pack"));
service.shutdown();
cleanupTestDir();
}
// ---------------------------------------------------------------------------
// 统计测试
// ---------------------------------------------------------------------------
TEST(AssetService, Stats) {
AssetService service;
service.init();
auto stats = service.stats();
TEST_ASSERT_EQ(0u, stats.hits);
TEST_ASSERT_EQ(0u, stats.misses);
TEST_ASSERT_EQ(0u, stats.count);
service.shutdown();
}
// ---------------------------------------------------------------------------
// 数据处理管道测试
// ---------------------------------------------------------------------------
TEST(AssetService, SetPipe) {
AssetService service;
service.init();
DataPipe pipe;
pipe.encrypt("key", Decryptor::Type::XOR);
service.setPipe(std::move(pipe));
service.shutdown();
}
// ---------------------------------------------------------------------------
// 异步加载测试
// ---------------------------------------------------------------------------
TEST(AssetService, AsyncLoad) {
setupTestDir();
std::string filePath = getTestPath("async.test");
writeTestFile(filePath, createTestData(512));
AssetService service;
service.init();
service.setRoot(testDir);
service.registerLoader<TestResource>(std::make_unique<TestResourceLoader>());
std::atomic<bool> callbackCalled{false};
AssetHandle<TestResource> resultHandle;
service.loadAsync<TestResource>("async.test",
[&](AssetHandle<TestResource> handle) {
resultHandle = handle;
callbackCalled = true;
});
for (int i = 0; i < 100 && !callbackCalled; ++i) {
service.process();
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
service.process();
TEST_ASSERT_TRUE(callbackCalled);
TEST_ASSERT_TRUE(resultHandle.valid());
service.shutdown();
cleanupTestDir();
}
TEST(AssetService, Preload) {
setupTestDir();
std::string filePath = getTestPath("preload.test");
writeTestFile(filePath, createTestData(256));
AssetService service;
service.init();
service.setRoot(testDir);
service.registerLoader<TestResource>(std::make_unique<TestResourceLoader>());
service.preload<TestResource>("preload.test");
for (int i = 0; i < 50; ++i) {
service.process();
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
service.shutdown();
cleanupTestDir();
}
// ---------------------------------------------------------------------------
// 多资源测试
// ---------------------------------------------------------------------------
TEST(AssetService, LoadMultiple) {
setupTestDir();
for (int i = 0; i < 5; ++i) {
std::string filePath = getTestPath("multi" + std::to_string(i) + ".test");
writeTestFile(filePath, createTestData(100 * (i + 1)));
}
AssetService service;
service.init();
service.setRoot(testDir);
service.registerLoader<TestResource>(std::make_unique<TestResourceLoader>());
for (int i = 0; i < 5; ++i) {
auto handle = service.load<TestResource>("multi" + std::to_string(i) + ".test");
TEST_ASSERT_TRUE(handle.valid());
}
TEST_ASSERT_GT(service.size(), 0u);
service.shutdown();
cleanupTestDir();
}

View File

@ -0,0 +1,260 @@
/**
* @file test_data_processor.cpp
* @brief DataProcessor
*
* //
*/
#include "test_framework.h"
#include <extra2d/asset/data_processor.h>
using namespace extra2d;
using namespace extra2d::test;
// ---------------------------------------------------------------------------
// XOR 加密/解密测试
// ---------------------------------------------------------------------------
TEST(DataProcessor, XOR_EncryptDecrypt) {
std::vector<u8> original = createTestData(1024);
std::string key = "test-key-12345";
Encryptor encryptor(key, Decryptor::Type::XOR);
Decryptor decryptor(key, Decryptor::Type::XOR);
std::vector<u8> encrypted = encryptor.process(original);
std::vector<u8> decrypted = decryptor.process(encrypted);
TEST_ASSERT_TRUE(dataEquals(original, decrypted));
TEST_ASSERT_FALSE(dataEquals(original, encrypted));
}
TEST(DataProcessor, XOR_SameKeyProducesSameResult) {
std::vector<u8> data = createTestData(512);
std::string key = "secret";
Encryptor enc1(key, Decryptor::Type::XOR);
Encryptor enc2(key, Decryptor::Type::XOR);
std::vector<u8> result1 = enc1.process(data);
std::vector<u8> result2 = enc2.process(data);
TEST_ASSERT_TRUE(dataEquals(result1, result2));
}
TEST(DataProcessor, XOR_DifferentKeysProduceDifferentResults) {
std::vector<u8> data = createTestData(512);
Encryptor enc1("key1", Decryptor::Type::XOR);
Encryptor enc2("key2", Decryptor::Type::XOR);
std::vector<u8> result1 = enc1.process(data);
std::vector<u8> result2 = enc2.process(data);
TEST_ASSERT_FALSE(dataEquals(result1, result2));
}
TEST(DataProcessor, XOR_EmptyKey) {
std::vector<u8> data = createTestData(256);
Encryptor encryptor("", Decryptor::Type::XOR);
Decryptor decryptor("", Decryptor::Type::XOR);
std::vector<u8> encrypted = encryptor.process(data);
std::vector<u8> decrypted = decryptor.process(encrypted);
TEST_ASSERT_TRUE(dataEquals(data, decrypted));
}
TEST(DataProcessor, XOR_EmptyData) {
std::vector<u8> empty;
std::string key = "test";
Encryptor encryptor(key, Decryptor::Type::XOR);
Decryptor decryptor(key, Decryptor::Type::XOR);
std::vector<u8> encrypted = encryptor.process(empty);
std::vector<u8> decrypted = decryptor.process(encrypted);
TEST_ASSERT_TRUE(decrypted.empty());
}
// ---------------------------------------------------------------------------
// DataPipe 测试
// ---------------------------------------------------------------------------
TEST(DataProcessor, DataPipe_Empty) {
DataPipe pipe;
TEST_ASSERT_TRUE(pipe.empty());
TEST_ASSERT_EQ(0u, pipe.size());
std::vector<u8> data = createTestData(100);
std::vector<u8> result = pipe.process(data);
TEST_ASSERT_TRUE(dataEquals(data, result));
}
TEST(DataProcessor, DataPipe_SingleProcessor) {
DataPipe pipe;
pipe.encrypt("key", Decryptor::Type::XOR);
TEST_ASSERT_FALSE(pipe.empty());
TEST_ASSERT_EQ(1u, pipe.size());
std::vector<u8> original = createTestData(256);
std::vector<u8> processed = pipe.process(original);
TEST_ASSERT_FALSE(dataEquals(original, processed));
}
TEST(DataProcessor, DataPipe_MultipleProcessors) {
DataPipe pipe;
pipe.encrypt("key", Decryptor::Type::XOR);
pipe.encrypt("key2", Decryptor::Type::XOR);
pipe.encrypt("key", Decryptor::Type::XOR);
pipe.encrypt("key2", Decryptor::Type::XOR);
TEST_ASSERT_EQ(4u, pipe.size());
std::vector<u8> original = createTestData(512);
std::vector<u8> processed = pipe.process(original);
TEST_ASSERT_TRUE(dataEquals(original, processed));
}
TEST(DataProcessor, DataPipe_EncryptDecrypt) {
DataPipe encryptPipe;
encryptPipe.encrypt("secret-key", Decryptor::Type::XOR);
DataPipe decryptPipe;
decryptPipe.decrypt("secret-key", Decryptor::Type::XOR);
std::vector<u8> original = createTestData(1024);
std::vector<u8> encrypted = encryptPipe.process(original);
std::vector<u8> decrypted = decryptPipe.process(encrypted);
TEST_ASSERT_TRUE(dataEquals(original, decrypted));
}
TEST(DataProcessor, DataPipe_Clear) {
DataPipe pipe;
pipe.encrypt("key", Decryptor::Type::XOR);
pipe.encrypt("key2", Decryptor::Type::XOR);
TEST_ASSERT_EQ(2u, pipe.size());
pipe.clear();
TEST_ASSERT_TRUE(pipe.empty());
TEST_ASSERT_EQ(0u, pipe.size());
}
TEST(DataProcessor, DataPipe_MoveSemantics) {
DataPipe pipe1;
pipe1.encrypt("key", Decryptor::Type::XOR);
DataPipe pipe2 = std::move(pipe1);
TEST_ASSERT_EQ(1u, pipe2.size());
std::vector<u8> data = createTestData(128);
std::vector<u8> result = pipe2.process(data);
TEST_ASSERT_FALSE(dataEquals(data, result));
}
// ---------------------------------------------------------------------------
// 校验和测试
// ---------------------------------------------------------------------------
TEST(DataProcessor, Checksum_Consistency) {
std::vector<u8> data = createTestData(1024);
std::vector<u8> checksum1 = computeChecksum(data);
std::vector<u8> checksum2 = computeChecksum(data);
TEST_ASSERT_TRUE(dataEquals(checksum1, checksum2));
}
TEST(DataProcessor, Checksum_DifferentData) {
std::vector<u8> data1 = createTestData(1024, 42);
std::vector<u8> data2 = createTestData(1024, 43);
std::vector<u8> checksum1 = computeChecksum(data1);
std::vector<u8> checksum2 = computeChecksum(data2);
TEST_ASSERT_FALSE(dataEquals(checksum1, checksum2));
}
TEST(DataProcessor, Checksum_Verify) {
std::vector<u8> data = createTestData(2048);
std::vector<u8> checksum = computeChecksum(data);
TEST_ASSERT_TRUE(verifyChecksum(data, checksum));
}
TEST(DataProcessor, Checksum_VerifyTampered) {
std::vector<u8> data = createTestData(2048);
std::vector<u8> checksum = computeChecksum(data);
data[100] ^= 0xFF;
TEST_ASSERT_FALSE(verifyChecksum(data, checksum));
}
TEST(DataProcessor, Checksum_Size) {
std::vector<u8> data = createTestData(100);
std::vector<u8> checksum = computeChecksum(data);
TEST_ASSERT_EQ(32u, checksum.size());
}
// ---------------------------------------------------------------------------
// 处理器链测试
// ---------------------------------------------------------------------------
TEST(DataProcessor, Chain_EncryptDecrypt) {
std::vector<u8> original = createTestData(512);
std::string key = "chain-test-key";
Encryptor encryptor(key, Decryptor::Type::XOR);
Decryptor decryptor(key, Decryptor::Type::XOR);
encryptor.setNext(std::make_unique<Decryptor>(key, Decryptor::Type::XOR));
std::vector<u8> result = encryptor.process(original);
TEST_ASSERT_TRUE(dataEquals(original, result));
}
// ---------------------------------------------------------------------------
// 边界条件测试
// ---------------------------------------------------------------------------
TEST(DataProcessor, Boundary_SingleByte) {
std::vector<u8> data = {0x42};
std::string key = "key";
Encryptor encryptor(key, Decryptor::Type::XOR);
Decryptor decryptor(key, Decryptor::Type::XOR);
std::vector<u8> encrypted = encryptor.process(data);
std::vector<u8> decrypted = decryptor.process(encrypted);
TEST_ASSERT_TRUE(dataEquals(data, decrypted));
TEST_ASSERT_EQ(1u, encrypted.size());
}
TEST(DataProcessor, Boundary_LargeData) {
std::vector<u8> data = createTestData(1024 * 1024);
std::string key = "large-data-key";
Encryptor encryptor(key, Decryptor::Type::XOR);
Decryptor decryptor(key, Decryptor::Type::XOR);
std::vector<u8> encrypted = encryptor.process(data);
std::vector<u8> decrypted = decryptor.process(encrypted);
TEST_ASSERT_TRUE(dataEquals(data, decrypted));
}

65
Tests/test_framework.cpp Normal file
View File

@ -0,0 +1,65 @@
/**
* @file test_framework.cpp
* @brief
*/
#include "test_framework.h"
namespace extra2d {
namespace test {
static TestStats g_stats;
std::vector<TestCase>& getTestCases() {
static std::vector<TestCase> cases;
return cases;
}
TestRegistrar::TestRegistrar(const std::string& suite, const std::string& name, TestFunc func) {
getTestCases().push_back({name, suite, func});
}
int runAllTests() {
auto& cases = getTestCases();
std::cout << "========================================\n";
std::cout << "Running " << cases.size() << " tests...\n";
std::cout << "========================================\n\n";
std::string currentSuite;
for (const auto& tc : cases) {
if (tc.suite != currentSuite) {
currentSuite = tc.suite;
std::cout << "\n[" << currentSuite << "]\n";
}
auto start = std::chrono::high_resolution_clock::now();
try {
tc.func();
auto end = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
std::cout << " [PASS] " << tc.name << " (" << duration.count() << "ms)\n";
g_stats.passed++;
} catch (const std::exception& e) {
auto end = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
std::cout << " [FAIL] " << tc.name << " (" << duration.count() << "ms)\n";
std::cout << " Error: " << e.what() << "\n";
g_stats.failed++;
}
}
std::cout << "\n========================================\n";
std::cout << "Results: " << g_stats.passed << " passed, "
<< g_stats.failed << " failed, "
<< g_stats.skipped << " skipped\n";
std::cout << "========================================\n";
return g_stats.failed > 0 ? 1 : 0;
}
} // namespace test
} // namespace extra2d

140
Tests/test_framework.h Normal file
View File

@ -0,0 +1,140 @@
#pragma once
/**
* @file test_framework.h
* @brief
*
*
*/
#include <extra2d/core/types.h>
#include <chrono>
#include <cstdio>
#include <functional>
#include <iostream>
#include <sstream>
#include <string>
#include <vector>
namespace extra2d {
namespace test {
// ---------------------------------------------------------------------------
// 测试结果统计
// ---------------------------------------------------------------------------
struct TestStats {
int passed = 0;
int failed = 0;
int skipped = 0;
int total() const { return passed + failed + skipped; }
};
// ---------------------------------------------------------------------------
// 测试断言
// ---------------------------------------------------------------------------
#define TEST_ASSERT(condition) \
do { \
if (!(condition)) { \
std::ostringstream _ss; \
_ss << "Assertion failed: " << #condition \
<< " at " << __FILE__ << ":" << __LINE__; \
throw std::runtime_error(_ss.str()); \
} \
} while (0)
#define TEST_ASSERT_EQ(expected, actual) \
do { \
if (!((expected) == (actual))) { \
std::ostringstream _ss; \
_ss << "Assertion failed: expected " << (expected) \
<< " but got " << (actual) \
<< " at " << __FILE__ << ":" << __LINE__; \
throw std::runtime_error(_ss.str()); \
} \
} while (0)
#define TEST_ASSERT_NE(val1, val2) \
do { \
if ((val1) == (val2)) { \
std::ostringstream _ss; \
_ss << "Assertion failed: " << (val1) \
<< " should not equal " << (val2) \
<< " at " << __FILE__ << ":" << __LINE__; \
throw std::runtime_error(_ss.str()); \
} \
} while (0)
#define TEST_ASSERT_TRUE(condition) TEST_ASSERT(condition)
#define TEST_ASSERT_FALSE(condition) TEST_ASSERT(!(condition))
#define TEST_ASSERT_NULL(ptr) TEST_ASSERT((ptr) == nullptr)
#define TEST_ASSERT_NOT_NULL(ptr) TEST_ASSERT((ptr) != nullptr)
#define TEST_ASSERT_GT(val1, val2) TEST_ASSERT((val1) > (val2))
#define TEST_ASSERT_GE(val1, val2) TEST_ASSERT((val1) >= (val2))
#define TEST_ASSERT_LT(val1, val2) TEST_ASSERT((val1) < (val2))
#define TEST_ASSERT_LE(val1, val2) TEST_ASSERT((val1) <= (val2))
// ---------------------------------------------------------------------------
// 测试用例
// ---------------------------------------------------------------------------
using TestFunc = std::function<void()>;
struct TestCase {
std::string name;
std::string suite;
TestFunc func;
};
std::vector<TestCase>& getTestCases();
// ---------------------------------------------------------------------------
// 测试注册器
// ---------------------------------------------------------------------------
class TestRegistrar {
public:
TestRegistrar(const std::string& suite, const std::string& name, TestFunc func);
};
#define TEST(suite, name) \
static void test_##suite##_##name(); \
static extra2d::test::TestRegistrar registrar_##suite##_##name(#suite, #name, test_##suite##_##name); \
static void test_##suite##_##name()
// ---------------------------------------------------------------------------
// 测试运行器
// ---------------------------------------------------------------------------
int runAllTests();
// ---------------------------------------------------------------------------
// 测试工具函数
// ---------------------------------------------------------------------------
/**
* @brief
* @param size
* @param seed
* @return
*/
inline std::vector<u8> createTestData(size_t size, u32 seed = 42) {
std::vector<u8> data(size);
for (size_t i = 0; i < size; ++i) {
data[i] = static_cast<u8>((seed + i) % 256);
}
return data;
}
/**
* @brief
*/
inline bool dataEquals(const std::vector<u8>& a, const std::vector<u8>& b) {
if (a.size() != b.size()) return false;
return std::equal(a.begin(), a.end(), b.begin());
}
} // namespace test
} // namespace extra2d

15
Tests/test_main.cpp Normal file
View File

@ -0,0 +1,15 @@
/**
* @file test_main.cpp
* @brief
*
*
*/
#include "test_framework.h"
int main(int argc, char* argv[]) {
(void)argc;
(void)argv;
return extra2d::test::runAllTests();
}

460
Tools/asset_packer.cpp Normal file
View File

@ -0,0 +1,460 @@
/**
* @file asset_packer.cpp
* @brief
*
*
*
* :
* asset_packer create <output.pack> [options] <input files/dirs...>
* asset_packer list <pack file>
* asset_packer extract <pack file> <output dir>
*
* :
* -c, --compression <algo> (none, zstd, lz4, zlib) zstd
* -l, --level <level> (1-22) 3
* -e, --encrypt <key>
* -t, --encrypt-type <type> (xor, aes) xor
* -v, --verbose
* -h, --help
*/
#include <extra2d/asset/asset_pack.h>
#include <extra2d/asset/data_processor.h>
#include <extra2d/core/types.h>
#include <chrono>
#include <cstring>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <sstream>
#include <string>
#include <vector>
namespace fs = std::filesystem;
// ---------------------------------------------------------------------------
// 命令行参数解析
// ---------------------------------------------------------------------------
struct Options {
std::string command;
std::string outputFile;
std::string packFile;
std::string outputDir;
std::vector<std::string> inputs;
extra2d::Compression compression = extra2d::Compression::Zstd;
int compressionLevel = 3;
std::string encryptKey;
extra2d::Decryptor::Type encryptType = extra2d::Decryptor::Type::XOR;
bool verbose = false;
bool showHelp = false;
};
/**
* @brief
*/
void printHelp(const char* programName) {
std::cout << "Extra2D 资源打包工具 v1.0.0\n\n";
std::cout << "用法:\n";
std::cout << " " << programName << " create <output.pack> [options] <inputs...>\n";
std::cout << " " << programName << " list <pack file>\n";
std::cout << " " << programName << " extract <pack file> <output dir>\n\n";
std::cout << "命令:\n";
std::cout << " create 创建资源包\n";
std::cout << " list 列出资源包内容\n";
std::cout << " extract 提取资源包内容\n\n";
std::cout << "选项:\n";
std::cout << " -c, --compression <algo> 压缩算法 (none, zstd, lz4, zlib),默认 zstd\n";
std::cout << " -l, --level <level> 压缩级别 (1-22),默认 3\n";
std::cout << " -e, --encrypt <key> 加密密钥\n";
std::cout << " -t, --encrypt-type <type> 加密类型 (xor, aes),默认 xor\n";
std::cout << " -v, --verbose 详细输出\n";
std::cout << " -h, --help 显示帮助\n";
}
/**
* @brief
*/
extra2d::Compression parseCompression(const std::string& name) {
if (name == "none" || name == "None" || name == "NONE") {
return extra2d::Compression::None;
} else if (name == "zstd" || name == "Zstd" || name == "ZSTD") {
return extra2d::Compression::Zstd;
} else if (name == "lz4" || name == "LZ4") {
return extra2d::Compression::LZ4;
} else if (name == "zlib" || name == "Zlib" || name == "ZLIB") {
return extra2d::Compression::Zlib;
}
return extra2d::Compression::Zstd;
}
/**
* @brief
*/
extra2d::Decryptor::Type parseEncryptType(const std::string& name) {
if (name == "xor" || name == "XOR") {
return extra2d::Decryptor::Type::XOR;
} else if (name == "aes" || name == "AES" || name == "aes256") {
return extra2d::Decryptor::Type::AES256;
}
return extra2d::Decryptor::Type::XOR;
}
/**
* @brief
*/
bool parseArgs(int argc, char* argv[], Options& opts) {
if (argc < 2) {
opts.showHelp = true;
return false;
}
opts.command = argv[1];
if (opts.command == "-h" || opts.command == "--help") {
opts.showHelp = true;
return true;
}
for (int i = 2; i < argc; ++i) {
std::string arg = argv[i];
if (arg == "-h" || arg == "--help") {
opts.showHelp = true;
return true;
} else if (arg == "-v" || arg == "--verbose") {
opts.verbose = true;
} else if (arg == "-c" || arg == "--compression") {
if (i + 1 < argc) {
opts.compression = parseCompression(argv[++i]);
}
} else if (arg == "-l" || arg == "--level") {
if (i + 1 < argc) {
opts.compressionLevel = std::stoi(argv[++i]);
}
} else if (arg == "-e" || arg == "--encrypt") {
if (i + 1 < argc) {
opts.encryptKey = argv[++i];
}
} else if (arg == "-t" || arg == "--encrypt-type") {
if (i + 1 < argc) {
opts.encryptType = parseEncryptType(argv[++i]);
}
} else if (arg[0] != '-') {
if (opts.command == "create") {
if (opts.outputFile.empty()) {
opts.outputFile = arg;
} else {
opts.inputs.push_back(arg);
}
} else if (opts.command == "list") {
if (opts.packFile.empty()) {
opts.packFile = arg;
}
} else if (opts.command == "extract") {
if (opts.packFile.empty()) {
opts.packFile = arg;
} else if (opts.outputDir.empty()) {
opts.outputDir = arg;
}
}
}
}
return true;
}
// ---------------------------------------------------------------------------
// 创建资源包
// ---------------------------------------------------------------------------
/**
* @brief
*/
std::string formatSize(size_t bytes) {
const char* units[] = {"B", "KB", "MB", "GB"};
int unitIndex = 0;
double size = static_cast<double>(bytes);
while (size >= 1024.0 && unitIndex < 3) {
size /= 1024.0;
++unitIndex;
}
std::ostringstream ss;
ss.precision(2);
ss << size << " " << units[unitIndex];
return ss.str();
}
/**
* @brief
*/
int createPack(const Options& opts) {
if (opts.outputFile.empty()) {
std::cerr << "错误: 未指定输出文件\n";
return 1;
}
if (opts.inputs.empty()) {
std::cerr << "错误: 未指定输入文件或目录\n";
return 1;
}
auto startTime = std::chrono::high_resolution_clock::now();
extra2d::AssetPackBuilder builder(opts.compression, opts.compressionLevel);
if (!opts.encryptKey.empty()) {
builder.setEncryption(opts.encryptKey, opts.encryptType);
}
size_t totalFiles = 0;
size_t totalSize = 0;
for (const auto& input : opts.inputs) {
fs::path inputPath(input);
if (!fs::exists(inputPath)) {
std::cerr << "警告: 跳过不存在的路径: " << input << "\n";
continue;
}
if (fs::is_directory(inputPath)) {
if (opts.verbose) {
std::cout << "扫描目录: " << input << "\n";
}
for (const auto& entry : fs::recursive_directory_iterator(inputPath)) {
if (entry.is_regular_file()) {
fs::path relativePath = fs::relative(entry.path(), inputPath);
std::string packPath = relativePath.generic_string();
if (opts.verbose) {
std::cout << " 添加: " << packPath << "\n";
}
if (builder.addFile(entry.path().string(), packPath)) {
++totalFiles;
totalSize += fs::file_size(entry.path());
}
}
}
} else if (fs::is_regular_file(inputPath)) {
std::string packPath = inputPath.filename().string();
if (opts.verbose) {
std::cout << "添加文件: " << packPath << "\n";
}
if (builder.addFile(inputPath.string(), packPath)) {
++totalFiles;
totalSize += fs::file_size(inputPath);
}
}
}
if (totalFiles == 0) {
std::cerr << "错误: 没有文件被添加到资源包\n";
return 1;
}
if (opts.verbose) {
std::cout << "\n构建资源包: " << opts.outputFile << "\n";
}
if (!builder.build(opts.outputFile)) {
std::cerr << "错误: 构建资源包失败\n";
return 1;
}
auto endTime = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(endTime - startTime);
size_t packSize = fs::file_size(opts.outputFile);
double ratio = totalSize > 0 ? (100.0 * packSize / totalSize) : 0.0;
std::cout << "\n完成!\n";
std::cout << " 文件数量: " << totalFiles << "\n";
std::cout << " 原始大小: " << formatSize(totalSize) << "\n";
std::cout << " 压缩大小: " << formatSize(packSize) << "\n";
std::cout << " 压缩比率: " << std::fixed << std::setprecision(1) << ratio << "%\n";
std::cout << " 耗时: " << duration.count() << "ms\n";
return 0;
}
// ---------------------------------------------------------------------------
// 列出资源包内容
// ---------------------------------------------------------------------------
/**
* @brief
*/
int listPack(const Options& opts) {
if (opts.packFile.empty()) {
std::cerr << "错误: 未指定资源包文件\n";
return 1;
}
if (!fs::exists(opts.packFile)) {
std::cerr << "错误: 资源包文件不存在: " << opts.packFile << "\n";
return 1;
}
extra2d::AssetPack pack;
if (!pack.open(opts.packFile)) {
std::cerr << "错误: 无法打开资源包\n";
return 1;
}
const auto& header = pack.header();
std::cout << "资源包: " << opts.packFile << "\n";
std::cout << "----------------------------------------\n";
std::cout << "版本: " << header.version << "\n";
std::cout << "条目数: " << pack.count() << "\n";
std::cout << "----------------------------------------\n\n";
auto assets = pack.assets();
std::cout << std::left << std::setw(40) << "路径"
<< std::right << std::setw(12) << "大小" << "\n";
std::cout << std::string(52, '-') << "\n";
size_t totalSize = 0;
for (const auto& id : assets) {
auto* entry = pack.getEntry(id);
if (entry) {
std::cout << std::left << std::setw(40) << id.path
<< std::right << std::setw(12) << formatSize(entry->size) << "\n";
totalSize += entry->size;
}
}
std::cout << std::string(52, '-') << "\n";
std::cout << std::left << std::setw(40) << "总计"
<< std::right << std::setw(12) << formatSize(totalSize) << "\n";
pack.close();
return 0;
}
// ---------------------------------------------------------------------------
// 提取资源包
// ---------------------------------------------------------------------------
/**
* @brief
*/
int extractPack(const Options& opts) {
if (opts.packFile.empty()) {
std::cerr << "错误: 未指定资源包文件\n";
return 1;
}
if (opts.outputDir.empty()) {
std::cerr << "错误: 未指定输出目录\n";
return 1;
}
if (!fs::exists(opts.packFile)) {
std::cerr << "错误: 资源包文件不存在: " << opts.packFile << "\n";
return 1;
}
extra2d::AssetPack pack;
if (!pack.open(opts.packFile)) {
std::cerr << "错误: 无法打开资源包\n";
return 1;
}
fs::path outputDir(opts.outputDir);
if (!fs::exists(outputDir)) {
fs::create_directories(outputDir);
}
auto assets = pack.assets();
size_t extracted = 0;
size_t failed = 0;
std::cout << "提取资源包到: " << opts.outputDir << "\n\n";
for (const auto& id : assets) {
fs::path outputPath = outputDir / id.path;
fs::path parentDir = outputPath.parent_path();
if (!parentDir.empty() && !fs::exists(parentDir)) {
fs::create_directories(parentDir);
}
auto data = pack.read(id);
if (data.empty()) {
std::cerr << "警告: 无法读取: " << id.path << "\n";
++failed;
continue;
}
std::ofstream outFile(outputPath, std::ios::binary);
if (!outFile) {
std::cerr << "警告: 无法创建文件: " << outputPath.string() << "\n";
++failed;
continue;
}
outFile.write(reinterpret_cast<const char*>(data.data()), data.size());
if (opts.verbose) {
std::cout << " 提取: " << id.path << " (" << formatSize(data.size()) << ")\n";
}
++extracted;
}
pack.close();
std::cout << "\n完成! 提取 " << extracted << " 个文件";
if (failed > 0) {
std::cout << ", " << failed << " 个失败";
}
std::cout << "\n";
return failed > 0 ? 1 : 0;
}
// ---------------------------------------------------------------------------
// 主函数
// ---------------------------------------------------------------------------
#include <iomanip>
int main(int argc, char* argv[]) {
Options opts;
if (!parseArgs(argc, argv, opts)) {
printHelp(argv[0]);
return 1;
}
if (opts.showHelp) {
printHelp(argv[0]);
return 0;
}
if (opts.command == "create") {
return createPack(opts);
} else if (opts.command == "list") {
return listPack(opts);
} else if (opts.command == "extract") {
return extractPack(opts);
} else {
std::cerr << "错误: 未知命令: " << opts.command << "\n";
printHelp(argv[0]);
return 1;
}
}

View File

@ -87,6 +87,9 @@ if target_plat ~= "switch" then
add_requires("glm")
add_requires("nlohmann_json")
add_requires("glfw")
add_requires("zstd")
add_requires("lz4")
add_requires("zlib")
end
@ -99,3 +102,97 @@ includes("xmake/engine.lua")
-- 定义引擎库
define_extra2d_engine()
-- ==============================================
-- 测试目标
-- ==============================================
target("extra2d_tests")
set_kind("binary")
set_default(false)
-- 测试源文件
add_files("Tests/test_framework.cpp")
add_files("Tests/test_main.cpp")
add_files("Tests/test_data_processor.cpp")
add_files("Tests/test_asset_cache.cpp")
add_files("Tests/test_asset_pack.cpp")
add_files("Tests/test_asset_service.cpp")
-- 头文件路径
add_includedirs("Extra2D/include", {public = true})
add_includedirs("Tests")
-- 依赖引擎库
add_deps("extra2d")
-- 平台配置
local plat = get_config("plat") or os.host()
if plat == "mingw" or plat == "windows" then
add_packages("glm", "nlohmann_json", "glfw", "zstd", "lz4", "zlib")
add_syslinks("winmm", "imm32", "version", "setupapi")
elseif plat == "linux" then
add_packages("glm", "nlohmann_json", "glfw", "zstd", "lz4", "zlib")
add_syslinks("dl", "pthread")
elseif plat == "macosx" then
add_packages("glm", "nlohmann_json", "glfw", "zstd", "lz4", "zlib")
add_frameworks("Cocoa", "IOKit", "CoreVideo")
end
-- 编译器标志
add_cxflags("-Wall", "-Wextra", {force = true})
add_cxflags("-Wno-unused-variable", "-Wno-unused-function", "-Wno-unused-parameter", {force = true})
if is_mode("debug") then
add_defines("_DEBUG")
add_cxxflags("-O0", "-g", {force = true})
else
add_defines("NDEBUG")
add_cxxflags("-O2", {force = true})
end
target_end()
-- ==============================================
-- 资源打包工具
-- ==============================================
target("asset_packer")
set_kind("binary")
set_default(false)
-- 源文件
add_files("Tools/asset_packer.cpp")
-- 头文件路径
add_includedirs("Extra2D/include")
-- 依赖引擎库
add_deps("extra2d")
-- 平台配置
local plat = get_config("plat") or os.host()
if plat == "mingw" or plat == "windows" then
add_packages("glm", "nlohmann_json", "glfw", "zstd", "lz4", "zlib")
add_syslinks("winmm", "imm32", "version", "setupapi")
elseif plat == "linux" then
add_packages("glm", "nlohmann_json", "glfw", "zstd", "lz4", "zlib")
add_syslinks("dl", "pthread")
elseif plat == "macosx" then
add_packages("glm", "nlohmann_json", "glfw", "zstd", "lz4", "zlib")
add_frameworks("Cocoa", "IOKit", "CoreVideo")
end
-- 编译器标志
add_cxflags("-Wall", "-Wextra", {force = true})
add_cxflags("-Wno-unused-variable", "-Wno-unused-function", "-Wno-unused-parameter", {force = true})
if is_mode("debug") then
add_defines("_DEBUG")
add_cxxflags("-O0", "-g", {force = true})
else
add_defines("NDEBUG")
add_cxxflags("-O2", {force = true})
end
target_end()

View File

@ -39,18 +39,21 @@ function define_extra2d_engine()
add_syslinks("glfw", "nx", "m", {public = true})
elseif plat == "mingw" or plat == "windows" then
-- Windows (MinGW) 平台配置
add_packages("glm", "nlohmann_json", "glfw", {public = true})
add_packages("glm", "nlohmann_json", "glfw", "zstd", "lz4", "zlib", {public = true})
add_syslinks("winmm", "imm32", "version", "setupapi", {public = true})
elseif plat == "linux" then
-- Linux 平台配置
add_packages("glm", "nlohmann_json", "glfw", {public = true})
add_packages("glm", "nlohmann_json", "glfw", "zstd", "lz4", "zlib", {public = true})
add_syslinks("dl", "pthread", {public = true})
elseif plat == "macosx" then
-- macOS 平台配置
add_packages("glm", "nlohmann_json", "glfw", {public = true})
add_packages("glm", "nlohmann_json", "glfw", "zstd", "lz4", "zlib", {public = true})
add_frameworks("Cocoa", "IOKit", "CoreVideo", {public = true})
end
-- 启用压缩库
add_defines("E2D_USE_ZSTD", "E2D_USE_LZ4", "E2D_USE_ZLIB", {public = true})
-- 编译器标志 (C 和 C++ 共用)
add_cxflags("-Wall", "-Wextra", {force = true})
add_cxflags("-Wno-unused-variable", "-Wno-unused-function", "-Wno-unused-parameter", {force = true})