feat: add export / file packing

This commit is contained in:
2026-10-30 15:42:08 +01:00
parent de7e489cc0
commit 0fbc023018
28 changed files with 782 additions and 157 deletions
+46
View File
@@ -0,0 +1,46 @@
#ifndef DESTRUM_PACKFILE_H
#define DESTRUM_PACKFILE_H
#include <cstdint>
#include <filesystem>
#include <string>
#include <string_view>
#include <unordered_map>
#include <utility>
#include <vector>
namespace PackFile {
inline constexpr std::uint32_t Version = 1;
inline constexpr std::string_view Extension = ".dpak";
struct Entry {
std::string path;
std::uint64_t offset{0};
std::uint64_t size{0};
};
struct Source {
std::string path;
std::filesystem::path file;
};
// Paths are "<scheme>/<relative path>", e.g. "engine/shaders/mesh.vert.spv"
void Write(const std::vector<Source>& files, const std::filesystem::path& path);
class Reader {
public:
explicit Reader(std::filesystem::path path);
[[nodiscard]] const Entry* Find(std::string_view path) const;
[[nodiscard]] std::vector<std::uint8_t> Read(const Entry& entry) const;
[[nodiscard]] const std::vector<Entry>& Entries() const { return m_Entries; }
[[nodiscard]] const std::filesystem::path& Path() const { return m_Path; }
private:
std::filesystem::path m_Path;
std::vector<Entry> m_Entries;
std::unordered_map<std::string, std::size_t> m_ByPath;
};
}
#endif // DESTRUM_PACKFILE_H
+12 -1
View File
@@ -3,12 +3,14 @@
#include <filesystem>
#include <cstdint>
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <vector>
#include <destrum/Singleton.h>
#include <destrum/Assets/PackFile.h>
#include <destrum/FS/Manifest.h>
@@ -18,17 +20,26 @@ struct FSMount {
std::filesystem::path root;
std::optional<AssetManifest> manifest;
std::optional<std::filesystem::path> sourceRoot;
std::shared_ptr<const PackFile::Reader> pack;
};
class AssetFS final: public Singleton<AssetFS> {
public:
static constexpr std::string_view PackName = "assets.dpak";
void Init(std::filesystem::path exeDir);
void Reset();
void Mount(std::string scheme, std::filesystem::path root);
void MountPack(const std::filesystem::path& packPath, const std::filesystem::path& root);
void ReloadManifest(std::string_view scheme);
std::vector<uint8_t> ReadBytes(std::string_view vpath);
[[nodiscard]] std::vector<uint8_t> ReadFile(const std::filesystem::path& fullPath) const;
[[nodiscard]] bool Exists(const std::filesystem::path& fullPath) const;
[[nodiscard]] std::vector<std::filesystem::path> ListFiles(const FSMount& mount) const;
[[nodiscard]] bool IsPacked() const;
[[nodiscard]] std::filesystem::path GetFullPath(std::string_view vpath) const;
[[nodiscard]] std::filesystem::path GetCookedPathForFile(std::string_view vpath) const;
@@ -43,7 +54,7 @@ public:
bool WriteFile(std::string_view vpath, std::string_view contents);
private:
static std::vector<uint8_t> ReadFile(const std::filesystem::path& fullPath);
void AddMount(std::string scheme, std::filesystem::path root, std::shared_ptr<const PackFile::Reader> pack);
std::vector<FSMount> mounts;
+2 -1
View File
@@ -4,6 +4,7 @@
#include <cstdint>
#include <filesystem>
#include <optional>
#include <span>
#include <string>
#include <string_view>
#include <unordered_map>
@@ -29,7 +30,7 @@ struct AssetManifest {
};
namespace FS {
AssetManifest LoadAssetManifest(const std::filesystem::path& manifestPath);
AssetManifest ParseAssetManifest(std::span<const std::uint8_t> bytes);
}
#endif //MANIFEST_H
+19 -8
View File
@@ -20,6 +20,7 @@
//
// Link against: assimp
#include <assimp/IOSystem.hpp>
#include <assimp/Importer.hpp>
#include <assimp/material.h>
#include <assimp/postprocess.h>
@@ -43,6 +44,7 @@
#include <filesystem>
#include <iostream>
#include <limits>
#include <memory>
#include <stdexcept>
#include <string>
#include <unordered_map>
@@ -998,8 +1000,12 @@ static void CollectMergedPerNodeMeshes(const aiScene* scene,
// ─── Public API ───────────────────────────────────────────────────────────────
static ModelAsset LoadModel(const std::string& path, const LoadOptions& options = {}) {
// The importer owns io, without one it reads from disk
static ModelAsset LoadModel(const std::string& path, const LoadOptions& options = {},
std::unique_ptr<Assimp::IOSystem> io = nullptr) {
Assimp::Importer importer;
if (io)
importer.SetIOHandler(io.release());
const aiScene* scene = LoadScene(importer, path, options);
ModelAsset model{};
@@ -1084,16 +1090,18 @@ static const aiScene* LoadAnimationScene(
Assimp::Importer& importer,
const std::string& path
) {
if (!std::filesystem::exists(path)) {
if (!importer.GetIOHandler()->Exists(path.c_str())) {
throw std::runtime_error("Animation file does not exist: " + path);
}
if (!std::filesystem::is_regular_file(path)) {
throw std::runtime_error("Animation path is not a regular file: " + path);
}
if (importer.IsDefaultIOHandler()) {
if (!std::filesystem::is_regular_file(path)) {
throw std::runtime_error("Animation path is not a regular file: " + path);
}
if (std::filesystem::file_size(path) == 0) {
throw std::runtime_error("Animation file is empty: " + path);
if (std::filesystem::file_size(path) == 0) {
throw std::runtime_error("Animation file is empty: " + path);
}
}
// Important: do not use mesh post-process flags for animation-only files.
@@ -1141,9 +1149,12 @@ static const aiScene* LoadAnimationScene(
static std::vector<SkeletalAnimation> LoadAnimationClips(
const std::string& path,
const Skeleton& targetSkeleton
const Skeleton& targetSkeleton,
std::unique_ptr<Assimp::IOSystem> io = nullptr
) {
Assimp::Importer importer;
if (io)
importer.SetIOHandler(io.release());
const aiScene* scene = LoadAnimationScene(importer, path);
auto clips = LoadAnimations(scene, targetSkeleton);