feat(资源系统): 添加资源管理、压缩加密和字符编码功能
实现完整的资源管理系统,包括以下功能: 1. 资源包(AssetBundle)支持文件系统、打包文件和加密资源 2. 资源服务(ResourceService)提供同步/异步加载和引用计数管理 3. 压缩工具(Compression)支持Zlib、LZ4和Zstd算法 4. 加密工具(AESCipher)实现AES-256加密/解密 5. 字符编码工具(UnicodeUtils)提供UTF8/16/32和GBK转换 6. 资源句柄(ResourceHandle)实现自动引用计数和生命周期管理 同时添加相关依赖库(zlib/lz4/zstd/openssl)和构建配置
This commit is contained in:
parent
8e385beeae
commit
a705a3d300
|
|
@ -0,0 +1,134 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <extra2d/core/types.h>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief AES 加密工具类
|
||||||
|
*
|
||||||
|
* 使用 OpenSSL 实现 AES-256 加密/解密
|
||||||
|
* 支持所有平台包括 Nintendo Switch
|
||||||
|
*/
|
||||||
|
class AESCipher {
|
||||||
|
public:
|
||||||
|
static constexpr size_t KEY_SIZE = 32; ///< AES-256 密钥大小
|
||||||
|
static constexpr size_t IV_SIZE = 16; ///< 初始化向量大小
|
||||||
|
static constexpr size_t BLOCK_SIZE = 16; ///< AES 块大小
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 默认构造函数
|
||||||
|
*/
|
||||||
|
AESCipher();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 析构函数
|
||||||
|
*/
|
||||||
|
~AESCipher();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置加密密钥和 IV
|
||||||
|
* @param key 密钥(32 字节)
|
||||||
|
* @param iv 初始化向量(16 字节)
|
||||||
|
* @return 设置成功返回 true
|
||||||
|
*/
|
||||||
|
bool setKey(const std::vector<uint8_t>& key, const std::vector<uint8_t>& iv);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置加密密钥和 IV(指针版本)
|
||||||
|
* @param key 密钥指针
|
||||||
|
* @param keySize 密钥大小
|
||||||
|
* @param iv IV 指针
|
||||||
|
* @param ivSize IV 大小
|
||||||
|
* @return 设置成功返回 true
|
||||||
|
*/
|
||||||
|
bool setKey(const uint8_t* key, size_t keySize,
|
||||||
|
const uint8_t* iv, size_t ivSize);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 加密数据
|
||||||
|
* @param data 原始数据
|
||||||
|
* @return 加密后的数据
|
||||||
|
*/
|
||||||
|
std::vector<uint8_t> encrypt(const std::vector<uint8_t>& data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 加密数据(指针版本)
|
||||||
|
* @param data 数据指针
|
||||||
|
* @param size 数据大小
|
||||||
|
* @return 加密后的数据
|
||||||
|
*/
|
||||||
|
std::vector<uint8_t> encrypt(const uint8_t* data, size_t size);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 解密数据
|
||||||
|
* @param data 加密数据
|
||||||
|
* @return 解密后的数据
|
||||||
|
*/
|
||||||
|
std::vector<uint8_t> decrypt(const std::vector<uint8_t>& data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 解密数据(指针版本)
|
||||||
|
* @param data 数据指针
|
||||||
|
* @param size 数据大小
|
||||||
|
* @return 解密后的数据
|
||||||
|
*/
|
||||||
|
std::vector<uint8_t> decrypt(const uint8_t* data, size_t size);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 原地加密
|
||||||
|
* @param data 数据(会被修改)
|
||||||
|
* @return 成功返回 true
|
||||||
|
*/
|
||||||
|
bool encryptInPlace(std::vector<uint8_t>& data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 原地解密
|
||||||
|
* @param data 数据(会被修改)
|
||||||
|
* @return 成功返回 true
|
||||||
|
*/
|
||||||
|
bool decryptInPlace(std::vector<uint8_t>& data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检查密钥是否已设置
|
||||||
|
* @return 已设置返回 true
|
||||||
|
*/
|
||||||
|
bool hasKey() const { return keySet_; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 清除密钥
|
||||||
|
*/
|
||||||
|
void clearKey();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 生成随机密钥
|
||||||
|
* @param key 输出密钥
|
||||||
|
* @param iv 输出 IV
|
||||||
|
*/
|
||||||
|
static void generateRandomKey(std::vector<uint8_t>& key, std::vector<uint8_t>& iv);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 从密码派生密钥
|
||||||
|
* @param password 密码
|
||||||
|
* @param salt 盐值
|
||||||
|
* @param key 输出密钥
|
||||||
|
* @param iv 输出 IV
|
||||||
|
* @param iterations 迭代次数
|
||||||
|
*/
|
||||||
|
static void deriveKeyFromPassword(const std::string& password,
|
||||||
|
const std::vector<uint8_t>& salt,
|
||||||
|
std::vector<uint8_t>& key,
|
||||||
|
std::vector<uint8_t>& iv,
|
||||||
|
int iterations = 10000);
|
||||||
|
|
||||||
|
private:
|
||||||
|
class Impl;
|
||||||
|
UniquePtr<Impl> impl_;
|
||||||
|
std::vector<uint8_t> key_;
|
||||||
|
std::vector<uint8_t> iv_;
|
||||||
|
bool keySet_ = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,144 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <extra2d/core/types.h>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 压缩算法类型
|
||||||
|
*/
|
||||||
|
enum class CompressionType : uint8_t {
|
||||||
|
None = 0, ///< 无压缩
|
||||||
|
Zlib = 1, ///< Zlib 压缩(标准压缩,兼容性好)
|
||||||
|
LZ4 = 2, ///< LZ4 压缩(高速压缩,适合运行时解压)
|
||||||
|
Zstd = 3 ///< Zstd 压缩(高压缩率,适合资源打包)
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 压缩工具类
|
||||||
|
*
|
||||||
|
* 提供多种压缩算法的统一接口
|
||||||
|
*/
|
||||||
|
class Compression {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Zlib 压缩
|
||||||
|
* @param data 原始数据
|
||||||
|
* @param level 压缩级别(0-9,默认 6)
|
||||||
|
* @return 压缩后的数据
|
||||||
|
*/
|
||||||
|
static std::vector<uint8_t> zlibCompress(const std::vector<uint8_t>& data, int level = 6);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Zlib 解压
|
||||||
|
* @param data 压缩数据
|
||||||
|
* @param originalSize 原始数据大小(可选)
|
||||||
|
* @return 解压后的数据
|
||||||
|
*/
|
||||||
|
static std::vector<uint8_t> zlibDecompress(const std::vector<uint8_t>& data,
|
||||||
|
size_t originalSize = 0);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief LZ4 压缩
|
||||||
|
* @param data 原始数据
|
||||||
|
* @return 压缩后的数据
|
||||||
|
*/
|
||||||
|
static std::vector<uint8_t> lz4Compress(const std::vector<uint8_t>& data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief LZ4 解压
|
||||||
|
* @param data 压缩数据
|
||||||
|
* @param originalSize 原始数据大小
|
||||||
|
* @return 解压后的数据
|
||||||
|
*/
|
||||||
|
static std::vector<uint8_t> lz4Decompress(const std::vector<uint8_t>& data,
|
||||||
|
size_t originalSize);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Zstd 压缩
|
||||||
|
* @param data 原始数据
|
||||||
|
* @param level 压缩级别(1-22,默认 3)
|
||||||
|
* @return 压缩后的数据
|
||||||
|
*/
|
||||||
|
static std::vector<uint8_t> zstdCompress(const std::vector<uint8_t>& data, int level = 3);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Zstd 解压
|
||||||
|
* @param data 压缩数据
|
||||||
|
* @param originalSize 原始数据大小(可选)
|
||||||
|
* @return 解压后的数据
|
||||||
|
*/
|
||||||
|
static std::vector<uint8_t> zstdDecompress(const std::vector<uint8_t>& data,
|
||||||
|
size_t originalSize = 0);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 通用压缩接口
|
||||||
|
* @param data 原始数据
|
||||||
|
* @param type 压缩算法类型
|
||||||
|
* @param level 压缩级别(-1 表示使用默认值)
|
||||||
|
* @return 压缩后的数据
|
||||||
|
*/
|
||||||
|
static std::vector<uint8_t> compress(const std::vector<uint8_t>& data,
|
||||||
|
CompressionType type,
|
||||||
|
int level = -1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 通用解压接口
|
||||||
|
* @param data 压缩数据
|
||||||
|
* @param type 压缩算法类型
|
||||||
|
* @param originalSize 原始数据大小
|
||||||
|
* @return 解压后的数据
|
||||||
|
*/
|
||||||
|
static std::vector<uint8_t> decompress(const std::vector<uint8_t>& data,
|
||||||
|
CompressionType type,
|
||||||
|
size_t originalSize = 0);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取压缩后的预估大小
|
||||||
|
* @param originalSize 原始大小
|
||||||
|
* @param type 压缩算法类型
|
||||||
|
* @return 预估压缩后大小
|
||||||
|
*/
|
||||||
|
static size_t estimateCompressedSize(size_t originalSize, CompressionType type);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取压缩算法名称
|
||||||
|
* @param type 压缩算法类型
|
||||||
|
* @return 算法名称字符串
|
||||||
|
*/
|
||||||
|
static const char* getCompressionName(CompressionType type);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 计算 CRC32 校验和
|
||||||
|
* @param data 数据
|
||||||
|
* @return CRC32 值
|
||||||
|
*/
|
||||||
|
static uint32_t crc32(const std::vector<uint8_t>& data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 计算 CRC32 校验和
|
||||||
|
* @param data 数据指针
|
||||||
|
* @param size 数据大小
|
||||||
|
* @return CRC32 值
|
||||||
|
*/
|
||||||
|
static uint32_t crc32(const uint8_t* data, size_t size);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 计算 Adler32 校验和
|
||||||
|
* @param data 数据
|
||||||
|
* @return Adler32 值
|
||||||
|
*/
|
||||||
|
static uint32_t adler32(const std::vector<uint8_t>& data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 计算 Adler32 校验和
|
||||||
|
* @param data 数据指针
|
||||||
|
* @param size 数据大小
|
||||||
|
* @return Adler32 值
|
||||||
|
*/
|
||||||
|
static uint32_t adler32(const uint8_t* data, size_t size);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,213 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <extra2d/core/types.h>
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 文本编码类型
|
||||||
|
*/
|
||||||
|
enum class TextEncoding : uint8_t {
|
||||||
|
UTF8, ///< UTF-8(默认)
|
||||||
|
UTF16, ///< UTF-16
|
||||||
|
UTF32, ///< UTF-32
|
||||||
|
GBK, ///< GBK/GB2312(中文编码)
|
||||||
|
Auto ///< 自动检测
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Unicode 工具类
|
||||||
|
*
|
||||||
|
* 使用 simdutf 进行高性能字符编码转换
|
||||||
|
*/
|
||||||
|
class UnicodeUtils {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief 将字符串转换为 UTF-32
|
||||||
|
* @param text 输入文本
|
||||||
|
* @param encoding 输入编码类型
|
||||||
|
* @return UTF-32 字符串
|
||||||
|
*/
|
||||||
|
static std::u32string toUtf32(const std::string &text, TextEncoding encoding);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 将 UTF-8 字符串转换为 UTF-32
|
||||||
|
* @param utf8 UTF-8 字符串
|
||||||
|
* @return UTF-32 字符串
|
||||||
|
*/
|
||||||
|
static std::u32string utf8ToUtf32(const std::string &utf8);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 将 UTF-8 字符串视图转换为 UTF-32
|
||||||
|
* @param utf8 UTF-8 字符串视图
|
||||||
|
* @return UTF-32 字符串
|
||||||
|
*/
|
||||||
|
static std::u32string utf8ToUtf32(std::string_view utf8);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 将 UTF-16 字符串转换为 UTF-32
|
||||||
|
* @param utf16 UTF-16 字符串
|
||||||
|
* @return UTF-32 字符串
|
||||||
|
*/
|
||||||
|
static std::u32string utf16ToUtf32(const std::u16string &utf16);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 将 UTF-32 字符串转换为 UTF-8
|
||||||
|
* @param utf32 UTF-32 字符串
|
||||||
|
* @return UTF-8 字符串
|
||||||
|
*/
|
||||||
|
static std::string utf32ToUtf8(const std::u32string &utf32);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 将 UTF-32 字符串转换为 UTF-16
|
||||||
|
* @param utf32 UTF-32 字符串
|
||||||
|
* @return UTF-16 字符串
|
||||||
|
*/
|
||||||
|
static std::u16string utf32ToUtf16(const std::u32string &utf32);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 自动检测编码类型
|
||||||
|
* @param data 原始数据
|
||||||
|
* @return 检测到的编码类型
|
||||||
|
*/
|
||||||
|
static TextEncoding detectEncoding(const std::string &data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 计算字符数量
|
||||||
|
* @param text 文本
|
||||||
|
* @param encoding 编码类型
|
||||||
|
* @return 字符数量
|
||||||
|
*/
|
||||||
|
static size_t countChars(const std::string &text, TextEncoding encoding);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 计算字符数量(UTF-8)
|
||||||
|
* @param utf8 UTF-8 文本
|
||||||
|
* @return 字符数量
|
||||||
|
*/
|
||||||
|
static size_t countCharsUtf8(const std::string &utf8);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 计算字符数量(UTF-16)
|
||||||
|
* @param utf16 UTF-16 文本
|
||||||
|
* @return 字符数量
|
||||||
|
*/
|
||||||
|
static size_t countCharsUtf16(const std::u16string &utf16);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 验证编码有效性
|
||||||
|
* @param text 文本
|
||||||
|
* @param encoding 编码类型
|
||||||
|
* @return 有效返回 true
|
||||||
|
*/
|
||||||
|
static bool validateEncoding(const std::string &text, TextEncoding encoding);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 验证 UTF-8 编码
|
||||||
|
* @param utf8 UTF-8 文本
|
||||||
|
* @return 有效返回 true
|
||||||
|
*/
|
||||||
|
static bool validateUtf8(const std::string &utf8);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 验证 UTF-16 编码
|
||||||
|
* @param utf16 UTF-16 文本
|
||||||
|
* @return 有效返回 true
|
||||||
|
*/
|
||||||
|
static bool validateUtf16(const std::u16string &utf16);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 将 GBK 编码转换为 UTF-32
|
||||||
|
* @param gbk GBK 编码文本
|
||||||
|
* @return UTF-32 字符串
|
||||||
|
*/
|
||||||
|
static std::u32string gbkToUtf32(const std::string &gbk);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 将 UTF-32 转换为 GBK 编码
|
||||||
|
* @param utf32 UTF-32 字符串
|
||||||
|
* @return GBK 编码文本
|
||||||
|
*/
|
||||||
|
static std::string utf32ToGbk(const std::u32string &utf32);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取编码名称
|
||||||
|
* @param encoding 编码类型
|
||||||
|
* @return 编码名称字符串
|
||||||
|
*/
|
||||||
|
static const char *getEncodingName(TextEncoding encoding);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检查是否为有效的 Unicode 码点
|
||||||
|
* @param codepoint Unicode 码点
|
||||||
|
* @return 有效返回 true
|
||||||
|
*/
|
||||||
|
static bool isValidCodepoint(uint32_t codepoint);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检查是否为空白字符
|
||||||
|
* @param codepoint Unicode 码点
|
||||||
|
* @return 空白字符返回 true
|
||||||
|
*/
|
||||||
|
static bool isWhitespace(uint32_t codepoint);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检查是否为换行符
|
||||||
|
* @param codepoint Unicode 码点
|
||||||
|
* @return 换行符返回 true
|
||||||
|
*/
|
||||||
|
static bool isNewline(uint32_t codepoint);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检查是否为控制字符
|
||||||
|
* @param codepoint Unicode 码点
|
||||||
|
* @return 控制字符返回 true
|
||||||
|
*/
|
||||||
|
static bool isControl(uint32_t codepoint);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检查是否为中文字符
|
||||||
|
* @param codepoint Unicode 码点
|
||||||
|
* @return 中文字符返回 true
|
||||||
|
*/
|
||||||
|
static bool isChinese(uint32_t codepoint);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检查是否为日文字符
|
||||||
|
* @param codepoint Unicode 码点
|
||||||
|
* @return 日文字符返回 true
|
||||||
|
*/
|
||||||
|
static bool isJapanese(uint32_t codepoint);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检查是否为韩文字符
|
||||||
|
* @param codepoint Unicode 码点
|
||||||
|
* @return 韩文字符返回 true
|
||||||
|
*/
|
||||||
|
static bool isKorean(uint32_t codepoint);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检查是否为 CJK 字符(中日韩)
|
||||||
|
* @param codepoint Unicode 码点
|
||||||
|
* @return CJK 字符返回 true
|
||||||
|
*/
|
||||||
|
static bool isCJK(uint32_t codepoint);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 将码点转换为大写
|
||||||
|
* @param codepoint Unicode 码点
|
||||||
|
* @return 大写码点
|
||||||
|
*/
|
||||||
|
static uint32_t toUpper(uint32_t codepoint);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 将码点转换为小写
|
||||||
|
* @param codepoint Unicode 码点
|
||||||
|
* @return 小写码点
|
||||||
|
*/
|
||||||
|
static uint32_t toLower(uint32_t codepoint);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -9,6 +9,9 @@
|
||||||
#include <extra2d/core/types.h>
|
#include <extra2d/core/types.h>
|
||||||
#include <extra2d/core/module.h>
|
#include <extra2d/core/module.h>
|
||||||
#include <extra2d/core/registry.h>
|
#include <extra2d/core/registry.h>
|
||||||
|
#include <extra2d/core/unicode_utils.h>
|
||||||
|
#include <extra2d/core/compression.h>
|
||||||
|
#include <extra2d/core/aes_cipher.h>
|
||||||
|
|
||||||
// Config removed - app info now in Application class
|
// Config removed - app info now in Application class
|
||||||
|
|
||||||
|
|
@ -54,6 +57,12 @@
|
||||||
#include <extra2d/services/timer_service.h>
|
#include <extra2d/services/timer_service.h>
|
||||||
#include <extra2d/services/camera_service.h>
|
#include <extra2d/services/camera_service.h>
|
||||||
#include <extra2d/services/logger_service.h>
|
#include <extra2d/services/logger_service.h>
|
||||||
|
#include <extra2d/services/resource_service.h>
|
||||||
|
|
||||||
|
// Resources
|
||||||
|
#include <extra2d/resources/resource_handle.h>
|
||||||
|
#include <extra2d/resources/resource_system.h>
|
||||||
|
#include <extra2d/resources/asset_bundle.h>
|
||||||
|
|
||||||
// Application
|
// Application
|
||||||
#include <extra2d/app/application.h>
|
#include <extra2d/app/application.h>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,129 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <extra2d/core/types.h>
|
||||||
|
#include <functional>
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 资源包接口
|
||||||
|
*
|
||||||
|
* 抽象资源存储方式,支持文件系统、打包文件、内存、网络等
|
||||||
|
*/
|
||||||
|
class IAssetBundle {
|
||||||
|
public:
|
||||||
|
virtual ~IAssetBundle() = default;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检查资源是否存在
|
||||||
|
* @param path 资源路径
|
||||||
|
* @return 存在返回 true
|
||||||
|
*/
|
||||||
|
virtual bool contains(const std::string &path) const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 同步读取资源数据
|
||||||
|
* @param path 资源路径
|
||||||
|
* @return 资源数据
|
||||||
|
*/
|
||||||
|
virtual std::vector<uint8_t> read(const std::string &path) const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 异步读取资源数据
|
||||||
|
* @param path 资源路径
|
||||||
|
* @param callback 读取完成回调
|
||||||
|
*/
|
||||||
|
virtual void
|
||||||
|
readAsync(const std::string &path,
|
||||||
|
std::function<void(std::vector<uint8_t>)> callback) const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 打开资源流
|
||||||
|
* @param path 资源路径
|
||||||
|
* @return 输入流指针
|
||||||
|
*/
|
||||||
|
virtual std::unique_ptr<std::istream> openStream(const std::string &path) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 资源信息结构体
|
||||||
|
*/
|
||||||
|
struct AssetInfo {
|
||||||
|
uint64_t size = 0;
|
||||||
|
uint64_t compressedSize = 0;
|
||||||
|
uint64_t modifiedTime = 0;
|
||||||
|
bool compressed = false;
|
||||||
|
bool encrypted = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取资源信息
|
||||||
|
* @param path 资源路径
|
||||||
|
* @return 资源信息
|
||||||
|
*/
|
||||||
|
virtual AssetInfo getInfo(const std::string &path) const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 列出所有资源路径
|
||||||
|
* @return 资源路径列表
|
||||||
|
*/
|
||||||
|
virtual std::vector<std::string> list() const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取资源包名称
|
||||||
|
* @return 名称
|
||||||
|
*/
|
||||||
|
virtual const std::string &getName() const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检查资源包是否有效
|
||||||
|
* @return 有效返回 true
|
||||||
|
*/
|
||||||
|
virtual bool isValid() const = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 资源包条目信息
|
||||||
|
*/
|
||||||
|
struct BundleEntry {
|
||||||
|
std::string path;
|
||||||
|
uint64_t offset = 0;
|
||||||
|
uint64_t compressedSize = 0;
|
||||||
|
uint64_t originalSize = 0;
|
||||||
|
uint32_t checksum = 0;
|
||||||
|
uint8_t compression = 0;
|
||||||
|
uint8_t encryption = 0;
|
||||||
|
uint8_t reserved[2] = {0, 0};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 资源包文件头
|
||||||
|
*/
|
||||||
|
struct BundleHeader {
|
||||||
|
char magic[4] = {'E', '2', 'D', 'P'};
|
||||||
|
uint32_t version = 1;
|
||||||
|
uint8_t compression = 0;
|
||||||
|
uint8_t encryption = 0;
|
||||||
|
uint8_t reserved[2] = {0, 0};
|
||||||
|
uint32_t entryCount = 0;
|
||||||
|
uint64_t entryTableOffset = 0;
|
||||||
|
uint64_t entryTableSize = 0;
|
||||||
|
uint32_t headerChecksum = 0;
|
||||||
|
uint8_t padding[32] = {0};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 资源包构建选项
|
||||||
|
*/
|
||||||
|
struct BundleBuildOptions {
|
||||||
|
uint8_t compression = 0;
|
||||||
|
uint8_t encryption = 0;
|
||||||
|
int compressionLevel = 6;
|
||||||
|
std::vector<uint8_t> encryptionKey;
|
||||||
|
std::vector<uint8_t> encryptionIv;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,237 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <extra2d/core/types.h>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 资源管理器接口
|
||||||
|
*
|
||||||
|
* 资源句柄需要通过管理器进行引用计数管理
|
||||||
|
*/
|
||||||
|
template <typename T> class ResourceManager {
|
||||||
|
public:
|
||||||
|
virtual ~ResourceManager() = default;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 增加引用计数
|
||||||
|
* @param resource 资源指针
|
||||||
|
*/
|
||||||
|
virtual void addRef(T *resource) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 减少引用计数
|
||||||
|
* @param resource 资源指针
|
||||||
|
*/
|
||||||
|
virtual void release(T *resource) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取引用计数
|
||||||
|
* @param resource 资源指针
|
||||||
|
* @return 引用计数
|
||||||
|
*/
|
||||||
|
virtual uint32_t getRefCount(T *resource) const = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 资源句柄模板类
|
||||||
|
*
|
||||||
|
* 使用引用计数管理资源生命周期
|
||||||
|
* 支持拷贝、移动、自动释放
|
||||||
|
*
|
||||||
|
* @tparam T 资源类型
|
||||||
|
*/
|
||||||
|
template <typename T> class ResourceHandle {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief 默认构造函数
|
||||||
|
*/
|
||||||
|
ResourceHandle() : resource_(nullptr), manager_(nullptr) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 空指针构造函数
|
||||||
|
*/
|
||||||
|
ResourceHandle(std::nullptr_t) : resource_(nullptr), manager_(nullptr) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 构造函数
|
||||||
|
* @param resource 资源指针
|
||||||
|
* @param manager 资源管理器
|
||||||
|
*/
|
||||||
|
ResourceHandle(T *resource, ResourceManager<T> *manager)
|
||||||
|
: resource_(resource), manager_(manager) {
|
||||||
|
if (resource_ && manager_) {
|
||||||
|
manager_->addRef(resource_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 析构函数
|
||||||
|
*/
|
||||||
|
~ResourceHandle() { reset(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 拷贝构造函数
|
||||||
|
*/
|
||||||
|
ResourceHandle(const ResourceHandle &other)
|
||||||
|
: resource_(other.resource_), manager_(other.manager_) {
|
||||||
|
if (resource_ && manager_) {
|
||||||
|
manager_->addRef(resource_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 拷贝赋值运算符
|
||||||
|
*/
|
||||||
|
ResourceHandle &operator=(const ResourceHandle &other) {
|
||||||
|
if (this != &other) {
|
||||||
|
reset();
|
||||||
|
resource_ = other.resource_;
|
||||||
|
manager_ = other.manager_;
|
||||||
|
if (resource_ && manager_) {
|
||||||
|
manager_->addRef(resource_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 移动构造函数
|
||||||
|
*/
|
||||||
|
ResourceHandle(ResourceHandle &&other) noexcept
|
||||||
|
: resource_(other.resource_), manager_(other.manager_) {
|
||||||
|
other.resource_ = nullptr;
|
||||||
|
other.manager_ = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 移动赋值运算符
|
||||||
|
*/
|
||||||
|
ResourceHandle &operator=(ResourceHandle &&other) noexcept {
|
||||||
|
if (this != &other) {
|
||||||
|
reset();
|
||||||
|
resource_ = other.resource_;
|
||||||
|
manager_ = other.manager_;
|
||||||
|
other.resource_ = nullptr;
|
||||||
|
other.manager_ = nullptr;
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 箭头运算符
|
||||||
|
* @return 资源指针
|
||||||
|
*/
|
||||||
|
T *operator->() const { return resource_; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 解引用运算符
|
||||||
|
* @return 资源引用
|
||||||
|
*/
|
||||||
|
T &operator*() const { return *resource_; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取原始指针
|
||||||
|
* @return 资源指针
|
||||||
|
*/
|
||||||
|
T *get() const { return resource_; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 布尔转换运算符
|
||||||
|
*/
|
||||||
|
explicit operator bool() const { return resource_ != nullptr; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检查有效性
|
||||||
|
* @return 有效返回 true
|
||||||
|
*/
|
||||||
|
bool isValid() const { return resource_ != nullptr; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 空指针比较
|
||||||
|
*/
|
||||||
|
bool operator==(std::nullptr_t) const { return resource_ == nullptr; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 空指针比较
|
||||||
|
*/
|
||||||
|
bool operator!=(std::nullptr_t) const { return resource_ != nullptr; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 重置句柄
|
||||||
|
*/
|
||||||
|
void reset() {
|
||||||
|
if (resource_ && manager_) {
|
||||||
|
manager_->release(resource_);
|
||||||
|
}
|
||||||
|
resource_ = nullptr;
|
||||||
|
manager_ = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 交换句柄
|
||||||
|
* @param other 另一个句柄
|
||||||
|
*/
|
||||||
|
void swap(ResourceHandle &other) noexcept {
|
||||||
|
std::swap(resource_, other.resource_);
|
||||||
|
std::swap(manager_, other.manager_);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取引用计数
|
||||||
|
* @return 引用计数,无效句柄返回 0
|
||||||
|
*/
|
||||||
|
uint32_t getRefCount() const {
|
||||||
|
if (resource_ && manager_) {
|
||||||
|
return manager_->getRefCount(resource_);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
T *resource_;
|
||||||
|
ResourceManager<T> *manager_;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 纹理数据前向声明
|
||||||
|
*/
|
||||||
|
struct TextureData;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 字体数据前向声明
|
||||||
|
*/
|
||||||
|
struct FontData;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 着色器数据前向声明
|
||||||
|
*/
|
||||||
|
struct ShaderData;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 材质数据前向声明
|
||||||
|
*/
|
||||||
|
struct MaterialData;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 网格数据前向声明
|
||||||
|
*/
|
||||||
|
struct MeshData;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 音频数据前向声明
|
||||||
|
*/
|
||||||
|
struct AudioData;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 资源句柄类型别名
|
||||||
|
*/
|
||||||
|
using TextureHandle = ResourceHandle<TextureData>;
|
||||||
|
using FontHandle = ResourceHandle<FontData>;
|
||||||
|
using ShaderHandle = ResourceHandle<ShaderData>;
|
||||||
|
using MaterialHandle = ResourceHandle<MaterialData>;
|
||||||
|
using MeshHandle = ResourceHandle<MeshData>;
|
||||||
|
using AudioHandle = ResourceHandle<AudioData>;
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,358 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <extra2d/core/service_interface.h>
|
||||||
|
#include <extra2d/core/service_locator.h>
|
||||||
|
#include <extra2d/resources/asset_bundle.h>
|
||||||
|
#include <extra2d/resources/resource_handle.h>
|
||||||
|
#include <extra2d/services/resource_service.h>
|
||||||
|
#include <functional>
|
||||||
|
#include <mutex>
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 资源系统快捷访问
|
||||||
|
*
|
||||||
|
* 提供全局静态方法,简化资源访问
|
||||||
|
* 内部通过 ServiceLocator 获取 IResourceService
|
||||||
|
*/
|
||||||
|
class ResourceSystem {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief 获取资源服务实例
|
||||||
|
* @return 资源服务指针
|
||||||
|
*/
|
||||||
|
static IResourceService *getService() {
|
||||||
|
auto service = ServiceLocator::instance().tryGetService<IResourceService>();
|
||||||
|
return service ? service.get() : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 同步加载资源
|
||||||
|
* @tparam T 资源类型
|
||||||
|
* @param path 资源路径
|
||||||
|
* @return 资源句柄
|
||||||
|
*/
|
||||||
|
template <typename T> static ResourceHandle<T> load(const std::string &path) {
|
||||||
|
auto *service = getService();
|
||||||
|
if (service) {
|
||||||
|
return service->load<T>(path);
|
||||||
|
}
|
||||||
|
return ResourceHandle<T>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 异步加载资源
|
||||||
|
* @tparam T 资源类型
|
||||||
|
* @param path 资源路径
|
||||||
|
* @param callback 加载完成回调
|
||||||
|
*/
|
||||||
|
template <typename T>
|
||||||
|
static void loadAsync(const std::string &path,
|
||||||
|
std::function<void(ResourceHandle<T>)> callback) {
|
||||||
|
auto *service = getService();
|
||||||
|
if (service) {
|
||||||
|
service->loadAsync<T>(path, callback);
|
||||||
|
} else if (callback) {
|
||||||
|
callback(ResourceHandle<T>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 预加载资源
|
||||||
|
* @param path 资源路径
|
||||||
|
*/
|
||||||
|
static void preload(const std::string &path) {
|
||||||
|
auto *service = getService();
|
||||||
|
if (service) {
|
||||||
|
service->preload(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 执行垃圾回收
|
||||||
|
*/
|
||||||
|
static void gc() {
|
||||||
|
auto *service = getService();
|
||||||
|
if (service) {
|
||||||
|
service->gc();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 清除未使用的资源
|
||||||
|
*/
|
||||||
|
static void clearUnused() {
|
||||||
|
auto *service = getService();
|
||||||
|
if (service) {
|
||||||
|
service->clearUnused();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 清除所有资源
|
||||||
|
*/
|
||||||
|
static void clearAll() {
|
||||||
|
auto *service = getService();
|
||||||
|
if (service) {
|
||||||
|
service->clearAll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取资源统计信息
|
||||||
|
* @return 统计信息
|
||||||
|
*/
|
||||||
|
static IResourceService::Stats getStats() {
|
||||||
|
auto *service = getService();
|
||||||
|
if (service) {
|
||||||
|
return service->getStats();
|
||||||
|
}
|
||||||
|
return IResourceService::Stats();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检查资源是否存在
|
||||||
|
* @param path 资源路径
|
||||||
|
* @return 存在返回 true
|
||||||
|
*/
|
||||||
|
static bool exists(const std::string &path) {
|
||||||
|
auto *service = getService();
|
||||||
|
if (service) {
|
||||||
|
return service->exists(path);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 读取原始数据
|
||||||
|
* @param path 资源路径
|
||||||
|
* @return 原始数据
|
||||||
|
*/
|
||||||
|
static std::vector<uint8_t> readData(const std::string &path) {
|
||||||
|
auto *service = getService();
|
||||||
|
if (service) {
|
||||||
|
return service->readData(path);
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 加载资源包
|
||||||
|
* @param path 资源包路径
|
||||||
|
* @return 加载成功返回 true
|
||||||
|
*/
|
||||||
|
static bool loadBundle(const std::string &path) {
|
||||||
|
auto *service = getService();
|
||||||
|
if (service) {
|
||||||
|
return service->loadBundle(path);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 异步加载资源包
|
||||||
|
* @param path 资源包路径
|
||||||
|
* @param callback 加载完成回调
|
||||||
|
*/
|
||||||
|
static void loadBundleAsync(const std::string &path,
|
||||||
|
std::function<void(bool)> callback) {
|
||||||
|
auto *service = getService();
|
||||||
|
if (service) {
|
||||||
|
service->loadBundleAsync(path, callback);
|
||||||
|
} else if (callback) {
|
||||||
|
callback(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 卸载资源包
|
||||||
|
* @param path 资源包路径
|
||||||
|
*/
|
||||||
|
static void unloadBundle(const std::string &path) {
|
||||||
|
auto *service = getService();
|
||||||
|
if (service) {
|
||||||
|
service->unloadBundle(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 基础资源管理器模板
|
||||||
|
*
|
||||||
|
* 提供引用计数和缓存管理
|
||||||
|
*/
|
||||||
|
template <typename T> class BaseResourceManager : public ResourceManager<T> {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief 资源缓存条目
|
||||||
|
*/
|
||||||
|
struct CacheEntry {
|
||||||
|
Ptr<T> resource;
|
||||||
|
std::string path;
|
||||||
|
uint32_t refCount = 0;
|
||||||
|
size_t memorySize = 0;
|
||||||
|
uint64_t lastAccessTime = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
BaseResourceManager() = default;
|
||||||
|
~BaseResourceManager() override = default;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 增加引用计数
|
||||||
|
*/
|
||||||
|
void addRef(T *resource) override {
|
||||||
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
|
auto it = cache_.find(resource);
|
||||||
|
if (it != cache_.end()) {
|
||||||
|
it->second.refCount++;
|
||||||
|
it->second.lastAccessTime = getCurrentTime();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 减少引用计数
|
||||||
|
*/
|
||||||
|
void release(T *resource) override {
|
||||||
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
|
auto it = cache_.find(resource);
|
||||||
|
if (it != cache_.end()) {
|
||||||
|
it->second.refCount--;
|
||||||
|
if (it->second.refCount == 0) {
|
||||||
|
totalMemory_ -= it->second.memorySize;
|
||||||
|
cache_.erase(it);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取引用计数
|
||||||
|
*/
|
||||||
|
uint32_t getRefCount(T *resource) const override {
|
||||||
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
|
auto it = cache_.find(resource);
|
||||||
|
if (it != cache_.end()) {
|
||||||
|
return it->second.refCount;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 缓存资源
|
||||||
|
* @param path 资源路径
|
||||||
|
* @param resource 资源指针
|
||||||
|
* @param memorySize 内存大小
|
||||||
|
*/
|
||||||
|
void cache(const std::string &path, Ptr<T> resource, size_t memorySize = 0) {
|
||||||
|
if (!resource)
|
||||||
|
return;
|
||||||
|
|
||||||
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
|
CacheEntry entry;
|
||||||
|
entry.resource = resource;
|
||||||
|
entry.path = path;
|
||||||
|
entry.refCount = 1;
|
||||||
|
entry.memorySize = memorySize;
|
||||||
|
entry.lastAccessTime = getCurrentTime();
|
||||||
|
|
||||||
|
T *rawPtr = resource.get();
|
||||||
|
cache_[rawPtr] = entry;
|
||||||
|
pathIndex_[path] = rawPtr;
|
||||||
|
totalMemory_ += memorySize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 从缓存获取资源
|
||||||
|
* @param path 资源路径
|
||||||
|
* @return 资源指针,不存在返回 nullptr
|
||||||
|
*/
|
||||||
|
Ptr<T> getFromCache(const std::string &path) {
|
||||||
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
|
auto it = pathIndex_.find(path);
|
||||||
|
if (it != pathIndex_.end()) {
|
||||||
|
auto cacheIt = cache_.find(it->second);
|
||||||
|
if (cacheIt != cache_.end()) {
|
||||||
|
cacheIt->second.refCount++;
|
||||||
|
cacheIt->second.lastAccessTime = getCurrentTime();
|
||||||
|
return cacheIt->second.resource;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检查缓存是否存在
|
||||||
|
* @param path 资源路径
|
||||||
|
* @return 存在返回 true
|
||||||
|
*/
|
||||||
|
bool hasInCache(const std::string &path) const {
|
||||||
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
|
return pathIndex_.find(path) != pathIndex_.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 清除未使用的资源
|
||||||
|
*/
|
||||||
|
void clearUnused() {
|
||||||
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
|
for (auto it = cache_.begin(); it != cache_.end();) {
|
||||||
|
if (it->second.refCount == 0) {
|
||||||
|
totalMemory_ -= it->second.memorySize;
|
||||||
|
pathIndex_.erase(it->second.path);
|
||||||
|
it = cache_.erase(it);
|
||||||
|
} else {
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 清除所有资源
|
||||||
|
*/
|
||||||
|
void clearAll() {
|
||||||
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
|
cache_.clear();
|
||||||
|
pathIndex_.clear();
|
||||||
|
totalMemory_ = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取总内存使用量
|
||||||
|
* @return 内存大小(字节)
|
||||||
|
*/
|
||||||
|
size_t getTotalMemory() const {
|
||||||
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
|
return totalMemory_;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取资源数量
|
||||||
|
* @return 资源数量
|
||||||
|
*/
|
||||||
|
size_t getCount() const {
|
||||||
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
|
return cache_.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/**
|
||||||
|
* @brief 获取当前时间戳
|
||||||
|
* @return 时间戳(毫秒)
|
||||||
|
*/
|
||||||
|
static uint64_t getCurrentTime() {
|
||||||
|
auto now = std::chrono::steady_clock::now();
|
||||||
|
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||||
|
now.time_since_epoch());
|
||||||
|
return static_cast<uint64_t>(ms.count());
|
||||||
|
}
|
||||||
|
|
||||||
|
mutable std::mutex mutex_;
|
||||||
|
std::unordered_map<T *, CacheEntry> cache_;
|
||||||
|
std::unordered_map<std::string, T *> pathIndex_;
|
||||||
|
size_t totalMemory_ = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -287,7 +287,7 @@ std::string format_str(const char *fmt, Args &&...args) {
|
||||||
#define E2D_WARN(...) E2D_LOG_WARN(__VA_ARGS__)
|
#define E2D_WARN(...) E2D_LOG_WARN(__VA_ARGS__)
|
||||||
#define E2D_ERROR(...) E2D_LOG_ERROR(__VA_ARGS__)
|
#define E2D_ERROR(...) E2D_LOG_ERROR(__VA_ARGS__)
|
||||||
#define E2D_FATAL(...) E2D_LOG_FATAL(__VA_ARGS__)
|
#define E2D_FATAL(...) E2D_LOG_FATAL(__VA_ARGS__)
|
||||||
|
#define E2D_DEBUG(...) E2D_LOG_DEBUG(__VA_ARGS__)
|
||||||
// 带颜色参数的日志宏
|
// 带颜色参数的日志宏
|
||||||
#define E2D_LOG_COLOR(level, color, ...) \
|
#define E2D_LOG_COLOR(level, color, ...) \
|
||||||
do { \
|
do { \
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,180 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <extra2d/core/service_interface.h>
|
||||||
|
#include <extra2d/core/types.h>
|
||||||
|
#include <extra2d/resources/asset_bundle.h>
|
||||||
|
#include <extra2d/resources/resource_handle.h>
|
||||||
|
#include <functional>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 资源服务接口
|
||||||
|
*
|
||||||
|
* 提供统一的资源加载和管理功能
|
||||||
|
* 支持同步/异步加载、自动缓存、引用计数
|
||||||
|
*/
|
||||||
|
class IResourceService : public IService {
|
||||||
|
public:
|
||||||
|
~IResourceService() override = default;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 初始化资源服务
|
||||||
|
* @param assetRoot 资源根目录路径
|
||||||
|
* @return 初始化成功返回 true
|
||||||
|
*/
|
||||||
|
virtual bool init(const std::string &assetRoot) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 关闭资源服务
|
||||||
|
*/
|
||||||
|
virtual void shutdown() override = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 加载资源包
|
||||||
|
* @param path 资源包路径
|
||||||
|
* @return 加载成功返回 true
|
||||||
|
*/
|
||||||
|
virtual bool loadBundle(const std::string &path) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 异步加载资源包
|
||||||
|
* @param path 资源包路径
|
||||||
|
* @param callback 加载完成回调
|
||||||
|
*/
|
||||||
|
virtual void loadBundleAsync(const std::string &path,
|
||||||
|
std::function<void(bool)> callback) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 卸载资源包
|
||||||
|
* @param path 资源包路径
|
||||||
|
*/
|
||||||
|
virtual void unloadBundle(const std::string &path) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 同步加载资源
|
||||||
|
* @tparam T 资源类型
|
||||||
|
* @param path 资源路径
|
||||||
|
* @return 资源句柄
|
||||||
|
*/
|
||||||
|
template <typename T> ResourceHandle<T> load(const std::string &path);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 异步加载资源
|
||||||
|
* @tparam T 资源类型
|
||||||
|
* @param path 资源路径
|
||||||
|
* @param callback 加载完成回调
|
||||||
|
*/
|
||||||
|
template <typename T>
|
||||||
|
void loadAsync(const std::string &path,
|
||||||
|
std::function<void(ResourceHandle<T>)> callback);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 预加载资源
|
||||||
|
* @param path 资源路径
|
||||||
|
*/
|
||||||
|
virtual void preload(const std::string &path) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 执行垃圾回收
|
||||||
|
*/
|
||||||
|
virtual void gc() = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 清除未使用的资源
|
||||||
|
*/
|
||||||
|
virtual void clearUnused() = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 清除所有资源
|
||||||
|
*/
|
||||||
|
virtual void clearAll() = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 资源统计信息
|
||||||
|
*/
|
||||||
|
struct Stats {
|
||||||
|
size_t totalMemory = 0;
|
||||||
|
size_t textureMemory = 0;
|
||||||
|
size_t audioMemory = 0;
|
||||||
|
size_t totalCount = 0;
|
||||||
|
size_t textureCount = 0;
|
||||||
|
size_t audioCount = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取资源统计信息
|
||||||
|
* @return 统计信息
|
||||||
|
*/
|
||||||
|
virtual Stats getStats() const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检查资源是否存在
|
||||||
|
* @param path 资源路径
|
||||||
|
* @return 存在返回 true
|
||||||
|
*/
|
||||||
|
virtual bool exists(const std::string &path) const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 读取原始数据
|
||||||
|
* @param path 资源路径
|
||||||
|
* @return 原始数据
|
||||||
|
*/
|
||||||
|
virtual std::vector<uint8_t> readData(const std::string &path) const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取资源根目录
|
||||||
|
* @return 根目录路径
|
||||||
|
*/
|
||||||
|
virtual const std::string &getAssetRoot() const = 0;
|
||||||
|
|
||||||
|
ServiceInfo getServiceInfo() const override {
|
||||||
|
ServiceInfo info;
|
||||||
|
info.name = "ResourceService";
|
||||||
|
info.priority = ServicePriority::Resource;
|
||||||
|
info.enabled = true;
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 资源类型标识
|
||||||
|
*/
|
||||||
|
enum class ResourceType : uint8_t {
|
||||||
|
Unknown = 0,
|
||||||
|
Texture = 1,
|
||||||
|
Font = 2,
|
||||||
|
Shader = 3,
|
||||||
|
Material = 4,
|
||||||
|
Mesh = 5,
|
||||||
|
Audio = 6,
|
||||||
|
Config = 7,
|
||||||
|
Custom = 255
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 资源加载状态
|
||||||
|
*/
|
||||||
|
enum class ResourceState : uint8_t {
|
||||||
|
Unloaded = 0,
|
||||||
|
Loading = 1,
|
||||||
|
Loaded = 2,
|
||||||
|
Failed = 3,
|
||||||
|
Unloading = 4
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 资源元数据
|
||||||
|
*/
|
||||||
|
struct ResourceMeta {
|
||||||
|
std::string path;
|
||||||
|
ResourceType type = ResourceType::Unknown;
|
||||||
|
ResourceState state = ResourceState::Unloaded;
|
||||||
|
size_t size = 0;
|
||||||
|
uint32_t refCount = 0;
|
||||||
|
uint64_t lastAccessTime = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,222 @@
|
||||||
|
#include <extra2d/core/aes_cipher.h>
|
||||||
|
#include <extra2d/services/logger_service.h>
|
||||||
|
#include <openssl/evp.h>
|
||||||
|
#include <openssl/rand.h>
|
||||||
|
#include <openssl/kdf.h>
|
||||||
|
#include <cstring>
|
||||||
|
#include <random>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief AESCipher 实现细节
|
||||||
|
*/
|
||||||
|
class AESCipher::Impl {
|
||||||
|
public:
|
||||||
|
EVP_CIPHER_CTX* ctx = nullptr;
|
||||||
|
|
||||||
|
Impl() {
|
||||||
|
ctx = EVP_CIPHER_CTX_new();
|
||||||
|
}
|
||||||
|
|
||||||
|
~Impl() {
|
||||||
|
if (ctx) {
|
||||||
|
EVP_CIPHER_CTX_free(ctx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
AESCipher::AESCipher() : impl_(makeUnique<Impl>()) {
|
||||||
|
}
|
||||||
|
|
||||||
|
AESCipher::~AESCipher() {
|
||||||
|
clearKey();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AESCipher::setKey(const std::vector<uint8_t>& key,
|
||||||
|
const std::vector<uint8_t>& iv) {
|
||||||
|
return setKey(key.data(), key.size(), iv.data(), iv.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AESCipher::setKey(const uint8_t* key, size_t keySize,
|
||||||
|
const uint8_t* iv, size_t ivSize) {
|
||||||
|
if (keySize != KEY_SIZE) {
|
||||||
|
E2D_ERROR("Invalid key size: expected {}, got {}", KEY_SIZE, keySize);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ivSize != IV_SIZE) {
|
||||||
|
E2D_ERROR("Invalid IV size: expected {}, got {}", IV_SIZE, ivSize);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
key_.assign(key, key + keySize);
|
||||||
|
iv_.assign(iv, iv + ivSize);
|
||||||
|
keySet_ = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint8_t> AESCipher::encrypt(const std::vector<uint8_t>& data) {
|
||||||
|
return encrypt(data.data(), data.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint8_t> AESCipher::encrypt(const uint8_t* data, size_t size) {
|
||||||
|
if (!keySet_) {
|
||||||
|
E2D_ERROR("Key not set");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!data || size == 0) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
int ciphertextLen = 0;
|
||||||
|
int finalLen = 0;
|
||||||
|
std::vector<uint8_t> ciphertext(size + BLOCK_SIZE);
|
||||||
|
|
||||||
|
if (EVP_EncryptInit_ex(impl_->ctx, EVP_aes_256_cbc(), nullptr,
|
||||||
|
key_.data(), iv_.data()) != 1) {
|
||||||
|
E2D_ERROR("EVP_EncryptInit_ex failed");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (EVP_EncryptUpdate(impl_->ctx, ciphertext.data(), &ciphertextLen,
|
||||||
|
data, static_cast<int>(size)) != 1) {
|
||||||
|
E2D_ERROR("EVP_EncryptUpdate failed");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (EVP_EncryptFinal_ex(impl_->ctx, ciphertext.data() + ciphertextLen,
|
||||||
|
&finalLen) != 1) {
|
||||||
|
E2D_ERROR("EVP_EncryptFinal_ex failed");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
ciphertext.resize(ciphertextLen + finalLen);
|
||||||
|
return ciphertext;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint8_t> AESCipher::decrypt(const std::vector<uint8_t>& data) {
|
||||||
|
return decrypt(data.data(), data.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint8_t> AESCipher::decrypt(const uint8_t* data, size_t size) {
|
||||||
|
if (!keySet_) {
|
||||||
|
E2D_ERROR("Key not set");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!data || size == 0) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
int plaintextLen = 0;
|
||||||
|
int finalLen = 0;
|
||||||
|
std::vector<uint8_t> plaintext(size + BLOCK_SIZE);
|
||||||
|
|
||||||
|
if (EVP_DecryptInit_ex(impl_->ctx, EVP_aes_256_cbc(), nullptr,
|
||||||
|
key_.data(), iv_.data()) != 1) {
|
||||||
|
E2D_ERROR("EVP_DecryptInit_ex failed");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (EVP_DecryptUpdate(impl_->ctx, plaintext.data(), &plaintextLen,
|
||||||
|
data, static_cast<int>(size)) != 1) {
|
||||||
|
E2D_ERROR("EVP_DecryptUpdate failed");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (EVP_DecryptFinal_ex(impl_->ctx, plaintext.data() + plaintextLen,
|
||||||
|
&finalLen) != 1) {
|
||||||
|
E2D_ERROR("EVP_DecryptFinal_ex failed - wrong key or corrupted data");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
plaintext.resize(plaintextLen + finalLen);
|
||||||
|
return plaintext;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AESCipher::encryptInPlace(std::vector<uint8_t>& data) {
|
||||||
|
auto encrypted = encrypt(data);
|
||||||
|
if (encrypted.empty() && !data.empty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
data = std::move(encrypted);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AESCipher::decryptInPlace(std::vector<uint8_t>& data) {
|
||||||
|
auto decrypted = decrypt(data);
|
||||||
|
if (decrypted.empty() && !data.empty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
data = std::move(decrypted);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AESCipher::clearKey() {
|
||||||
|
if (!key_.empty()) {
|
||||||
|
std::memset(key_.data(), 0, key_.size());
|
||||||
|
key_.clear();
|
||||||
|
}
|
||||||
|
if (!iv_.empty()) {
|
||||||
|
std::memset(iv_.data(), 0, iv_.size());
|
||||||
|
iv_.clear();
|
||||||
|
}
|
||||||
|
keySet_ = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AESCipher::generateRandomKey(std::vector<uint8_t>& key,
|
||||||
|
std::vector<uint8_t>& iv) {
|
||||||
|
key.resize(KEY_SIZE);
|
||||||
|
iv.resize(IV_SIZE);
|
||||||
|
|
||||||
|
if (RAND_bytes(key.data(), KEY_SIZE) != 1) {
|
||||||
|
std::random_device rd;
|
||||||
|
std::mt19937 gen(rd());
|
||||||
|
std::uniform_int_distribution<uint8_t> dis(0, 255);
|
||||||
|
for (size_t i = 0; i < KEY_SIZE; ++i) {
|
||||||
|
key[i] = dis(gen);
|
||||||
|
}
|
||||||
|
for (size_t i = 0; i < IV_SIZE; ++i) {
|
||||||
|
iv[i] = dis(gen);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
RAND_bytes(iv.data(), IV_SIZE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AESCipher::deriveKeyFromPassword(const std::string& password,
|
||||||
|
const std::vector<uint8_t>& salt,
|
||||||
|
std::vector<uint8_t>& key,
|
||||||
|
std::vector<uint8_t>& iv,
|
||||||
|
int iterations) {
|
||||||
|
key.resize(KEY_SIZE);
|
||||||
|
iv.resize(IV_SIZE);
|
||||||
|
|
||||||
|
if (salt.empty()) {
|
||||||
|
std::vector<uint8_t> defaultSalt(16, 0);
|
||||||
|
PKCS5_PBKDF2_HMAC(password.c_str(), static_cast<int>(password.length()),
|
||||||
|
defaultSalt.data(), static_cast<int>(defaultSalt.size()),
|
||||||
|
iterations, EVP_sha256(),
|
||||||
|
KEY_SIZE, key.data());
|
||||||
|
} else {
|
||||||
|
PKCS5_PBKDF2_HMAC(password.c_str(), static_cast<int>(password.length()),
|
||||||
|
salt.data(), static_cast<int>(salt.size()),
|
||||||
|
iterations, EVP_sha256(),
|
||||||
|
KEY_SIZE, key.data());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint8_t> ivDerive(IV_SIZE);
|
||||||
|
std::vector<uint8_t> saltForIv = salt.empty() ? std::vector<uint8_t>(16, 1) : salt;
|
||||||
|
for (auto& b : saltForIv) {
|
||||||
|
b = ~b;
|
||||||
|
}
|
||||||
|
|
||||||
|
PKCS5_PBKDF2_HMAC(password.c_str(), static_cast<int>(password.length()),
|
||||||
|
saltForIv.data(), static_cast<int>(saltForIv.size()),
|
||||||
|
iterations, EVP_sha256(),
|
||||||
|
IV_SIZE, iv.data());
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,244 @@
|
||||||
|
#include <cstring>
|
||||||
|
#include <extra2d/core/compression.h>
|
||||||
|
#include <extra2d/services/logger_service.h>
|
||||||
|
#include <lz4.h>
|
||||||
|
#include <lz4hc.h>
|
||||||
|
#include <zlib.h>
|
||||||
|
#include <zstd.h>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
std::vector<uint8_t> Compression::zlibCompress(const std::vector<uint8_t> &data,
|
||||||
|
int level) {
|
||||||
|
if (data.empty()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
uLongf compressedSize = compressBound(data.size());
|
||||||
|
std::vector<uint8_t> compressed(compressedSize);
|
||||||
|
|
||||||
|
int result = compress2(compressed.data(), &compressedSize, data.data(),
|
||||||
|
data.size(), level);
|
||||||
|
|
||||||
|
if (result != Z_OK) {
|
||||||
|
E2D_ERROR("Zlib compression failed with error: {}", result);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
compressed.resize(compressedSize);
|
||||||
|
return compressed;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint8_t>
|
||||||
|
Compression::zlibDecompress(const std::vector<uint8_t> &data,
|
||||||
|
size_t originalSize) {
|
||||||
|
if (data.empty()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t outputSize = originalSize;
|
||||||
|
if (outputSize == 0) {
|
||||||
|
outputSize = data.size() * 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint8_t> decompressed(outputSize);
|
||||||
|
uLongf destLen = outputSize;
|
||||||
|
|
||||||
|
int result =
|
||||||
|
uncompress(decompressed.data(), &destLen, data.data(), data.size());
|
||||||
|
|
||||||
|
if (result == Z_BUF_ERROR && originalSize == 0) {
|
||||||
|
outputSize *= 2;
|
||||||
|
decompressed.resize(outputSize);
|
||||||
|
destLen = outputSize;
|
||||||
|
result =
|
||||||
|
uncompress(decompressed.data(), &destLen, data.data(), data.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result != Z_OK) {
|
||||||
|
E2D_ERROR("Zlib decompression failed with error: {}", result);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
decompressed.resize(destLen);
|
||||||
|
return decompressed;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint8_t>
|
||||||
|
Compression::lz4Compress(const std::vector<uint8_t> &data) {
|
||||||
|
if (data.empty()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
int compressedSize = LZ4_compressBound(static_cast<int>(data.size()));
|
||||||
|
std::vector<uint8_t> compressed(compressedSize);
|
||||||
|
|
||||||
|
compressedSize =
|
||||||
|
LZ4_compress_default(reinterpret_cast<const char *>(data.data()),
|
||||||
|
reinterpret_cast<char *>(compressed.data()),
|
||||||
|
static_cast<int>(data.size()), compressedSize);
|
||||||
|
|
||||||
|
if (compressedSize <= 0) {
|
||||||
|
E2D_ERROR("LZ4 compression failed");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
compressed.resize(compressedSize);
|
||||||
|
return compressed;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint8_t>
|
||||||
|
Compression::lz4Decompress(const std::vector<uint8_t> &data,
|
||||||
|
size_t originalSize) {
|
||||||
|
if (data.empty() || originalSize == 0) {
|
||||||
|
E2D_ERROR("LZ4 decompression requires original size");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint8_t> decompressed(originalSize);
|
||||||
|
|
||||||
|
int result = LZ4_decompress_safe(
|
||||||
|
reinterpret_cast<const char *>(data.data()),
|
||||||
|
reinterpret_cast<char *>(decompressed.data()),
|
||||||
|
static_cast<int>(data.size()), static_cast<int>(originalSize));
|
||||||
|
|
||||||
|
if (result < 0) {
|
||||||
|
E2D_ERROR("LZ4 decompression failed with error: {}", result);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
return decompressed;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint8_t> Compression::zstdCompress(const std::vector<uint8_t> &data,
|
||||||
|
int level) {
|
||||||
|
if (data.empty()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t compressedSize = ZSTD_compressBound(data.size());
|
||||||
|
std::vector<uint8_t> compressed(compressedSize);
|
||||||
|
|
||||||
|
compressedSize = ZSTD_compress(compressed.data(), compressedSize, data.data(),
|
||||||
|
data.size(), level);
|
||||||
|
|
||||||
|
if (ZSTD_isError(compressedSize)) {
|
||||||
|
E2D_ERROR("Zstd compression failed: {}", ZSTD_getErrorName(compressedSize));
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
compressed.resize(compressedSize);
|
||||||
|
return compressed;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint8_t>
|
||||||
|
Compression::zstdDecompress(const std::vector<uint8_t> &data,
|
||||||
|
size_t originalSize) {
|
||||||
|
if (data.empty()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t outputSize = originalSize;
|
||||||
|
if (outputSize == 0) {
|
||||||
|
outputSize = ZSTD_getFrameContentSize(data.data(), data.size());
|
||||||
|
if (outputSize == ZSTD_CONTENTSIZE_ERROR ||
|
||||||
|
outputSize == ZSTD_CONTENTSIZE_UNKNOWN) {
|
||||||
|
outputSize = data.size() * 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint8_t> decompressed(outputSize);
|
||||||
|
|
||||||
|
size_t result = ZSTD_decompress(decompressed.data(), outputSize, data.data(),
|
||||||
|
data.size());
|
||||||
|
|
||||||
|
if (ZSTD_isError(result)) {
|
||||||
|
E2D_ERROR("Zstd decompression failed: {}", ZSTD_getErrorName(result));
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
decompressed.resize(result);
|
||||||
|
return decompressed;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint8_t> Compression::compress(const std::vector<uint8_t> &data,
|
||||||
|
CompressionType type, int level) {
|
||||||
|
switch (type) {
|
||||||
|
case CompressionType::None:
|
||||||
|
return data;
|
||||||
|
case CompressionType::Zlib:
|
||||||
|
return zlibCompress(data, level >= 0 ? level : 6);
|
||||||
|
case CompressionType::LZ4:
|
||||||
|
return lz4Compress(data);
|
||||||
|
case CompressionType::Zstd:
|
||||||
|
return zstdCompress(data, level >= 0 ? level : 3);
|
||||||
|
default:
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint8_t> Compression::decompress(const std::vector<uint8_t> &data,
|
||||||
|
CompressionType type,
|
||||||
|
size_t originalSize) {
|
||||||
|
switch (type) {
|
||||||
|
case CompressionType::None:
|
||||||
|
return data;
|
||||||
|
case CompressionType::Zlib:
|
||||||
|
return zlibDecompress(data, originalSize);
|
||||||
|
case CompressionType::LZ4:
|
||||||
|
return lz4Decompress(data, originalSize);
|
||||||
|
case CompressionType::Zstd:
|
||||||
|
return zstdDecompress(data, originalSize);
|
||||||
|
default:
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t Compression::estimateCompressedSize(size_t originalSize,
|
||||||
|
CompressionType type) {
|
||||||
|
switch (type) {
|
||||||
|
case CompressionType::None:
|
||||||
|
return originalSize;
|
||||||
|
case CompressionType::Zlib:
|
||||||
|
return compressBound(originalSize);
|
||||||
|
case CompressionType::LZ4:
|
||||||
|
return LZ4_compressBound(static_cast<int>(originalSize));
|
||||||
|
case CompressionType::Zstd:
|
||||||
|
return ZSTD_compressBound(originalSize);
|
||||||
|
default:
|
||||||
|
return originalSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *Compression::getCompressionName(CompressionType type) {
|
||||||
|
switch (type) {
|
||||||
|
case CompressionType::None:
|
||||||
|
return "None";
|
||||||
|
case CompressionType::Zlib:
|
||||||
|
return "Zlib";
|
||||||
|
case CompressionType::LZ4:
|
||||||
|
return "LZ4";
|
||||||
|
case CompressionType::Zstd:
|
||||||
|
return "Zstd";
|
||||||
|
default:
|
||||||
|
return "Unknown";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t Compression::crc32(const std::vector<uint8_t> &data) {
|
||||||
|
return crc32(data.data(), data.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t Compression::crc32(const uint8_t *data, size_t size) {
|
||||||
|
return static_cast<uint32_t>(::crc32(0L, data, static_cast<uInt>(size)));
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t Compression::adler32(const std::vector<uint8_t> &data) {
|
||||||
|
return adler32(data.data(), data.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t Compression::adler32(const uint8_t *data, size_t size) {
|
||||||
|
return static_cast<uint32_t>(::adler32(1L, data, static_cast<uInt>(size)));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,328 @@
|
||||||
|
#include <cstring>
|
||||||
|
#include <extra2d/core/unicode_utils.h>
|
||||||
|
#include <simdutf/simdutf.h>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
std::u32string UnicodeUtils::toUtf32(const std::string &text,
|
||||||
|
TextEncoding encoding) {
|
||||||
|
if (text.empty()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (encoding) {
|
||||||
|
case TextEncoding::UTF8:
|
||||||
|
return utf8ToUtf32(text);
|
||||||
|
case TextEncoding::UTF16: {
|
||||||
|
std::u16string utf16;
|
||||||
|
utf16.resize(text.size() / 2);
|
||||||
|
std::memcpy(utf16.data(), text.data(), text.size());
|
||||||
|
return utf16ToUtf32(utf16);
|
||||||
|
}
|
||||||
|
case TextEncoding::UTF32: {
|
||||||
|
std::u32string utf32;
|
||||||
|
utf32.resize(text.size() / 4);
|
||||||
|
std::memcpy(utf32.data(), text.data(), text.size());
|
||||||
|
return utf32;
|
||||||
|
}
|
||||||
|
case TextEncoding::GBK:
|
||||||
|
return gbkToUtf32(text);
|
||||||
|
case TextEncoding::Auto:
|
||||||
|
return utf8ToUtf32(text);
|
||||||
|
default:
|
||||||
|
return utf8ToUtf32(text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::u32string UnicodeUtils::utf8ToUtf32(const std::string &utf8) {
|
||||||
|
if (utf8.empty()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t len = simdutf::utf32_length_from_utf8(utf8.data(), utf8.size());
|
||||||
|
std::u32string utf32;
|
||||||
|
utf32.resize(len);
|
||||||
|
|
||||||
|
size_t result =
|
||||||
|
simdutf::convert_utf8_to_utf32(utf8.data(), utf8.size(), utf32.data());
|
||||||
|
|
||||||
|
if (result == 0 && len > 0) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
utf32.resize(result);
|
||||||
|
return utf32;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::u32string UnicodeUtils::utf8ToUtf32(std::string_view utf8) {
|
||||||
|
if (utf8.empty()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t len = simdutf::utf32_length_from_utf8(utf8.data(), utf8.size());
|
||||||
|
std::u32string utf32;
|
||||||
|
utf32.resize(len);
|
||||||
|
|
||||||
|
size_t result =
|
||||||
|
simdutf::convert_utf8_to_utf32(utf8.data(), utf8.size(), utf32.data());
|
||||||
|
|
||||||
|
if (result == 0 && len > 0) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
utf32.resize(result);
|
||||||
|
return utf32;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::u32string UnicodeUtils::utf16ToUtf32(const std::u16string &utf16) {
|
||||||
|
if (utf16.empty()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t len = simdutf::utf32_length_from_utf16(utf16.data(), utf16.size());
|
||||||
|
std::u32string utf32;
|
||||||
|
utf32.resize(len);
|
||||||
|
|
||||||
|
size_t result =
|
||||||
|
simdutf::convert_utf16_to_utf32(utf16.data(), utf16.size(), utf32.data());
|
||||||
|
|
||||||
|
if (result == 0 && len > 0) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
utf32.resize(result);
|
||||||
|
return utf32;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string UnicodeUtils::utf32ToUtf8(const std::u32string &utf32) {
|
||||||
|
if (utf32.empty()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t len = simdutf::utf8_length_from_utf32(utf32.data(), utf32.size());
|
||||||
|
std::string utf8;
|
||||||
|
utf8.resize(len);
|
||||||
|
|
||||||
|
size_t result =
|
||||||
|
simdutf::convert_utf32_to_utf8(utf32.data(), utf32.size(), utf8.data());
|
||||||
|
|
||||||
|
if (result == 0 && len > 0) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
utf8.resize(result);
|
||||||
|
return utf8;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::u16string UnicodeUtils::utf32ToUtf16(const std::u32string &utf32) {
|
||||||
|
if (utf32.empty()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t len = simdutf::utf16_length_from_utf32(utf32.data(), utf32.size());
|
||||||
|
std::u16string utf16;
|
||||||
|
utf16.resize(len);
|
||||||
|
|
||||||
|
size_t result =
|
||||||
|
simdutf::convert_utf32_to_utf16(utf32.data(), utf32.size(), utf16.data());
|
||||||
|
|
||||||
|
if (result == 0 && len > 0) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
utf16.resize(result);
|
||||||
|
return utf16;
|
||||||
|
}
|
||||||
|
|
||||||
|
TextEncoding UnicodeUtils::detectEncoding(const std::string &data) {
|
||||||
|
if (data.empty()) {
|
||||||
|
return TextEncoding::UTF8;
|
||||||
|
}
|
||||||
|
|
||||||
|
simdutf::encoding_type detected =
|
||||||
|
simdutf::autodetect_encoding(data.data(), data.size());
|
||||||
|
|
||||||
|
switch (detected) {
|
||||||
|
case simdutf::encoding_type::UTF8:
|
||||||
|
return TextEncoding::UTF8;
|
||||||
|
case simdutf::encoding_type::UTF16_LE:
|
||||||
|
case simdutf::encoding_type::UTF16_BE:
|
||||||
|
return TextEncoding::UTF16;
|
||||||
|
case simdutf::encoding_type::UTF32_LE:
|
||||||
|
case simdutf::encoding_type::UTF32_BE:
|
||||||
|
return TextEncoding::UTF32;
|
||||||
|
default:
|
||||||
|
return TextEncoding::UTF8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t UnicodeUtils::countChars(const std::string &text,
|
||||||
|
TextEncoding encoding) {
|
||||||
|
if (text.empty()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (encoding) {
|
||||||
|
case TextEncoding::UTF8:
|
||||||
|
return countCharsUtf8(text);
|
||||||
|
case TextEncoding::UTF16: {
|
||||||
|
std::u16string utf16;
|
||||||
|
utf16.resize(text.size() / 2);
|
||||||
|
std::memcpy(utf16.data(), text.data(), text.size());
|
||||||
|
return countCharsUtf16(utf16);
|
||||||
|
}
|
||||||
|
case TextEncoding::UTF32:
|
||||||
|
return text.size() / 4;
|
||||||
|
default:
|
||||||
|
return countCharsUtf8(text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t UnicodeUtils::countCharsUtf8(const std::string &utf8) {
|
||||||
|
if (utf8.empty()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return simdutf::utf32_length_from_utf8(utf8.data(), utf8.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t UnicodeUtils::countCharsUtf16(const std::u16string &utf16) {
|
||||||
|
if (utf16.empty()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return simdutf::utf32_length_from_utf16(utf16.data(), utf16.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UnicodeUtils::validateEncoding(const std::string &text,
|
||||||
|
TextEncoding encoding) {
|
||||||
|
if (text.empty()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (encoding) {
|
||||||
|
case TextEncoding::UTF8:
|
||||||
|
return validateUtf8(text);
|
||||||
|
case TextEncoding::UTF16: {
|
||||||
|
if (text.size() % 2 != 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return simdutf::validate_utf16(
|
||||||
|
reinterpret_cast<const char16_t *>(text.data()), text.size() / 2);
|
||||||
|
}
|
||||||
|
case TextEncoding::UTF32: {
|
||||||
|
if (text.size() % 4 != 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return simdutf::validate_utf32(
|
||||||
|
reinterpret_cast<const char32_t *>(text.data()), text.size() / 4);
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return validateUtf8(text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UnicodeUtils::validateUtf8(const std::string &utf8) {
|
||||||
|
if (utf8.empty()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return simdutf::validate_utf8(utf8.data(), utf8.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UnicodeUtils::validateUtf16(const std::u16string &utf16) {
|
||||||
|
if (utf16.empty()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return simdutf::validate_utf16(utf16.data(), utf16.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::u32string UnicodeUtils::gbkToUtf32(const std::string &gbk) {
|
||||||
|
return utf8ToUtf32(gbk);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string UnicodeUtils::utf32ToGbk(const std::u32string &utf32) {
|
||||||
|
return utf32ToUtf8(utf32);
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *UnicodeUtils::getEncodingName(TextEncoding encoding) {
|
||||||
|
switch (encoding) {
|
||||||
|
case TextEncoding::UTF8:
|
||||||
|
return "UTF-8";
|
||||||
|
case TextEncoding::UTF16:
|
||||||
|
return "UTF-16";
|
||||||
|
case TextEncoding::UTF32:
|
||||||
|
return "UTF-32";
|
||||||
|
case TextEncoding::GBK:
|
||||||
|
return "GBK";
|
||||||
|
case TextEncoding::Auto:
|
||||||
|
return "Auto";
|
||||||
|
default:
|
||||||
|
return "Unknown";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UnicodeUtils::isValidCodepoint(uint32_t codepoint) {
|
||||||
|
return codepoint <= 0x10FFFF && !(codepoint >= 0xD800 && codepoint <= 0xDFFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UnicodeUtils::isWhitespace(uint32_t codepoint) {
|
||||||
|
return codepoint == ' ' || codepoint == '\t' || codepoint == '\n' ||
|
||||||
|
codepoint == '\r' || codepoint == '\v' || codepoint == '\f' ||
|
||||||
|
codepoint == 0x00A0 || codepoint == 0x1680 ||
|
||||||
|
(codepoint >= 0x2000 && codepoint <= 0x200A) || codepoint == 0x2028 ||
|
||||||
|
codepoint == 0x2029 || codepoint == 0x202F || codepoint == 0x205F ||
|
||||||
|
codepoint == 0x3000;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UnicodeUtils::isNewline(uint32_t codepoint) {
|
||||||
|
return codepoint == '\n' || codepoint == '\r' || codepoint == 0x2028 ||
|
||||||
|
codepoint == 0x2029;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UnicodeUtils::isControl(uint32_t codepoint) {
|
||||||
|
return (codepoint <= 0x1F) || (codepoint >= 0x7F && codepoint <= 0x9F);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UnicodeUtils::isChinese(uint32_t codepoint) {
|
||||||
|
return (codepoint >= 0x4E00 && codepoint <= 0x9FFF) ||
|
||||||
|
(codepoint >= 0x3400 && codepoint <= 0x4DBF) ||
|
||||||
|
(codepoint >= 0x20000 && codepoint <= 0x2A6DF) ||
|
||||||
|
(codepoint >= 0x2A700 && codepoint <= 0x2B73F) ||
|
||||||
|
(codepoint >= 0x2B740 && codepoint <= 0x2B81F) ||
|
||||||
|
(codepoint >= 0x2B820 && codepoint <= 0x2CEAF) ||
|
||||||
|
(codepoint >= 0xF900 && codepoint <= 0xFAFF) ||
|
||||||
|
(codepoint >= 0x2F800 && codepoint <= 0x2FA1F);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UnicodeUtils::isJapanese(uint32_t codepoint) {
|
||||||
|
return (codepoint >= 0x3040 && codepoint <= 0x309F) ||
|
||||||
|
(codepoint >= 0x30A0 && codepoint <= 0x30FF) ||
|
||||||
|
(codepoint >= 0x31F0 && codepoint <= 0x31FF) ||
|
||||||
|
(codepoint >= 0xFF65 && codepoint <= 0xFF9F);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UnicodeUtils::isKorean(uint32_t codepoint) {
|
||||||
|
return (codepoint >= 0xAC00 && codepoint <= 0xD7AF) ||
|
||||||
|
(codepoint >= 0x1100 && codepoint <= 0x11FF) ||
|
||||||
|
(codepoint >= 0x3130 && codepoint <= 0x318F) ||
|
||||||
|
(codepoint >= 0xA960 && codepoint <= 0xA97F) ||
|
||||||
|
(codepoint >= 0xD7B0 && codepoint <= 0xD7FF);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UnicodeUtils::isCJK(uint32_t codepoint) {
|
||||||
|
return isChinese(codepoint) || isJapanese(codepoint) || isKorean(codepoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t UnicodeUtils::toUpper(uint32_t codepoint) {
|
||||||
|
if (codepoint >= 'a' && codepoint <= 'z') {
|
||||||
|
return codepoint - 32;
|
||||||
|
}
|
||||||
|
return codepoint;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t UnicodeUtils::toLower(uint32_t codepoint) {
|
||||||
|
if (codepoint >= 'A' && codepoint <= 'Z') {
|
||||||
|
return codepoint + 32;
|
||||||
|
}
|
||||||
|
return codepoint;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,389 @@
|
||||||
|
#include <extra2d/core/aes_cipher.h>
|
||||||
|
#include <extra2d/core/compression.h>
|
||||||
|
#include <extra2d/core/unicode_utils.h>
|
||||||
|
#include <extra2d/resources/asset_bundle.h>
|
||||||
|
#include <extra2d/resources/resource_system.h>
|
||||||
|
#include <extra2d/services/logger_service.h>
|
||||||
|
#include <extra2d/services/resource_service.h>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <fstream>
|
||||||
|
#include <thread>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 文件系统资源包实现
|
||||||
|
*/
|
||||||
|
class FileBundle : public IAssetBundle {
|
||||||
|
public:
|
||||||
|
explicit FileBundle(const std::string &rootPath) : rootPath_(rootPath) {
|
||||||
|
if (!std::filesystem::exists(rootPath_)) {
|
||||||
|
E2D_WARN("FileBundle root path does not exist: {}", rootPath_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool contains(const std::string &path) const override {
|
||||||
|
auto fullPath = std::filesystem::path(rootPath_) / path;
|
||||||
|
return std::filesystem::exists(fullPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint8_t> read(const std::string &path) const override {
|
||||||
|
auto fullPath = std::filesystem::path(rootPath_) / path;
|
||||||
|
if (!std::filesystem::exists(fullPath)) {
|
||||||
|
E2D_WARN("File not found: {}", path);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::ifstream file(fullPath, std::ios::binary | std::ios::ate);
|
||||||
|
if (!file.is_open()) {
|
||||||
|
E2D_WARN("Failed to open file: {}", path);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
auto size = file.tellg();
|
||||||
|
file.seekg(0, std::ios::beg);
|
||||||
|
|
||||||
|
std::vector<uint8_t> data(size);
|
||||||
|
file.read(reinterpret_cast<char *>(data.data()), size);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
readAsync(const std::string &path,
|
||||||
|
std::function<void(std::vector<uint8_t>)> callback) const override {
|
||||||
|
std::thread([this, path, callback]() {
|
||||||
|
auto data = read(path);
|
||||||
|
if (callback) {
|
||||||
|
callback(std::move(data));
|
||||||
|
}
|
||||||
|
}).detach();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<std::istream> openStream(const std::string &path) override {
|
||||||
|
auto fullPath = std::filesystem::path(rootPath_) / path;
|
||||||
|
return std::make_unique<std::ifstream>(fullPath, std::ios::binary);
|
||||||
|
}
|
||||||
|
|
||||||
|
AssetInfo getInfo(const std::string &path) const override {
|
||||||
|
AssetInfo info;
|
||||||
|
auto fullPath = std::filesystem::path(rootPath_) / path;
|
||||||
|
if (std::filesystem::exists(fullPath)) {
|
||||||
|
info.size = std::filesystem::file_size(fullPath);
|
||||||
|
auto ftime = std::filesystem::last_write_time(fullPath);
|
||||||
|
auto sctp = std::chrono::time_point_cast<std::chrono::seconds>(
|
||||||
|
ftime - std::filesystem::file_time_type::clock::now() +
|
||||||
|
std::chrono::system_clock::now());
|
||||||
|
info.modifiedTime = sctp.time_since_epoch().count();
|
||||||
|
}
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> list() const override {
|
||||||
|
std::vector<std::string> result;
|
||||||
|
if (!std::filesystem::exists(rootPath_)) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto &entry :
|
||||||
|
std::filesystem::recursive_directory_iterator(rootPath_)) {
|
||||||
|
if (entry.is_regular_file()) {
|
||||||
|
auto relative = std::filesystem::relative(entry.path(), rootPath_);
|
||||||
|
result.push_back(relative.string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string &getName() const override { return rootPath_; }
|
||||||
|
bool isValid() const override { return std::filesystem::exists(rootPath_); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string rootPath_;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 打包资源包实现
|
||||||
|
*/
|
||||||
|
class PackBundle : public IAssetBundle {
|
||||||
|
public:
|
||||||
|
PackBundle() = default;
|
||||||
|
~PackBundle() override = default;
|
||||||
|
|
||||||
|
bool load(const std::string &path) {
|
||||||
|
std::ifstream file(path, std::ios::binary);
|
||||||
|
if (!file.is_open()) {
|
||||||
|
E2D_ERROR("Failed to open pack file: {}", path);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
file.read(reinterpret_cast<char *>(&header_), sizeof(BundleHeader));
|
||||||
|
if (std::string(header_.magic, 4) != "E2DP") {
|
||||||
|
E2D_ERROR("Invalid pack file magic: {}", path);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
file.seekg(header_.entryTableOffset);
|
||||||
|
entries_.clear();
|
||||||
|
entries_.reserve(header_.entryCount);
|
||||||
|
|
||||||
|
for (uint32_t i = 0; i < header_.entryCount; ++i) {
|
||||||
|
BundleEntry entry;
|
||||||
|
|
||||||
|
uint16_t pathLen;
|
||||||
|
file.read(reinterpret_cast<char *>(&pathLen), sizeof(pathLen));
|
||||||
|
entry.path.resize(pathLen);
|
||||||
|
file.read(entry.path.data(), pathLen);
|
||||||
|
|
||||||
|
file.read(reinterpret_cast<char *>(&entry.offset), sizeof(entry.offset));
|
||||||
|
file.read(reinterpret_cast<char *>(&entry.compressedSize),
|
||||||
|
sizeof(entry.compressedSize));
|
||||||
|
file.read(reinterpret_cast<char *>(&entry.originalSize),
|
||||||
|
sizeof(entry.originalSize));
|
||||||
|
file.read(reinterpret_cast<char *>(&entry.checksum),
|
||||||
|
sizeof(entry.checksum));
|
||||||
|
file.read(reinterpret_cast<char *>(&entry.compression),
|
||||||
|
sizeof(entry.compression));
|
||||||
|
file.read(reinterpret_cast<char *>(&entry.encryption),
|
||||||
|
sizeof(entry.encryption));
|
||||||
|
file.read(reinterpret_cast<char *>(entry.reserved),
|
||||||
|
sizeof(entry.reserved));
|
||||||
|
|
||||||
|
entries_.push_back(entry);
|
||||||
|
entryMap_[entry.path] = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
filePath_ = path;
|
||||||
|
name_ = std::filesystem::path(path).filename().string();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool contains(const std::string &path) const override {
|
||||||
|
return entryMap_.find(path) != entryMap_.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint8_t> read(const std::string &path) const override {
|
||||||
|
auto it = entryMap_.find(path);
|
||||||
|
if (it == entryMap_.end()) {
|
||||||
|
E2D_WARN("Entry not found in pack: {}", path);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto &entry = entries_[it->second];
|
||||||
|
std::ifstream file(filePath_, std::ios::binary);
|
||||||
|
if (!file.is_open()) {
|
||||||
|
E2D_ERROR("Failed to open pack file: {}", filePath_);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
file.seekg(entry.offset);
|
||||||
|
std::vector<uint8_t> data(entry.compressedSize);
|
||||||
|
file.read(reinterpret_cast<char *>(data.data()), entry.compressedSize);
|
||||||
|
|
||||||
|
if (entry.compression != 0) {
|
||||||
|
auto compressionType = static_cast<CompressionType>(entry.compression);
|
||||||
|
data = Compression::decompress(data, compressionType, entry.originalSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
readAsync(const std::string &path,
|
||||||
|
std::function<void(std::vector<uint8_t>)> callback) const override {
|
||||||
|
std::thread([this, path, callback]() {
|
||||||
|
auto data = read(path);
|
||||||
|
if (callback) {
|
||||||
|
callback(std::move(data));
|
||||||
|
}
|
||||||
|
}).detach();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<std::istream> openStream(const std::string &path) override {
|
||||||
|
auto data = read(path);
|
||||||
|
auto stream = std::make_unique<std::stringstream>();
|
||||||
|
stream->write(reinterpret_cast<const char *>(data.data()), data.size());
|
||||||
|
stream->seekg(0);
|
||||||
|
return stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
AssetInfo getInfo(const std::string &path) const override {
|
||||||
|
AssetInfo info;
|
||||||
|
auto it = entryMap_.find(path);
|
||||||
|
if (it != entryMap_.end()) {
|
||||||
|
const auto &entry = entries_[it->second];
|
||||||
|
info.size = entry.originalSize;
|
||||||
|
info.compressedSize = entry.compressedSize;
|
||||||
|
info.compressed = entry.compression != 0;
|
||||||
|
info.encrypted = entry.encryption != 0;
|
||||||
|
}
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> list() const override {
|
||||||
|
std::vector<std::string> result;
|
||||||
|
result.reserve(entries_.size());
|
||||||
|
for (const auto &entry : entries_) {
|
||||||
|
result.push_back(entry.path);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string &getName() const override { return name_; }
|
||||||
|
bool isValid() const override { return !filePath_.empty(); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string filePath_;
|
||||||
|
std::string name_;
|
||||||
|
BundleHeader header_;
|
||||||
|
std::vector<BundleEntry> entries_;
|
||||||
|
std::unordered_map<std::string, size_t> entryMap_;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 加密资源包实现
|
||||||
|
*/
|
||||||
|
class CryptoBundle : public PackBundle {
|
||||||
|
public:
|
||||||
|
CryptoBundle() = default;
|
||||||
|
|
||||||
|
bool load(const std::string &path, const std::vector<uint8_t> &key,
|
||||||
|
const std::vector<uint8_t> &iv) {
|
||||||
|
if (!cipher_.setKey(key, iv)) {
|
||||||
|
E2D_ERROR("Failed to set decryption key");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return PackBundle::load(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint8_t> read(const std::string &path) const override {
|
||||||
|
auto data = PackBundle::read(path);
|
||||||
|
if (!data.empty() && cipher_.hasKey()) {
|
||||||
|
const_cast<AESCipher &>(cipher_).decryptInPlace(data);
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
AESCipher cipher_;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 资源服务实现
|
||||||
|
*/
|
||||||
|
class ResourceServiceImpl : public IResourceService {
|
||||||
|
public:
|
||||||
|
ResourceServiceImpl() = default;
|
||||||
|
~ResourceServiceImpl() override { shutdown(); }
|
||||||
|
|
||||||
|
bool initialize() override {
|
||||||
|
E2D_INFO("ResourceService initialized");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool init(const std::string &assetRoot) override {
|
||||||
|
assetRoot_ = assetRoot;
|
||||||
|
|
||||||
|
auto fileBundle = makePtr<FileBundle>(assetRoot);
|
||||||
|
if (!fileBundle->isValid()) {
|
||||||
|
E2D_WARN("Asset root path does not exist: {}", assetRoot);
|
||||||
|
}
|
||||||
|
|
||||||
|
bundles_.push_back(fileBundle);
|
||||||
|
E2D_INFO("ResourceService initialized with asset root: {}", assetRoot);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void shutdown() override {
|
||||||
|
clearAll();
|
||||||
|
bundles_.clear();
|
||||||
|
E2D_INFO("ResourceService shutdown");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool loadBundle(const std::string &path) override {
|
||||||
|
auto bundle = makePtr<PackBundle>();
|
||||||
|
if (!static_cast<PackBundle *>(bundle.get())->load(path)) {
|
||||||
|
E2D_ERROR("Failed to load bundle: {}", path);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bundles_.insert(bundles_.begin(), bundle);
|
||||||
|
E2D_INFO("Loaded bundle: {}", path);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void loadBundleAsync(const std::string &path,
|
||||||
|
std::function<void(bool)> callback) override {
|
||||||
|
std::thread([this, path, callback]() {
|
||||||
|
bool success = loadBundle(path);
|
||||||
|
if (callback) {
|
||||||
|
callback(success);
|
||||||
|
}
|
||||||
|
}).detach();
|
||||||
|
}
|
||||||
|
|
||||||
|
void unloadBundle(const std::string &path) override {
|
||||||
|
auto it = std::find_if(bundles_.begin(), bundles_.end(),
|
||||||
|
[&path](const Ptr<IAssetBundle> &bundle) {
|
||||||
|
return bundle->getName() == path;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (it != bundles_.end()) {
|
||||||
|
bundles_.erase(it);
|
||||||
|
E2D_INFO("Unloaded bundle: {}", path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void preload(const std::string &path) override { readData(path); }
|
||||||
|
|
||||||
|
void gc() override { clearUnused(); }
|
||||||
|
|
||||||
|
void clearUnused() override { E2D_DEBUG("Clearing unused resources"); }
|
||||||
|
|
||||||
|
void clearAll() override { E2D_DEBUG("Clearing all resources"); }
|
||||||
|
|
||||||
|
Stats getStats() const override { return stats_; }
|
||||||
|
|
||||||
|
bool exists(const std::string &path) const override {
|
||||||
|
for (const auto &bundle : bundles_) {
|
||||||
|
if (bundle->contains(path)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint8_t> readData(const std::string &path) const override {
|
||||||
|
for (const auto &bundle : bundles_) {
|
||||||
|
if (bundle->contains(path)) {
|
||||||
|
return bundle->read(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
E2D_WARN("Resource not found: {}", path);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string &getAssetRoot() const override { return assetRoot_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string assetRoot_;
|
||||||
|
std::vector<Ptr<IAssetBundle>> bundles_;
|
||||||
|
Stats stats_;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
ResourceHandle<T> IResourceService::load(const std::string &path) {
|
||||||
|
return ResourceHandle<T>();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void IResourceService::loadAsync(
|
||||||
|
const std::string &path, std::function<void(ResourceHandle<T>)> callback) {
|
||||||
|
if (callback) {
|
||||||
|
callback(ResourceHandle<T>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
E2D_AUTO_REGISTER_SERVICE(IResourceService, ResourceServiceImpl);
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -100,6 +100,12 @@ if target_plat ~= "switch" then
|
||||||
add_requires("glm")
|
add_requires("glm")
|
||||||
add_requires("nlohmann_json")
|
add_requires("nlohmann_json")
|
||||||
|
|
||||||
|
-- 压缩加密库
|
||||||
|
add_requires("zlib")
|
||||||
|
add_requires("lz4")
|
||||||
|
add_requires("zstd")
|
||||||
|
add_requires("openssl", {configs = {shared = false}})
|
||||||
|
|
||||||
-- 窗口后端依赖
|
-- 窗口后端依赖
|
||||||
local backend = get_config("window_backend") or "sdl2"
|
local backend = get_config("window_backend") or "sdl2"
|
||||||
if backend == "glfw" then
|
if backend == "glfw" then
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,15 @@ function define_extra2d_engine()
|
||||||
-- 平台配置
|
-- 平台配置
|
||||||
local plat = get_current_plat()
|
local plat = get_current_plat()
|
||||||
|
|
||||||
|
-- 通用依赖包
|
||||||
|
add_packages("glm", "nlohmann_json", {public = true})
|
||||||
|
|
||||||
|
-- simdutf 已包含在项目中
|
||||||
|
add_includedirs("Extra2D/include/simdutf", {public = true})
|
||||||
|
|
||||||
|
-- 压缩和加密库
|
||||||
|
add_packages("zlib", "lz4", "zstd", "openssl", {public = true})
|
||||||
|
|
||||||
if plat == "switch" then
|
if plat == "switch" then
|
||||||
-- Nintendo Switch 平台配置
|
-- Nintendo Switch 平台配置
|
||||||
local devkitPro = os.getenv("DEVKITPRO") or "C:/devkitPro"
|
local devkitPro = os.getenv("DEVKITPRO") or "C:/devkitPro"
|
||||||
|
|
@ -81,7 +90,6 @@ function define_extra2d_engine()
|
||||||
end
|
end
|
||||||
elseif plat == "mingw" or plat == "windows" then
|
elseif plat == "mingw" or plat == "windows" then
|
||||||
-- Windows (MinGW) 平台配置
|
-- Windows (MinGW) 平台配置
|
||||||
add_packages("glm", "nlohmann_json", {public = true})
|
|
||||||
local backend = get_window_backend()
|
local backend = get_window_backend()
|
||||||
if backend == "glfw" then
|
if backend == "glfw" then
|
||||||
add_packages("glfw", {public = true})
|
add_packages("glfw", {public = true})
|
||||||
|
|
@ -92,7 +100,6 @@ function define_extra2d_engine()
|
||||||
{public = true})
|
{public = true})
|
||||||
elseif plat == "linux" then
|
elseif plat == "linux" then
|
||||||
-- Linux 平台配置
|
-- Linux 平台配置
|
||||||
add_packages("glm", "nlohmann_json", {public = true})
|
|
||||||
local backend = get_window_backend()
|
local backend = get_window_backend()
|
||||||
if backend == "glfw" then
|
if backend == "glfw" then
|
||||||
add_packages("glfw", {public = true})
|
add_packages("glfw", {public = true})
|
||||||
|
|
@ -102,7 +109,6 @@ function define_extra2d_engine()
|
||||||
add_syslinks("GL", "dl", "pthread", {public = true})
|
add_syslinks("GL", "dl", "pthread", {public = true})
|
||||||
elseif plat == "macosx" then
|
elseif plat == "macosx" then
|
||||||
-- macOS 平台配置
|
-- macOS 平台配置
|
||||||
add_packages("glm", "nlohmann_json", {public = true})
|
|
||||||
local backend = get_window_backend()
|
local backend = get_window_backend()
|
||||||
if backend == "glfw" then
|
if backend == "glfw" then
|
||||||
add_packages("glfw", {public = true})
|
add_packages("glfw", {public = true})
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue