From c8605fe78bbbbb55735ea7f5cd0b01e6075e34ef Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sun, 30 Aug 2026 23:37:58 +0300 Subject: [PATCH] feat: reloading assets for textures, models and animation --- src/assets/Assets.cpp | 4 +- src/assets/Assets.hpp | 23 +++- src/assets/AssetsLoader.cpp | 32 ++++- src/assets/AssetsLoader.hpp | 14 +- src/assets/assetload_funcs.cpp | 227 +++++++++++++++++++-------------- 5 files changed, 190 insertions(+), 110 deletions(-) diff --git a/src/assets/Assets.cpp b/src/assets/Assets.cpp index 3e0776747..5b306a375 100644 --- a/src/assets/Assets.cpp +++ b/src/assets/Assets.cpp @@ -1,6 +1,8 @@ #include "Assets.hpp" +#include "AssetsLoader.hpp" +#include "io/path.hpp" -Assets::Assets(util::ObjectsKeeper* vault) : vault(vault) { +Assets::Assets(util::ObjectsKeeper* vault) : vault(vault), assetsLoadInfo(std::make_unique()) { } Assets::~Assets() { diff --git a/src/assets/Assets.hpp b/src/assets/Assets.hpp index 68f802047..5858e17c5 100644 --- a/src/assets/Assets.hpp +++ b/src/assets/Assets.hpp @@ -3,6 +3,7 @@ #include "util/stringutil.hpp" #include "util/ObjectsKeeper.hpp" #include "graphics/core/TextureAnimation.hpp" +#include "io/fwd.hpp" #include #include @@ -16,6 +17,8 @@ #include class Assets; +struct AssetsLoadInfo; +class AssetsLoader; enum class AssetType { ANIMATION, @@ -34,8 +37,6 @@ namespace assetload { /// @brief final work to do in the main thread using postfunc = std::function; - using setupfunc = std::function; - template void assets_setup(const Assets&); @@ -63,15 +64,13 @@ namespace assetload { return reason; } }; + } class Assets { - util::ObjectsKeeper* vault; - std::vector animations; - - using assets_map = std::unordered_map>; - std::unordered_map assets; public: + using assets_map = std::unordered_map>; + Assets(util::ObjectsKeeper* vault); Assets(const Assets&) = delete; ~Assets(); @@ -142,6 +141,16 @@ public: } return &mapIter->second; } + + AssetsLoadInfo& getLoadInfo() const { + return *assetsLoadInfo; + } +private: + util::ObjectsKeeper* vault; + std::vector animations; + + std::unordered_map assets; + std::unique_ptr assetsLoadInfo; }; template diff --git a/src/assets/AssetsLoader.cpp b/src/assets/AssetsLoader.cpp index 207dab3a1..454f2434f 100644 --- a/src/assets/AssetsLoader.cpp +++ b/src/assets/AssetsLoader.cpp @@ -29,7 +29,10 @@ static debug::Logger logger("assets-loader"); AssetsLoader::AssetsLoader( Engine& engine, Assets& assets, const ResPaths& paths ) - : engine(engine), assets(assets), paths(paths) { + : engine(engine), + assets(assets), + paths(paths), + assetsLoadInfo(assets.getLoadInfo()) { addLoader(AssetType::ANIMATION, assetload::animation); addLoader(AssetType::ATLAS, assetload::atlas); addLoader(AssetType::FONT, assetload::font); @@ -53,11 +56,11 @@ void AssetsLoader::add( std::shared_ptr settings, bool overwrite ) { - if (!overwrite && enqueued.find({tag, alias}) != enqueued.end()){ + if (!overwrite && enqueued.find({alias, tag}) != enqueued.end()){ return; } entries.push(aloader_entry {tag, filename, alias, std::move(settings)}); - enqueued.insert({tag, alias}); + enqueued.insert({alias, tag}); } bool AssetsLoader::hasNext() const { @@ -75,7 +78,7 @@ aloader_func AssetsLoader::getLoader(AssetType tag) { } void AssetsLoader::loadNext() { - const aloader_entry& entry = entries.front(); + aloader_entry& entry = entries.front(); logger.info() << "loading " << entry.filename << " as " << entry.alias; std::string error {}; @@ -96,6 +99,8 @@ void AssetsLoader::loadNext() { entries.pop(); throw assetload::error(tag, std::move(filename), std::move(error)); } + assetsLoadInfo.processedEntries[{entry.alias, entry.tag}] = + std::move(entry); entries.pop(); } @@ -411,3 +416,22 @@ std::shared_ptr AssetsLoader::startTask(runnable onDone, int maxWorkers) { } return pool; } + +void AssetsLoader::attachToFile(const io::path& file, AssetFullId assetId) { + assetsLoadInfo.referencedAssets.insert({file, std::move(assetId)}); +} + +int AssetsLoader::addReload(const io::path& path, AssetsLoader& dst) { + int added = 0; + + auto range = assetsLoadInfo.referencedAssets.equal_range(path); + for (auto it = range.first; it != range.second; ++it) { + const auto& recipe = assetsLoadInfo.processedEntries.find(it->second); + if (recipe != assetsLoadInfo.processedEntries.end()) { + dst.entries.push(recipe->second); + added++; + } + } + + return added; +} diff --git a/src/assets/AssetsLoader.hpp b/src/assets/AssetsLoader.hpp index 48676d6ed..86350d2cb 100644 --- a/src/assets/AssetsLoader.hpp +++ b/src/assets/AssetsLoader.hpp @@ -86,6 +86,13 @@ struct aloader_entry { std::shared_ptr config; }; +using AssetFullId = std::pair; + +struct AssetsLoadInfo { + std::map processedEntries; + std::multimap referencedAssets; +}; + class AssetsLoader { public: AssetsLoader(Engine& engine, Assets& assets, const ResPaths& paths); @@ -126,6 +133,9 @@ public: const std::vector& alternatives ); + int addReload(const io::path& path, AssetsLoader& dst); + void attachToFile(const io::path& file, AssetFullId assetId); + Assets& getAssets(); Engine& getEngine(); private: @@ -133,8 +143,10 @@ private: Assets& assets; std::map loaders; std::queue entries; - std::set> enqueued; + std::set enqueued; + const ResPaths& paths; + AssetsLoadInfo& assetsLoadInfo; void processPreload( AssetType tag, const std::string& name, const dv::value& map diff --git a/src/assets/assetload_funcs.cpp b/src/assets/assetload_funcs.cpp index f406cef45..c14fd8fb2 100644 --- a/src/assets/assetload_funcs.cpp +++ b/src/assets/assetload_funcs.cpp @@ -46,13 +46,15 @@ static bool load_animated_texture( ); assetload::postfunc assetload::animation( - AssetsLoader&, + AssetsLoader& loader, const ResPaths& paths, const std::string& filename, const std::string& name, const std::shared_ptr& ) { auto path = paths.find(filename + ".vca"); + loader.attachToFile(path, {name, AssetType::ANIMATION}); + if (io::is_regular_file(path)) { scripting::load_vca_animation(path, name); } @@ -60,20 +62,21 @@ assetload::postfunc assetload::animation( } assetload::postfunc assetload::texture( - AssetsLoader&, + AssetsLoader& loader, const ResPaths& paths, const std::string& filename, const std::string& name, const std::shared_ptr& ) { - auto actualFile = paths.find(filename + ".png"); + auto file = paths.find(filename + ".png"); + loader.attachToFile(file, {name, AssetType::TEXTURE}); try { - std::shared_ptr image(imageio::read(actualFile)); - return [name, image, actualFile](auto& assets) { + std::shared_ptr image(imageio::read(file)); + return [name, image, file](auto& assets) { assets.store(Texture::from(image.get()), name); }; } catch (const std::runtime_error& err) { - logger.error() << actualFile.string() << ": " << err.what(); + logger.error() << file.string() << ": " << err.what(); return [](auto&) {}; } } @@ -339,6 +342,123 @@ static void request_textures(AssetsLoader& loader, const model::Model& model) { } } +static assetload::postfunc load_vec3_model( + const io::path& file, + AssetsLoader& loader, + const std::string& name, + const std::shared_ptr& config +) { + auto bytes = io::read_bytes_buffer(file); + auto modelVEC3 = std::make_shared(vec3::load(file.string(), bytes)); + return [&loader, name, file, config, modelVEC3 = std::move(modelVEC3)]( + Assets& assets + ) { + loader.attachToFile(file, {name, AssetType::MODEL}); + if (config && config->squashed) { + model::Model fullModel; + for (auto& entry : modelVEC3->models) { + auto& vec3model = entry.second; + auto& model = vec3model.model; + model.translate(vec3model.origin); + fullModel.merge(std::move(model)); + } + request_textures(loader, fullModel); + assets.store( + std::make_unique(fullModel), + name + ); + logger.info() << "store model " << util::quote(name); + return; + } + for (auto& [modelName, model] : modelVEC3->models) { + request_textures(loader, model.model); + std::string fullName = name; + if (name != modelName) { + fullName += "." + modelName; + } + assets.store( + std::make_unique(model.model), + fullName + ); + logger.info() << "store model " << util::quote(modelName) + << " as " << util::quote(fullName); + } + }; +} + +static assetload::postfunc load_obj_model( + const io::path& file, + AssetsLoader& loader, + const std::string& name, + const std::shared_ptr& +) { + auto text = io::read_string(file); + try { + auto model = obj::parse(file.string(), text).release(); + return [=, &loader](Assets& assets) { + request_textures(loader, *model); + assets.store(std::unique_ptr(model), name); + }; + } catch (const parsing_error& err) { + std::cerr << err.errorLog() << std::endl; + throw; + } +} + +static assetload::postfunc load_vcm_model( + const io::path& file, + AssetsLoader& loader, + const std::string& name, + const std::shared_ptr& cfg +) { + auto text = io::read_string(file); + try { + auto vcmModel = vcm::parse(file.string(), text, file.extension() == ".xml"); + + assert(vcmModel.parts.size() > 0); + + if (vcmModel.parts.size() == 1 || (cfg && cfg->squashed)) { + auto modelPtr = + std::make_unique(std::move(vcmModel.squash())) + .release(); + return [=, &loader](Assets& assets) { + auto model = std::unique_ptr(modelPtr); + request_textures(loader, *model); + assets.store(std::move(model), name); + logger.info() << "store model " << util::quote(name); + }; + } + auto vcmModelPtr = + std::make_unique(std::move(vcmModel)).release(); + return [=](Assets& assets) { + auto vcmModel = std::unique_ptr(vcmModelPtr); + for (auto& [partName, model] : vcmModel->parts) { + auto fullName = name + "." + partName; + logger.info() + << "store model part " << util::quote(partName) + << " as " << util::quote(fullName); + assets.store( + std::make_unique(std::move(model)), + fullName + ); + } + for (auto& bone : vcmModel->skeleton->getBones()) { + bone->setModel(name + "." + bone->model.name); + } + logger.info() << "store skeleton " << util::quote(name); + assets.store( + std::make_unique( + std::move(*vcmModel->skeleton) + ), + name + ); + }; + } catch (const parsing_error& err) { + std::cerr << err.errorLog() << std::endl; + throw; + } +} + assetload::postfunc assetload::model( AssetsLoader& loader, const ResPaths& paths, @@ -346,59 +466,15 @@ assetload::postfunc assetload::model( const std::string& name, const std::shared_ptr& config ) { - auto cfg = std::dynamic_pointer_cast(config); + auto modelConfig = std::dynamic_pointer_cast(config); auto path = paths.find(file + ".vec3"); if (io::exists(path)) { - auto bytes = io::read_bytes_buffer(path); - auto modelVEC3 = std::make_shared(vec3::load(path.string(), bytes)); - return [&loader, name, cfg, modelVEC3 = std::move(modelVEC3)]( - Assets& assets - ) { - if (cfg && cfg->squashed) { - model::Model fullModel; - for (auto& entry : modelVEC3->models) { - auto& vec3model = entry.second; - auto& model = vec3model.model; - model.translate(vec3model.origin); - fullModel.merge(std::move(model)); - } - request_textures(loader, fullModel); - assets.store( - std::make_unique(fullModel), - name - ); - logger.info() << "store model " << util::quote(name); - return; - } - for (auto& [modelName, model] : modelVEC3->models) { - request_textures(loader, model.model); - std::string fullName = name; - if (name != modelName) { - fullName += "." + modelName; - } - assets.store( - std::make_unique(model.model), - fullName - ); - logger.info() << "store model " << util::quote(modelName) - << " as " << util::quote(fullName); - } - }; + return load_vec3_model(path, loader, name, modelConfig); } path = paths.find(file + ".obj"); if (io::exists(path)) { - auto text = io::read_string(path); - try { - auto model = obj::parse(path.string(), text).release(); - return [=, &loader](Assets& assets) { - request_textures(loader, *model); - assets.store(std::unique_ptr(model), name); - }; - } catch (const parsing_error& err) { - std::cerr << err.errorLog() << std::endl; - throw; - } + return load_obj_model(path, loader, name, modelConfig); } std::array extensions { @@ -418,50 +494,7 @@ assetload::postfunc assetload::model( throw std::runtime_error("could not to find model " + util::quote(file)); } - auto text = io::read_string(path); - try { - auto vcmModel = vcm::parse(path.string(), text, path.extension() == ".xml"); - - assert(vcmModel.parts.size() > 0); - - if (vcmModel.parts.size() == 1 || (cfg && cfg->squashed)) { - auto modelPtr = std::make_unique(std::move(vcmModel.squash())).release(); - return [=, &loader](Assets& assets) { - auto model = std::unique_ptr(modelPtr); - request_textures(loader, *model); - assets.store(std::move(model), name); - logger.info() << "store model " << util::quote(name); - }; - } else { - auto vcmModelPtr = std::make_unique(std::move(vcmModel)).release(); - return [=](Assets& assets) { - auto vcmModel = std::unique_ptr(vcmModelPtr); - for (auto& [partName, model] : vcmModel->parts) { - auto fullName = name + "." + partName; - logger.info() - << "store model part " << util::quote(partName) - << " as " << util::quote(fullName); - assets.store( - std::make_unique(std::move(model)), - fullName - ); - } - for (auto& bone : vcmModel->skeleton->getBones()) { - bone->setModel(name + "." + bone->model.name); - } - logger.info() << "store skeleton " << util::quote(name); - assets.store( - std::make_unique( - std::move(*vcmModel->skeleton) - ), - name - ); - }; - } - } catch (const parsing_error& err) { - std::cerr << err.errorLog() << std::endl; - throw; - } + return load_vcm_model(path, loader, name, modelConfig); } assetload::postfunc assetload::skeleton(