Initial engine repository
This commit is contained in:
@@ -0,0 +1,397 @@
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <frostbite2D/types/type_alias.h>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace frostbite2D {
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
/**
|
||||
* @brief 文件信息结构体
|
||||
*/
|
||||
struct FileInfo {
|
||||
std::string name; ///< 文件名(包含扩展名)
|
||||
std::string extension; ///< 文件扩展名(包含点,如 ".txt")
|
||||
std::string fullPath; ///< 完整路径
|
||||
uint64 size = 0; ///< 文件大小(字节)
|
||||
bool isDirectory = false; ///< 是否为目录
|
||||
bool isRegularFile = false; ///< 是否为普通文件
|
||||
bool exists = false; ///< 是否存在
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 资源管理类(单例)
|
||||
*
|
||||
* 提供文件读写、目录操作、路径处理等功能。
|
||||
* 支持设置工作目录,所有相对路径将基于工作目录解析。
|
||||
* 支持 UTF-8 编码的中文路径。
|
||||
*
|
||||
* @example
|
||||
* auto& asset = Asset::get();
|
||||
* asset.setWorkingDirectory("D:/游戏/资源");
|
||||
* std::string content;
|
||||
* asset.readTextFile("配置/设置.json", content);
|
||||
*/
|
||||
class Asset {
|
||||
public:
|
||||
/**
|
||||
* @brief 获取单例实例
|
||||
* @return 资源管理器实例引用
|
||||
*/
|
||||
static Asset &get();
|
||||
|
||||
Asset(const Asset &) = delete;
|
||||
Asset &operator=(const Asset &) = delete;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 文件读写
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 读取文本文件
|
||||
* @param path 文件路径(相对或绝对路径)
|
||||
* @param outContent 输出文件内容
|
||||
* @return 读取成功返回 true
|
||||
*/
|
||||
bool readTextFile(const std::string &path, std::string &outContent);
|
||||
|
||||
/**
|
||||
* @brief 写入文本文件
|
||||
* @param path 文件路径
|
||||
* @param content 要写入的内容
|
||||
* @param append 是否追加模式,默认覆盖
|
||||
* @return 写入成功返回 true
|
||||
*/
|
||||
bool writeTextFile(const std::string &path, const std::string &content,
|
||||
bool append = false);
|
||||
|
||||
/**
|
||||
* @brief 读取二进制文件
|
||||
* @param path 文件路径
|
||||
* @param outData 输出字节数组
|
||||
* @return 读取成功返回 true
|
||||
*/
|
||||
bool readBinaryFile(const std::string &path, std::vector<uint8> &outData);
|
||||
|
||||
/**
|
||||
* @brief 写入二进制文件
|
||||
* @param path 文件路径
|
||||
* @param data 要写入的字节数组
|
||||
* @param append 是否追加模式,默认覆盖
|
||||
* @return 写入成功返回 true
|
||||
*/
|
||||
bool writeBinaryFile(const std::string &path, const std::vector<uint8> &data,
|
||||
bool append = false);
|
||||
|
||||
/**
|
||||
* @brief 读取文本文件到字符串(便捷方法)
|
||||
* @param path 文件路径
|
||||
* @return 文件内容,失败返回 std::nullopt
|
||||
*/
|
||||
std::optional<std::string> readFileToString(const std::string &path);
|
||||
|
||||
/**
|
||||
* @brief 读取二进制文件到字节数组(便捷方法)
|
||||
* @param path 文件路径
|
||||
* @return 字节数组,失败返回 std::nullopt
|
||||
*/
|
||||
std::optional<std::vector<uint8>> readFileToBytes(const std::string &path);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 文件/目录检查
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 检查路径是否存在
|
||||
* @param path 路径
|
||||
* @return 存在返回 true
|
||||
*/
|
||||
bool exists(const std::string &path) const;
|
||||
|
||||
/**
|
||||
* @brief 检查路径是否为目录
|
||||
* @param path 路径
|
||||
* @return 是目录返回 true
|
||||
*/
|
||||
bool isDirectory(const std::string &path) const;
|
||||
|
||||
/**
|
||||
* @brief 检查路径是否为普通文件
|
||||
* @param path 路径
|
||||
* @return 是普通文件返回 true
|
||||
*/
|
||||
bool isRegularFile(const std::string &path) const;
|
||||
|
||||
/**
|
||||
* @brief 检查文件或目录是否为空
|
||||
* @param path 路径
|
||||
* @return 为空返回 true
|
||||
*/
|
||||
bool isEmpty(const std::string &path) const;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 目录操作
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 创建单层目录
|
||||
* @param path 目录路径
|
||||
* @return 创建成功返回 true
|
||||
*/
|
||||
bool createDirectory(const std::string &path);
|
||||
|
||||
/**
|
||||
* @brief 递归创建多层目录
|
||||
* @param path 目录路径
|
||||
* @return 创建成功返回 true
|
||||
*/
|
||||
bool createDirectories(const std::string &path);
|
||||
|
||||
/**
|
||||
* @brief 删除文件
|
||||
* @param path 文件路径
|
||||
* @return 删除成功返回 true
|
||||
*/
|
||||
bool removeFile(const std::string &path);
|
||||
|
||||
/**
|
||||
* @brief 删除目录及其所有内容
|
||||
* @param path 目录路径
|
||||
* @return 删除成功返回 true
|
||||
*/
|
||||
bool removeDirectory(const std::string &path);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 文件操作
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 复制文件
|
||||
* @param from 源文件路径
|
||||
* @param to 目标文件路径
|
||||
* @param overwrite 是否覆盖已存在的文件
|
||||
* @return 复制成功返回 true
|
||||
*/
|
||||
bool copyFile(const std::string &from, const std::string &to,
|
||||
bool overwrite = false);
|
||||
|
||||
/**
|
||||
* @brief 移动文件
|
||||
* @param from 源文件路径
|
||||
* @param to 目标文件路径
|
||||
* @return 移动成功返回 true
|
||||
*/
|
||||
bool moveFile(const std::string &from, const std::string &to);
|
||||
|
||||
/**
|
||||
* @brief 重命名文件或目录
|
||||
* @param oldPath 原路径
|
||||
* @param newPath 新路径
|
||||
* @return 重命名成功返回 true
|
||||
*/
|
||||
bool rename(const std::string &oldPath, const std::string &newPath);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 目录遍历
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 列出目录中的所有文件
|
||||
* @param directoryPath 目录路径
|
||||
* @param recursive 是否递归遍历子目录
|
||||
* @return 文件路径列表
|
||||
*/
|
||||
std::vector<std::string> listFiles(const std::string &directoryPath,
|
||||
bool recursive = false);
|
||||
|
||||
/**
|
||||
* @brief 列出目录中的所有子目录
|
||||
* @param directoryPath 目录路径
|
||||
* @param recursive 是否递归遍历子目录
|
||||
* @return 子目录路径列表
|
||||
*/
|
||||
std::vector<std::string> listDirectories(const std::string &directoryPath,
|
||||
bool recursive = false);
|
||||
|
||||
/**
|
||||
* @brief 列出目录中的所有文件和子目录
|
||||
* @param directoryPath 目录路径
|
||||
* @param recursive 是否递归遍历子目录
|
||||
* @return 所有项目路径列表
|
||||
*/
|
||||
std::vector<std::string> listAll(const std::string &directoryPath,
|
||||
bool recursive = false);
|
||||
|
||||
/**
|
||||
* @brief 按扩展名列出文件
|
||||
* @param directoryPath 目录路径
|
||||
* @param extension 文件扩展名(如 ".png")
|
||||
* @param recursive 是否递归遍历子目录
|
||||
* @return 匹配的文件路径列表
|
||||
*/
|
||||
std::vector<std::string>
|
||||
listFilesWithExtension(const std::string &directoryPath,
|
||||
const std::string &extension, bool recursive = false);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 文件信息
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 获取文件详细信息
|
||||
* @param path 文件路径
|
||||
* @return 文件信息结构体
|
||||
*/
|
||||
FileInfo getFileInfo(const std::string &path);
|
||||
|
||||
/**
|
||||
* @brief 获取文件大小
|
||||
* @param path 文件路径
|
||||
* @return 文件大小(字节)
|
||||
*/
|
||||
uint64 getFileSize(const std::string &path) const;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 路径处理
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 获取文件名(包含扩展名)
|
||||
* @param path 文件路径
|
||||
* @return 文件名
|
||||
*/
|
||||
std::string getFileName(const std::string &path) const;
|
||||
|
||||
/**
|
||||
* @brief 获取文件名(不含扩展名)
|
||||
* @param path 文件路径
|
||||
* @return 文件名
|
||||
*/
|
||||
std::string getFileNameWithoutExtension(const std::string &path) const;
|
||||
|
||||
/**
|
||||
* @brief 获取文件扩展名
|
||||
* @param path 文件路径
|
||||
* @return 扩展名(包含点,如 ".txt")
|
||||
*/
|
||||
std::string getExtension(const std::string &path) const;
|
||||
|
||||
/**
|
||||
* @brief 获取父目录路径
|
||||
* @param path 文件路径
|
||||
* @return 父目录路径
|
||||
*/
|
||||
std::string getParentPath(const std::string &path) const;
|
||||
|
||||
/**
|
||||
* @brief 获取绝对路径
|
||||
* @param path 相对或绝对路径
|
||||
* @return 绝对路径
|
||||
*/
|
||||
std::string getAbsolutePath(const std::string &path) const;
|
||||
|
||||
/**
|
||||
* @brief 获取规范路径(解析符号链接和相对路径)
|
||||
* @param path 路径
|
||||
* @return 规范化路径
|
||||
*/
|
||||
std::string getCanonicalPath(const std::string &path) const;
|
||||
|
||||
/**
|
||||
* @brief 合并两个路径
|
||||
* @param left 左侧路径
|
||||
* @param right 右侧路径
|
||||
* @return 合并后的路径
|
||||
*/
|
||||
std::string combinePath(const std::string &left,
|
||||
const std::string &right) const;
|
||||
|
||||
/**
|
||||
* @brief 规范化路径(转换为系统首选格式)
|
||||
* @param path 路径
|
||||
* @return 规范化路径
|
||||
*/
|
||||
std::string normalizePath(const std::string &path) const;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 工作目录
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 获取当前工作目录
|
||||
* @return 当前工作目录路径
|
||||
*/
|
||||
std::string getCurrentPath() const;
|
||||
|
||||
/**
|
||||
* @brief 设置当前工作目录
|
||||
* @param path 目录路径
|
||||
* @return 设置成功返回 true
|
||||
*/
|
||||
bool setCurrentPath(const std::string &path);
|
||||
|
||||
/**
|
||||
* @brief 设置资源工作目录
|
||||
*
|
||||
* 设置后,所有相对路径操作都将基于此目录。
|
||||
*
|
||||
* @param path 工作目录路径
|
||||
*/
|
||||
void setWorkingDirectory(const std::string &path);
|
||||
|
||||
/**
|
||||
* @brief 获取资源工作目录
|
||||
* @return 工作目录路径
|
||||
*/
|
||||
const std::string &getWorkingDirectory() const;
|
||||
|
||||
/**
|
||||
* @brief 解析相对路径为完整路径
|
||||
* @param relativePath 相对路径
|
||||
* @return 完整路径
|
||||
*/
|
||||
std::string resolvePath(const std::string &relativePath) const;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 资源根目录
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 设置资源根目录
|
||||
* @param root 资源根目录路径
|
||||
*/
|
||||
void setAssetRoot(const std::string &root);
|
||||
|
||||
/**
|
||||
* @brief 获取资源根目录
|
||||
* @return 资源根目录路径
|
||||
*/
|
||||
const std::string &getAssetRoot() const;
|
||||
|
||||
/**
|
||||
* @brief 解析资源相对路径
|
||||
*
|
||||
* 将相对路径解析为:工作目录/资源根目录/相对路径
|
||||
*
|
||||
* @param relativePath 相对路径
|
||||
* @return 完整路径
|
||||
*/
|
||||
std::string resolveAssetPath(const std::string &relativePath) const;
|
||||
|
||||
private:
|
||||
Asset() = default;
|
||||
~Asset() = default;
|
||||
|
||||
fs::path toPath(const std::string &path) const;
|
||||
std::string fromPath(const fs::path &path) const;
|
||||
std::string resolveFullPath(const std::string &path) const;
|
||||
|
||||
std::string workingDirectory_;
|
||||
std::string assetRoot_;
|
||||
};
|
||||
|
||||
} // namespace frostbite2D
|
||||
@@ -0,0 +1,353 @@
|
||||
#pragma once
|
||||
|
||||
#include <frostbite2D/types/type_alias.h>
|
||||
#include <optional>
|
||||
#include <random>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace frostbite2D {
|
||||
|
||||
/**
|
||||
* @brief 音频项类型枚举
|
||||
*/
|
||||
enum class AudioEntryType {
|
||||
None, ///< invalid
|
||||
Effect, ///< one-shot sound effect
|
||||
Ambient, ///< looping ambient track
|
||||
Music, ///< background music
|
||||
Random ///< random sound group
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 音效配置
|
||||
*/
|
||||
struct AudioEffect {
|
||||
std::string id; ///< 音效 ID
|
||||
std::string file; ///< 文件路径
|
||||
std::string subgroup; ///< 子组(可选)
|
||||
int32 loopDelay = -1; ///< 循环延迟(-1 表示未设置)
|
||||
int32 loopDelayRange = -1;///< 循环延迟范围(-1 表示未设置)
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 音乐配置
|
||||
*/
|
||||
struct AudioMusic {
|
||||
std::string id; ///< 音乐 ID
|
||||
std::string file; ///< 文件路径
|
||||
int32 loopDelay = -1; ///< 循环延迟(-1 表示未设置)
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief ?????????
|
||||
*/
|
||||
struct AudioAmbient {
|
||||
std::string id; ///< ?????ID
|
||||
std::string file; ///< ??????
|
||||
int32 loopDelay = -1; ///< ????????1 ?????????
|
||||
int32 loopDelayRange = -1;///< ???????????1 ?????????
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 随机音效项
|
||||
*/
|
||||
struct RandomItem {
|
||||
std::string tag; ///< 音效标签(ID)
|
||||
int32 probability = 0; ///< 概率权重
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 随机音效组
|
||||
*/
|
||||
struct AudioRandom {
|
||||
std::string id; ///< 随机组 ID
|
||||
std::vector<RandomItem> items; ///< 随机项列表
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 统一的音频条目(包含类型和数据)
|
||||
*/
|
||||
struct AudioEntry {
|
||||
AudioEntryType type = AudioEntryType::None; ///< 条目类型
|
||||
|
||||
const AudioEffect* effect = nullptr; ///< 音效指针(type == Effect 时有效)
|
||||
const AudioAmbient* ambient = nullptr;///< ???????????ype == Ambient ??????
|
||||
const AudioMusic* music = nullptr; ///< 音乐指针(type == Music 时有效)
|
||||
const AudioRandom* random = nullptr; ///< 随机组指针(type == Random 时有效)
|
||||
|
||||
/// @brief 判断是否有效
|
||||
bool isValid() const { return type != AudioEntryType::None; }
|
||||
|
||||
/// @brief 判断是否为音效
|
||||
bool isEffect() const { return type == AudioEntryType::Effect; }
|
||||
|
||||
/// @brief ????????????
|
||||
bool isAmbient() const { return type == AudioEntryType::Ambient; }
|
||||
|
||||
/// @brief 判断是否为音乐
|
||||
bool isMusic() const { return type == AudioEntryType::Music; }
|
||||
|
||||
/// @brief 判断是否为随机组
|
||||
bool isRandom() const { return type == AudioEntryType::Random; }
|
||||
|
||||
/// @brief 安全获取文件路径
|
||||
std::string getFilePath() const {
|
||||
if (type == AudioEntryType::Effect && effect)
|
||||
return effect->file;
|
||||
if (type == AudioEntryType::Ambient && ambient)
|
||||
return ambient->file;
|
||||
if (type == AudioEntryType::Music && music)
|
||||
return music->file;
|
||||
return "";
|
||||
}
|
||||
|
||||
/// @brief 安全获取子组
|
||||
std::string getSubgroup() const {
|
||||
if (type == AudioEntryType::Effect && effect)
|
||||
return effect->subgroup;
|
||||
return "";
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 音频数据库(单例)
|
||||
*
|
||||
* 用于解析和访问 audio.xml 文件中的音频配置。
|
||||
* 提供统一的查询接口,自动判断音频类型。
|
||||
*
|
||||
* @example
|
||||
* auto& audioDB = AudioDatabase::get();
|
||||
* if (audioDB.loadFromFile("audio.xml")) {
|
||||
* // 统一查询
|
||||
* if (auto entry = audioDB.get("GN_FINAL_SHOT_01")) {
|
||||
* // 使用 entry
|
||||
* }
|
||||
*
|
||||
* // 获取文件路径
|
||||
* if (auto filePath = audioDB.getFilePath("GN_FINAL_SHOT_01")) {
|
||||
* // 使用 *filePath
|
||||
* }
|
||||
*
|
||||
* // 获取音效(自动处理随机组)
|
||||
* if (auto soundId = audioDB.getSound("R_GN_FINAL_SHOT")) {
|
||||
* // 使用 *soundId
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
class AudioDatabase {
|
||||
public:
|
||||
/**
|
||||
* @brief 获取单例实例
|
||||
* @return 音频数据库实例引用
|
||||
*/
|
||||
static AudioDatabase& get();
|
||||
|
||||
AudioDatabase(const AudioDatabase&) = delete;
|
||||
AudioDatabase& operator=(const AudioDatabase&) = delete;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 加载和解析
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 从文件加载音频数据库
|
||||
* @param path XML 文件路径
|
||||
* @return 加载成功返回 true
|
||||
*/
|
||||
bool loadFromFile(const std::string& path);
|
||||
|
||||
/**
|
||||
* @brief 从字符串加载音频数据库
|
||||
* @param xmlContent XML 内容字符串
|
||||
* @return 加载成功返回 true
|
||||
*/
|
||||
bool loadFromString(const std::string& xmlContent);
|
||||
|
||||
/**
|
||||
* @brief 清空数据库
|
||||
*/
|
||||
void clear();
|
||||
|
||||
/**
|
||||
* @brief 检查数据库是否已加载
|
||||
* @return 已加载返回 true
|
||||
*/
|
||||
bool isLoaded() const;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 统一查询接口
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 统一查询:输入任意 ID,自动判断类型
|
||||
* @param id 音频 ID(可以是 EFFECT、MUSIC 或 RANDOM 的 ID)
|
||||
* @return 音频条目,未找到返回 std::nullopt
|
||||
*/
|
||||
std::optional<AudioEntry> get(const std::string& id);
|
||||
|
||||
/**
|
||||
* @brief 获取音效 ID(自动处理 RANDOM 类型)
|
||||
*
|
||||
* 如果 id 是 RANDOM 类型,会按概率随机选择一个音效;
|
||||
* 如果 id 是 EFFECT 类型,直接返回该 ID。
|
||||
*
|
||||
* @param id 音效 ID 或随机组 ID
|
||||
* @return 音效 ID,未找到返回 std::nullopt
|
||||
*/
|
||||
std::optional<std::string> getSound(const std::string& id);
|
||||
|
||||
/**
|
||||
* @brief 获取文件路径
|
||||
*
|
||||
* 对于 EFFECT 和 MUSIC 类型,返回对应的文件路径;
|
||||
* 对于 RANDOM 类型,先随机选择一个音效再返回其文件路径。
|
||||
*
|
||||
* @param id 音频 ID
|
||||
* @return 文件路径,未找到返回 std::nullopt
|
||||
*/
|
||||
std::optional<std::string> getFilePath(const std::string& id);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 类型特定查询(高级用法)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 获取音效配置
|
||||
* @param id 音效 ID
|
||||
* @return 音效配置指针,未找到返回 std::nullopt
|
||||
*/
|
||||
std::optional<const AudioEffect*> getEffect(const std::string& id);
|
||||
|
||||
/**
|
||||
* @brief ????????????
|
||||
* @param id ?????ID
|
||||
* @return ????????????????????? std::nullopt
|
||||
*/
|
||||
std::optional<const AudioAmbient*> getAmbient(const std::string& id);
|
||||
|
||||
/**
|
||||
* @brief 获取音乐配置
|
||||
* @param id 音乐 ID
|
||||
* @return 音乐配置指针,未找到返回 std::nullopt
|
||||
*/
|
||||
std::optional<const AudioMusic*> getMusic(const std::string& id);
|
||||
|
||||
/**
|
||||
* @brief 获取随机音效组配置
|
||||
* @param id 随机组 ID
|
||||
* @return 随机组配置指针,未找到返回 std::nullopt
|
||||
*/
|
||||
std::optional<const AudioRandom*> getRandom(const std::string& id);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 统计信息
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 获取音效数量
|
||||
*/
|
||||
size_t effectCount() const;
|
||||
|
||||
/**
|
||||
* @brief ???????????
|
||||
*/
|
||||
size_t ambientCount() const;
|
||||
|
||||
/**
|
||||
* @brief 获取音乐数量
|
||||
*/
|
||||
size_t musicCount() const;
|
||||
|
||||
/**
|
||||
* @brief 获取随机组数量
|
||||
*/
|
||||
size_t randomCount() const;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 便捷访问方法(推荐使用)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 检查 ID 是否存在
|
||||
* @param id 音频 ID
|
||||
* @return 存在返回 true
|
||||
*/
|
||||
bool has(const std::string& id);
|
||||
|
||||
/**
|
||||
* @brief 获取文件路径(不存在返回空字符串)
|
||||
* @param id 音频 ID
|
||||
* @return 文件路径,未找到返回空字符串
|
||||
*/
|
||||
std::string filePath(const std::string& id);
|
||||
|
||||
/**
|
||||
* @brief 获取音效 ID(自动处理随机组,不存在返回空字符串)
|
||||
* @param idOrRandomId 音效 ID 或随机组 ID
|
||||
* @return 音效 ID,未找到返回空字符串
|
||||
*/
|
||||
std::string soundId(const std::string& idOrRandomId);
|
||||
|
||||
/**
|
||||
* @brief 获取子组(EFFECT 的 SUBGROUP 属性)
|
||||
* @param id 音频 ID
|
||||
* @return 子组名,未找到或不存在返回空字符串
|
||||
*/
|
||||
std::string subgroup(const std::string& id);
|
||||
|
||||
/**
|
||||
* @brief 获取条目类型
|
||||
* @param id 音频 ID
|
||||
* @return 条目类型,未找到返回 AudioEntryType::None
|
||||
*/
|
||||
AudioEntryType typeOf(const std::string& id);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 带检查的访问方法
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 尝试获取文件路径,带成功标志
|
||||
* @param id 音频 ID
|
||||
* @param outPath 输出文件路径
|
||||
* @return 成功返回 true
|
||||
*/
|
||||
bool tryGetFilePath(const std::string& id, std::string& outPath);
|
||||
|
||||
/**
|
||||
* @brief 尝试获取音效 ID,带成功标志
|
||||
* @param id 音效 ID 或随机组 ID
|
||||
* @param outSoundId 输出音效 ID
|
||||
* @return 成功返回 true
|
||||
*/
|
||||
bool tryGetSoundId(const std::string& id, std::string& outSoundId);
|
||||
|
||||
private:
|
||||
AudioDatabase() = default;
|
||||
~AudioDatabase() = default;
|
||||
|
||||
/**
|
||||
* @brief 初始化随机数生成器
|
||||
*/
|
||||
void initializeRNG();
|
||||
|
||||
/**
|
||||
* @brief 从随机组中按概率选择一个音效
|
||||
* @param randomGroup 随机组配置
|
||||
* @return 选中的音效 ID
|
||||
*/
|
||||
std::string selectFromRandom(const AudioRandom& randomGroup);
|
||||
|
||||
std::unordered_map<std::string, AudioEffect> effectMap_; ///< 音效映射
|
||||
std::unordered_map<std::string, AudioAmbient> ambientMap_; ///< ????????
|
||||
std::unordered_map<std::string, AudioMusic> musicMap_; ///< 音乐映射
|
||||
std::unordered_map<std::string, AudioRandom> randomMap_; ///< 随机组映射
|
||||
|
||||
std::mt19937 rng_; ///< 随机数生成器
|
||||
bool rngInitialized_ = false; ///< 随机数生成器是否已初始化
|
||||
bool loaded_ = false;
|
||||
};
|
||||
|
||||
} // namespace frostbite2D
|
||||
@@ -0,0 +1,62 @@
|
||||
#pragma once
|
||||
|
||||
#include <frostbite2D/types/type_alias.h>
|
||||
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace frostbite2D {
|
||||
|
||||
class BinaryFileStreamReader {
|
||||
public:
|
||||
BinaryFileStreamReader() = default;
|
||||
explicit BinaryFileStreamReader(const std::string& filePath);
|
||||
~BinaryFileStreamReader();
|
||||
|
||||
bool open(const std::string& filePath);
|
||||
void close();
|
||||
bool isOpen() const;
|
||||
|
||||
size_t tell() const;
|
||||
void seek(size_t pos);
|
||||
void skip(size_t count);
|
||||
bool eof() const;
|
||||
|
||||
size_t size() const;
|
||||
size_t remaining() const;
|
||||
size_t lastReadCount() const;
|
||||
|
||||
size_t read(char* buffer, size_t size);
|
||||
std::vector<uint8> readBytes(size_t size);
|
||||
|
||||
template <typename T>
|
||||
T read() {
|
||||
T value{};
|
||||
read(reinterpret_cast<char*>(&value), sizeof(T));
|
||||
return value;
|
||||
}
|
||||
|
||||
int8 readInt8();
|
||||
int16 readInt16();
|
||||
int32 readInt32();
|
||||
int64 readInt64();
|
||||
uint8 readUInt8();
|
||||
uint16 readUInt16();
|
||||
uint32 readUInt32();
|
||||
uint64 readUInt64();
|
||||
float readFloat();
|
||||
double readDouble();
|
||||
|
||||
std::string readString(size_t length);
|
||||
std::string readNullTerminatedString(size_t maxLength = 4096);
|
||||
|
||||
private:
|
||||
std::ifstream stream_;
|
||||
std::string filePath_;
|
||||
size_t size_ = 0;
|
||||
size_t position_ = 0;
|
||||
size_t lastReadCount_ = 0;
|
||||
};
|
||||
|
||||
} // namespace frostbite2D
|
||||
@@ -0,0 +1,269 @@
|
||||
#pragma once
|
||||
|
||||
#include <frostbite2D/types/type_alias.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <cstring>
|
||||
#include <algorithm>
|
||||
|
||||
namespace frostbite2D {
|
||||
|
||||
/**
|
||||
* @brief 二进制文件读取器
|
||||
*
|
||||
* 将整个二进制文件加载到内存中,提供便捷的读取接口。
|
||||
* 支持各种基本类型的读取、定位操作和 CRC 解码。
|
||||
*
|
||||
* @example
|
||||
* BinaryReader reader("data.bin");
|
||||
* if (reader.isOpen()) {
|
||||
* int32 value = reader.read<int32>();
|
||||
* std::string str = reader.readString(10);
|
||||
* }
|
||||
*/
|
||||
class BinaryReader {
|
||||
public:
|
||||
/**
|
||||
* @brief 默认构造函数
|
||||
*/
|
||||
BinaryReader() = default;
|
||||
|
||||
/**
|
||||
* @brief 从文件构造
|
||||
* @param filePath 文件路径
|
||||
*/
|
||||
explicit BinaryReader(const std::string& filePath);
|
||||
|
||||
/**
|
||||
* @brief 从内存数据构造
|
||||
* @param data 数据指针
|
||||
* @param size 数据大小
|
||||
*/
|
||||
BinaryReader(const char* data, size_t size);
|
||||
|
||||
/**
|
||||
* @brief 析构函数
|
||||
*/
|
||||
~BinaryReader() = default;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 文件操作
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 打开文件
|
||||
* @param filePath 文件路径
|
||||
* @return 打开成功返回 true
|
||||
*/
|
||||
bool open(const std::string& filePath);
|
||||
|
||||
/**
|
||||
* @brief 从内存加载数据
|
||||
* @param data 数据指针
|
||||
* @param size 数据大小
|
||||
*/
|
||||
void loadFromMemory(const char* data, size_t size);
|
||||
|
||||
/**
|
||||
* @brief 关闭并清空数据
|
||||
*/
|
||||
void close();
|
||||
|
||||
/**
|
||||
* @brief 检查文件是否成功打开
|
||||
* @return 已打开返回 true
|
||||
*/
|
||||
bool isOpen() const;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 位置操作
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 获取当前读取位置
|
||||
* @return 当前位置(字节偏移)
|
||||
*/
|
||||
size_t tell() const;
|
||||
|
||||
/**
|
||||
* @brief 设置读取位置
|
||||
* @param pos 目标位置(字节偏移)
|
||||
*/
|
||||
void seek(size_t pos);
|
||||
|
||||
/**
|
||||
* @brief 跳过指定字节数
|
||||
* @param count 要跳过的字节数
|
||||
*/
|
||||
void skip(size_t count);
|
||||
|
||||
/**
|
||||
* @brief 检查是否已到达文件末尾
|
||||
* @return 到达末尾返回 true
|
||||
*/
|
||||
bool eof() const;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 信息获取
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 获取数据总大小
|
||||
* @return 数据大小(字节)
|
||||
*/
|
||||
size_t size() const;
|
||||
|
||||
/**
|
||||
* @brief 获取剩余可读取的字节数
|
||||
* @return 剩余字节数
|
||||
*/
|
||||
size_t remaining() const;
|
||||
|
||||
/**
|
||||
* @brief 获取上一次读取的字节数
|
||||
* @return 上次读取的字节数
|
||||
*/
|
||||
size_t lastReadCount() const;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 原始数据读取
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 读取原始字节数据
|
||||
* @param buffer 输出缓冲区
|
||||
* @param size 要读取的字节数
|
||||
* @return 实际读取的字节数
|
||||
*/
|
||||
size_t read(char* buffer, size_t size);
|
||||
|
||||
/**
|
||||
* @brief 读取原始字节数据到 vector
|
||||
* @param size 要读取的字节数
|
||||
* @return 读取的数据
|
||||
*/
|
||||
std::vector<uint8> readBytes(size_t size);
|
||||
|
||||
/**
|
||||
* @brief 获取数据指针(不移动读取位置)
|
||||
* @return 当前位置的数据指针
|
||||
*/
|
||||
const char* data() const;
|
||||
|
||||
/**
|
||||
* @brief 获取当前位置的数据指针(不移动读取位置)
|
||||
* @return 当前位置的数据指针
|
||||
*/
|
||||
const char* currentData() const;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 类型化读取(模板方法)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 读取指定类型的值
|
||||
* @tparam T 要读取的类型
|
||||
* @return 读取的值
|
||||
*/
|
||||
template <typename T>
|
||||
T read() {
|
||||
T value;
|
||||
read(reinterpret_cast<char*>(&value), sizeof(T));
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 读取 int8
|
||||
* @return 读取的值
|
||||
*/
|
||||
int8 readInt8();
|
||||
|
||||
/**
|
||||
* @brief 读取 int16
|
||||
* @return 读取的值
|
||||
*/
|
||||
int16 readInt16();
|
||||
|
||||
/**
|
||||
* @brief 读取 int32
|
||||
* @return 读取的值
|
||||
*/
|
||||
int32 readInt32();
|
||||
|
||||
/**
|
||||
* @brief 读取 int64
|
||||
* @return 读取的值
|
||||
*/
|
||||
int64 readInt64();
|
||||
|
||||
/**
|
||||
* @brief 读取 uint8
|
||||
* @return 读取的值
|
||||
*/
|
||||
uint8 readUInt8();
|
||||
|
||||
/**
|
||||
* @brief 读取 uint16
|
||||
* @return 读取的值
|
||||
*/
|
||||
uint16 readUInt16();
|
||||
|
||||
/**
|
||||
* @brief 读取 uint32
|
||||
* @return 读取的值
|
||||
*/
|
||||
uint32 readUInt32();
|
||||
|
||||
/**
|
||||
* @brief 读取 uint64
|
||||
* @return 读取的值
|
||||
*/
|
||||
uint64 readUInt64();
|
||||
|
||||
/**
|
||||
* @brief 读取 float
|
||||
* @return 读取的值
|
||||
*/
|
||||
float readFloat();
|
||||
|
||||
/**
|
||||
* @brief 读取 double
|
||||
* @return 读取的值
|
||||
*/
|
||||
double readDouble();
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 字符串读取
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 读取指定长度的字符串
|
||||
* @param length 字符串长度(字节)
|
||||
* @return 读取的字符串
|
||||
*/
|
||||
std::string readString(size_t length);
|
||||
|
||||
/**
|
||||
* @brief 读取以 null 结尾的字符串
|
||||
* @return 读取的字符串
|
||||
*/
|
||||
std::string readNullTerminatedString();
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// CRC 解码
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief CRC 解码
|
||||
* @param length 要解码的数据长度(字节)
|
||||
* @param crc32 CRC32 值
|
||||
*/
|
||||
void crcDecode(size_t length, uint32 crc32);
|
||||
|
||||
private:
|
||||
std::vector<char> data_; ///< 存储的数据
|
||||
size_t position_ = 0; ///< 当前读取位置
|
||||
size_t lastReadCount_ = 0; ///< 上一次读取的字节数
|
||||
};
|
||||
|
||||
} // namespace frostbite2D
|
||||
@@ -0,0 +1,131 @@
|
||||
#pragma once
|
||||
|
||||
#include <frostbite2D/types/type_alias.h>
|
||||
#include <frostbite2D/resource/asset.h>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <list>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
#include <zlib.h>
|
||||
|
||||
namespace frostbite2D {
|
||||
|
||||
class BinaryFileStreamReader;
|
||||
|
||||
struct ImageFrame {
|
||||
int32 type = 0;
|
||||
int32 compressionType = 0;
|
||||
int32 width = 0;
|
||||
int32 height = 0;
|
||||
int32 xPos = 0;
|
||||
int32 yPos = 0;
|
||||
int32 frameXPos = 0;
|
||||
int32 frameYPos = 0;
|
||||
uint32 offset = 0;
|
||||
int32 size = 0;
|
||||
std::vector<uint8> data;
|
||||
};
|
||||
|
||||
struct ImgRef {
|
||||
std::string path;
|
||||
std::string npkFile;
|
||||
size_t frameCount = 0;
|
||||
uint32 offset = 0;
|
||||
uint32 size = 0;
|
||||
bool loaded = false;
|
||||
};
|
||||
|
||||
struct CachedImageData {
|
||||
std::vector<ImageFrame> frames;
|
||||
uint64 lastUseTime = 0;
|
||||
size_t memoryUsage = 0;
|
||||
};
|
||||
|
||||
class NpkArchive {
|
||||
public:
|
||||
static NpkArchive& get();
|
||||
|
||||
NpkArchive(const NpkArchive&) = delete;
|
||||
NpkArchive& operator=(const NpkArchive&) = delete;
|
||||
NpkArchive(NpkArchive&&) = delete;
|
||||
NpkArchive& operator=(NpkArchive&&) = delete;
|
||||
|
||||
void setImagePackDirectory(const std::string& dir);
|
||||
const std::string& getImagePackDirectory() const;
|
||||
|
||||
void init();
|
||||
void close();
|
||||
bool isOpen() const;
|
||||
|
||||
bool hasImg(const std::string& path) const;
|
||||
std::optional<ImgRef> getImg(const std::string& path);
|
||||
std::vector<std::string> listImgs() const;
|
||||
|
||||
std::optional<ImageFrame> getImageFrame(const ImgRef& img, size_t index);
|
||||
size_t getFrameCount(const ImgRef& img) const;
|
||||
|
||||
void setCacheSize(size_t maxBytes);
|
||||
void clearCache();
|
||||
size_t getCacheUsage() const;
|
||||
|
||||
void setDefaultImg(const std::string& imgPath, size_t frameIndex = 0);
|
||||
const std::string& getDefaultImgPath() const;
|
||||
size_t getDefaultImgFrame() const;
|
||||
|
||||
private:
|
||||
enum class IndexCacheValidationMode : uint32 {
|
||||
TrustCache = 1,
|
||||
StrictSourceState = 2
|
||||
};
|
||||
|
||||
struct SourceFileState {
|
||||
std::string fileName;
|
||||
uint64 size = 0;
|
||||
int64 writeTime = 0;
|
||||
|
||||
bool operator==(const SourceFileState& other) const {
|
||||
return fileName == other.fileName && size == other.size &&
|
||||
writeTime == other.writeTime;
|
||||
}
|
||||
};
|
||||
|
||||
NpkArchive() = default;
|
||||
~NpkArchive() = default;
|
||||
|
||||
std::string normalizePath(const std::string& path) const;
|
||||
std::string getCacheFilePath() const;
|
||||
IndexCacheValidationMode getIndexCacheValidationMode() const;
|
||||
std::vector<SourceFileState> collectSourceFileStates() const;
|
||||
bool loadIndexCache(const std::vector<SourceFileState>& sourceFiles);
|
||||
bool saveIndexCache(const std::vector<SourceFileState>& sourceFiles) const;
|
||||
void scanNpkFiles();
|
||||
bool parseNpkFile(const std::string& npkPath);
|
||||
bool loadImgData(ImgRef& img);
|
||||
void parseColor(const uint8* tab, int type, uint8* saveByte, int offset);
|
||||
void evictCacheIfNeeded(size_t requiredSize);
|
||||
void updateCacheUsage(const std::string& imgPath);
|
||||
std::string readNpkInfoString(BinaryFileStreamReader& reader);
|
||||
|
||||
static const uint8 NPK_KEY[256];
|
||||
|
||||
std::string imagePackDirectory_ = "ImagePacks2";
|
||||
bool initialized_ = false;
|
||||
std::unordered_map<std::string, ImgRef> imgIndex_;
|
||||
std::unordered_map<std::string, CachedImageData> imageCache_;
|
||||
std::list<std::string> lruList_;
|
||||
std::unordered_map<std::string, std::list<std::string>::iterator> lruLookup_;
|
||||
size_t maxCacheSize_ = 512 * 1024 * 1024;
|
||||
size_t currentCacheSize_ = 0;
|
||||
std::string defaultImgPath_;
|
||||
size_t defaultImgFrame_ = 0;
|
||||
bool verboseFallbackLog_ =
|
||||
#ifndef NDEBUG
|
||||
true;
|
||||
#else
|
||||
false;
|
||||
#endif
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
#pragma once
|
||||
|
||||
#include <frostbite2D/resource/binary_file_stream_reader.h>
|
||||
#include <frostbite2D/types/type_alias.h>
|
||||
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace frostbite2D {
|
||||
|
||||
/**
|
||||
* @brief PVF ???????
|
||||
*/
|
||||
struct PvfFileInfo {
|
||||
size_t offset = 0; ///< ????????????
|
||||
uint32 crc32 = 0; ///< CRC32 ???
|
||||
size_t length = 0; ///< ????????
|
||||
bool decoded = false; ///< ?????
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief ?????????
|
||||
*/
|
||||
struct RawData {
|
||||
std::unique_ptr<char[]> data; ///< ??????????????????
|
||||
size_t size; ///< ????????
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief PVF ??????
|
||||
*
|
||||
* ??????? PVF ???????????
|
||||
* ??????????????????????????
|
||||
*/
|
||||
class PvfArchive {
|
||||
public:
|
||||
static PvfArchive& get();
|
||||
|
||||
PvfArchive(const PvfArchive&) = delete;
|
||||
PvfArchive& operator=(const PvfArchive&) = delete;
|
||||
PvfArchive(PvfArchive&&) = delete;
|
||||
PvfArchive& operator=(PvfArchive&&) = delete;
|
||||
|
||||
bool open(const std::string& filePath = "Script.pvf");
|
||||
void close();
|
||||
bool isOpen() const;
|
||||
|
||||
void init();
|
||||
void initHeader();
|
||||
void initBinStringTable();
|
||||
void initLoadStrings();
|
||||
|
||||
bool hasFile(const std::string& path) const;
|
||||
std::optional<PvfFileInfo> getFileInfo(const std::string& path) const;
|
||||
std::vector<std::string> listFiles() const;
|
||||
|
||||
std::optional<std::string> getFileContent(const std::string& path);
|
||||
std::optional<std::vector<uint8>> getFileBytes(const std::string& path);
|
||||
std::optional<RawData> getFileRawData(const std::string& path);
|
||||
|
||||
std::optional<std::string> getBinString(int key) const;
|
||||
std::optional<std::string> getLoadString(const std::string& type,
|
||||
const std::string& key) const;
|
||||
bool hasBinString(int key) const;
|
||||
bool hasLoadString(const std::string& type, const std::string& key) const;
|
||||
|
||||
std::string normalizePath(const std::string& path) const;
|
||||
std::string resolvePath(const std::string& baseDir,
|
||||
const std::string& path) const;
|
||||
|
||||
private:
|
||||
PvfArchive() = default;
|
||||
~PvfArchive() = default;
|
||||
|
||||
std::vector<std::string> splitString(const std::string& str,
|
||||
const std::string& delimiter) const;
|
||||
bool decodeFile(const std::string& normalizedPath, PvfFileInfo& info);
|
||||
void clearInitData();
|
||||
std::optional<std::vector<uint8>> readArchiveBytes(size_t absoluteOffset,
|
||||
size_t length);
|
||||
std::optional<std::vector<uint8>> readDecodedFileBytes(
|
||||
const PvfFileInfo& info);
|
||||
static void crcDecodeBuffer(std::vector<uint8>& data, uint32 crc32);
|
||||
|
||||
BinaryFileStreamReader reader_;
|
||||
size_t dataStartPos_ = 0;
|
||||
std::map<std::string, PvfFileInfo> fileInfo_;
|
||||
std::map<int, std::string> binStringTable_;
|
||||
std::map<std::string, std::map<std::string, std::string>> loadStrings_;
|
||||
std::map<std::string, std::vector<uint8>> decodedFileCache_;
|
||||
};
|
||||
|
||||
} // namespace frostbite2D
|
||||
@@ -0,0 +1,115 @@
|
||||
#pragma once
|
||||
|
||||
#include <frostbite2D/types/type_alias.h>
|
||||
#include <json/json.hpp>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace frostbite2D {
|
||||
|
||||
/**
|
||||
* @brief Save system runtime configuration.
|
||||
*/
|
||||
struct SaveSystemConfig {
|
||||
int32 slotCount = 10;
|
||||
std::string saveRoot = ".save";
|
||||
std::string indexFile = "index.json";
|
||||
std::string slotPrefix = "slot_";
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Optional display metadata stored in each slot document.
|
||||
*/
|
||||
struct SaveMeta {
|
||||
std::string displayName;
|
||||
double playTimeSeconds = 0.0;
|
||||
std::string userTag;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Cached per-slot summary used by save slot list UI.
|
||||
*/
|
||||
struct SaveSlotInfo {
|
||||
int32 slot = -1;
|
||||
bool exists = false;
|
||||
std::string updatedAtUtc;
|
||||
uint64 fileSize = 0;
|
||||
int32 schemaVersion = 1;
|
||||
SaveMeta meta;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Manual save/load service using Asset as the only filesystem backend.
|
||||
*/
|
||||
class SaveSystem {
|
||||
public:
|
||||
/**
|
||||
* @brief Get singleton instance.
|
||||
*/
|
||||
static SaveSystem& get();
|
||||
|
||||
SaveSystem(const SaveSystem&) = delete;
|
||||
SaveSystem& operator=(const SaveSystem&) = delete;
|
||||
|
||||
/**
|
||||
* @brief Initialize save root, slot cache and index file.
|
||||
*/
|
||||
bool init(const SaveSystemConfig& config = SaveSystemConfig{});
|
||||
|
||||
/**
|
||||
* @brief Save payload into a fixed slot and update slot index metadata.
|
||||
*/
|
||||
bool saveSlot(int32 slot, const nlohmann::json& payload,
|
||||
const SaveMeta& meta = SaveMeta{});
|
||||
|
||||
/**
|
||||
* @brief Load payload object from a slot.
|
||||
*/
|
||||
std::optional<nlohmann::json> loadSlotPayload(int32 slot) const;
|
||||
|
||||
/**
|
||||
* @brief Query summary info for one slot.
|
||||
*/
|
||||
std::optional<SaveSlotInfo> getSlotInfo(int32 slot) const;
|
||||
|
||||
/**
|
||||
* @brief List summary info of all slots.
|
||||
*/
|
||||
std::vector<SaveSlotInfo> listSlots() const;
|
||||
|
||||
/**
|
||||
* @brief Delete a slot file and clear its index entry.
|
||||
*/
|
||||
bool deleteSlot(int32 slot);
|
||||
|
||||
/**
|
||||
* @brief Check whether slot has an existing save file.
|
||||
*/
|
||||
bool hasSlot(int32 slot) const;
|
||||
|
||||
private:
|
||||
SaveSystem() = default;
|
||||
~SaveSystem() = default;
|
||||
|
||||
bool isValidSlot(int32 slot) const;
|
||||
void resetSlots();
|
||||
bool ensureSaveDirectory() const;
|
||||
bool refreshSlotsFromDisk();
|
||||
bool tryReadSlotDocument(int32 slot, nlohmann::json& outDoc) const;
|
||||
bool writeIndexFile() const;
|
||||
|
||||
std::string indexPath() const;
|
||||
std::string slotPath(int32 slot) const;
|
||||
std::string slotFileName(int32 slot) const;
|
||||
|
||||
static std::string makeUtcTimestamp();
|
||||
static nlohmann::json toJson(const SaveMeta& meta);
|
||||
static SaveMeta fromJson(const nlohmann::json& json);
|
||||
|
||||
SaveSystemConfig config_;
|
||||
std::vector<SaveSlotInfo> slots_;
|
||||
bool initialized_ = false;
|
||||
};
|
||||
|
||||
} // namespace frostbite2D
|
||||
@@ -0,0 +1,208 @@
|
||||
#pragma once
|
||||
|
||||
#include <frostbite2D/types/type_alias.h>
|
||||
#include <frostbite2D/resource/pvf_archive.h>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <cstring>
|
||||
|
||||
namespace frostbite2D {
|
||||
|
||||
/**
|
||||
* @brief 脚本指令操作码枚举
|
||||
*/
|
||||
enum class ScriptOpcode : uint8 {
|
||||
Integer = 2, ///< 整数值
|
||||
Float = 4, ///< 浮点值
|
||||
StringRef5 = 5, ///< 二进制字符串引用(类型5)
|
||||
StringRef6 = 6, ///< 二进制字符串引用(类型6)
|
||||
StringRef7 = 7, ///< 二进制字符串引用(类型7)
|
||||
StringRef8 = 8, ///< 二进制字符串引用(类型8)
|
||||
ExtendedString9 = 9, ///< 扩展字符串(带额外字节)
|
||||
ExtendedString10 = 10, ///< 扩展字符串
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 脚本值类型
|
||||
*/
|
||||
enum class ScriptValueType {
|
||||
Empty, ///< 空值
|
||||
Integer, ///< 整数值
|
||||
Float, ///< 浮点值
|
||||
String, ///< 字符串值
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 脚本解析结果值
|
||||
*/
|
||||
struct ScriptValue {
|
||||
ScriptValueType type = ScriptValueType::Empty; ///< 值类型
|
||||
int32 intValue = 0; ///< 整数值(type == Integer 时有效)
|
||||
double floatValue = 0.0; ///< 浮点值(type == Float 时有效)
|
||||
std::string stringValue; ///< 字符串值(type == String 时有效)
|
||||
|
||||
/**
|
||||
* @brief 判断是否为空值
|
||||
*/
|
||||
bool isEmpty() const { return type == ScriptValueType::Empty; }
|
||||
|
||||
/**
|
||||
* @brief 转换为字符串表示
|
||||
*/
|
||||
std::string toString() const;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 脚本二进制数据解析器
|
||||
*
|
||||
* 用于解析 PVF 存档中的脚本二进制数据。
|
||||
* 每条指令为 5 字节:1 字节操作码 + 4 字节数据。
|
||||
* 支持与 PvfArchive 交互获取字符串资源。
|
||||
*
|
||||
* @example
|
||||
* auto& archive = PvfArchive::get();
|
||||
* if (auto rawData = archive.getFileRawData("script/example.bin")) {
|
||||
* ScriptParser parser(*rawData, "script/example.bin");
|
||||
*
|
||||
* // 迭代解析
|
||||
* while (!parser.isEnd()) {
|
||||
* if (auto value = parser.next()) {
|
||||
* // 处理 value
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* // 或者一次性解析所有
|
||||
* auto allValues = parser.parseAll();
|
||||
* }
|
||||
*/
|
||||
class ScriptParser {
|
||||
public:
|
||||
/**
|
||||
* @brief 从 RawData 构造
|
||||
* @param data 原始数据
|
||||
* @param filePath 文件路径(用于提取文件类型)
|
||||
*/
|
||||
ScriptParser(const RawData& data, const std::string& filePath);
|
||||
|
||||
/**
|
||||
* @brief 从字节数组构造
|
||||
* @param data 字节数组
|
||||
* @param filePath 文件路径(用于提取文件类型)
|
||||
*/
|
||||
ScriptParser(const std::vector<uint8>& data, const std::string& filePath);
|
||||
|
||||
/**
|
||||
* @brief 从指针和大小构造
|
||||
* @param data 数据指针
|
||||
* @param size 数据大小
|
||||
* @param filePath 文件路径(用于提取文件类型)
|
||||
*/
|
||||
ScriptParser(const char* data, size_t size, const std::string& filePath);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 迭代器风格 API
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 获取下一个值
|
||||
* @return 解析的值,失败或结束返回 std::nullopt
|
||||
*/
|
||||
std::optional<ScriptValue> next();
|
||||
|
||||
/**
|
||||
* @brief 回退一个指令
|
||||
*/
|
||||
void back();
|
||||
|
||||
/**
|
||||
* @brief 检查是否已到达数据末尾
|
||||
* @return 到达末尾返回 true
|
||||
*/
|
||||
bool isEnd() const;
|
||||
|
||||
/**
|
||||
* @brief 重置解析位置到开头
|
||||
*/
|
||||
void reset();
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 批量解析
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 解析所有值
|
||||
* @return 所有解析值的列表
|
||||
*/
|
||||
std::vector<ScriptValue> parseAll();
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 状态查询
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 获取当前解析位置
|
||||
* @return 当前位置(字节偏移)
|
||||
*/
|
||||
size_t position() const;
|
||||
|
||||
/**
|
||||
* @brief 获取数据总大小
|
||||
* @return 数据大小(字节)
|
||||
*/
|
||||
size_t size() const;
|
||||
|
||||
/**
|
||||
* @brief 获取文件路径
|
||||
* @return 文件路径
|
||||
*/
|
||||
const std::string& filePath() const;
|
||||
|
||||
/**
|
||||
* @brief 获取文件类型(从路径提取)
|
||||
* @return 文件类型
|
||||
*/
|
||||
const std::string& fileType() const;
|
||||
|
||||
/**
|
||||
* @brief 检查解析器是否有效(有数据)
|
||||
* @return 有效返回 true
|
||||
*/
|
||||
bool isValid() const;
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief 从文件路径提取文件类型
|
||||
* @param filePath 文件路径
|
||||
* @return 文件类型
|
||||
*/
|
||||
std::string extractFileType(const std::string& filePath) const;
|
||||
|
||||
/**
|
||||
* @brief 读取 1 字节
|
||||
* @param offset 偏移位置
|
||||
* @return 字节值
|
||||
*/
|
||||
uint8 readByte(size_t offset) const;
|
||||
|
||||
/**
|
||||
* @brief 读取 4 字节整数
|
||||
* @param offset 偏移位置
|
||||
* @return 整数值
|
||||
*/
|
||||
int32 readInt32(size_t offset) const;
|
||||
|
||||
/**
|
||||
* @brief 解析单个值
|
||||
* @param offset 偏移位置
|
||||
* @return 解析的值
|
||||
*/
|
||||
std::optional<ScriptValue> parseValueAt(size_t offset) const;
|
||||
|
||||
std::vector<char> data_; ///< 数据副本
|
||||
size_t position_ = 2; ///< 当前解析位置(从第 2 字节开始)
|
||||
std::string filePath_; ///< 文件路径
|
||||
std::string fileType_; ///< 文件类型
|
||||
};
|
||||
|
||||
} // namespace frostbite2D
|
||||
@@ -0,0 +1,84 @@
|
||||
#pragma once
|
||||
|
||||
#include <frostbite2D/types/type_alias.h>
|
||||
#include <frostbite2D/resource/binary_reader.h>
|
||||
#include <frostbite2D/resource/asset.h>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
#include <list>
|
||||
#include <memory>
|
||||
|
||||
namespace frostbite2D {
|
||||
|
||||
class BinaryFileStreamReader;
|
||||
|
||||
struct AudioRef {
|
||||
std::string path;
|
||||
std::string npkFile;
|
||||
uint32 offset = 0;
|
||||
uint32 size = 0;
|
||||
bool loaded = false;
|
||||
};
|
||||
|
||||
struct CachedAudioData {
|
||||
std::vector<uint8> data;
|
||||
uint64 lastUseTime = 0;
|
||||
size_t memoryUsage = 0;
|
||||
};
|
||||
|
||||
class SoundPackArchive {
|
||||
public:
|
||||
static SoundPackArchive& get();
|
||||
|
||||
SoundPackArchive(const SoundPackArchive&) = delete;
|
||||
SoundPackArchive& operator=(const SoundPackArchive&) = delete;
|
||||
SoundPackArchive(SoundPackArchive&&) = delete;
|
||||
SoundPackArchive& operator=(SoundPackArchive&&) = delete;
|
||||
|
||||
void setSoundPackDirectory(const std::string& dir);
|
||||
const std::string& getSoundPackDirectory() const;
|
||||
|
||||
void init();
|
||||
void close();
|
||||
bool isOpen() const;
|
||||
|
||||
bool hasAudio(const std::string& path) const;
|
||||
std::optional<AudioRef> getAudio(const std::string& path);
|
||||
std::vector<std::string> listAudios() const;
|
||||
|
||||
std::optional<std::vector<uint8>> getAudioData(const AudioRef& audio);
|
||||
|
||||
void setCacheSize(size_t maxBytes);
|
||||
void clearCache();
|
||||
size_t getCacheUsage() const;
|
||||
|
||||
void setDefaultAudio(const std::string& audioPath);
|
||||
const std::string& getDefaultAudioPath() const;
|
||||
|
||||
private:
|
||||
SoundPackArchive() = default;
|
||||
~SoundPackArchive() = default;
|
||||
|
||||
std::string normalizePath(const std::string& path) const;
|
||||
void scanNpkFiles();
|
||||
bool parseNpkFile(const std::string& npkPath);
|
||||
bool loadAudioData(AudioRef& audio);
|
||||
void evictCacheIfNeeded(size_t requiredSize);
|
||||
void updateCacheUsage(const std::string& audioPath);
|
||||
std::string readNpkInfoString(BinaryFileStreamReader& reader);
|
||||
|
||||
static const uint8 NPK_KEY[256];
|
||||
|
||||
std::string soundPackDirectory_ = "SoundPacks";
|
||||
bool initialized_ = false;
|
||||
std::map<std::string, AudioRef> audioIndex_;
|
||||
std::map<std::string, CachedAudioData> audioCache_;
|
||||
std::list<std::string> lruList_;
|
||||
size_t maxCacheSize_ = 256 * 1024 * 1024;
|
||||
size_t currentCacheSize_ = 0;
|
||||
std::string defaultAudioPath_;
|
||||
};
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user