feat: reloading assets for textures, models and animation

This commit is contained in:
MihailRis 2026-08-30 23:37:58 +03:00
parent 103ca99d81
commit c8605fe78b
5 changed files with 190 additions and 110 deletions

View file

@ -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<AssetsLoadInfo>()) {
}
Assets::~Assets() {

View file

@ -3,6 +3,7 @@
#include "util/stringutil.hpp"
#include "util/ObjectsKeeper.hpp"
#include "graphics/core/TextureAnimation.hpp"
#include "io/fwd.hpp"
#include <functional>
#include <memory>
@ -16,6 +17,8 @@
#include <vector>
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<void(Assets&)>;
using setupfunc = std::function<void(const Assets&)>;
template <class T>
void assets_setup(const Assets&);
@ -63,15 +64,13 @@ namespace assetload {
return reason;
}
};
}
class Assets {
util::ObjectsKeeper* vault;
std::vector<TextureAnimation> animations;
using assets_map = std::unordered_map<std::string, std::shared_ptr<void>>;
std::unordered_map<std::type_index, assets_map> assets;
public:
using assets_map = std::unordered_map<std::string, std::shared_ptr<void>>;
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<TextureAnimation> animations;
std::unordered_map<std::type_index, assets_map> assets;
std::unique_ptr<AssetsLoadInfo> assetsLoadInfo;
};
template <class T>

View file

@ -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<AssetCfg> 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<Task> 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;
}

View file

@ -86,6 +86,13 @@ struct aloader_entry {
std::shared_ptr<AssetCfg> config;
};
using AssetFullId = std::pair<std::string, AssetType>;
struct AssetsLoadInfo {
std::map<AssetFullId, aloader_entry> processedEntries;
std::multimap<io::path, AssetFullId> referencedAssets;
};
class AssetsLoader {
public:
AssetsLoader(Engine& engine, Assets& assets, const ResPaths& paths);
@ -126,6 +133,9 @@ public:
const std::vector<io::path>& 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<AssetType, aloader_func> loaders;
std::queue<aloader_entry> entries;
std::set<std::pair<AssetType, std::string>> enqueued;
std::set<AssetFullId> enqueued;
const ResPaths& paths;
AssetsLoadInfo& assetsLoadInfo;
void processPreload(
AssetType tag, const std::string& name, const dv::value& map

View file

@ -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<AssetCfg>&
) {
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<AssetCfg>&
) {
auto actualFile = paths.find(filename + ".png");
auto file = paths.find(filename + ".png");
loader.attachToFile(file, {name, AssetType::TEXTURE});
try {
std::shared_ptr<ImageData> image(imageio::read(actualFile));
return [name, image, actualFile](auto& assets) {
std::shared_ptr<ImageData> 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<ModelCfg>& config
) {
auto bytes = io::read_bytes_buffer(file);
auto modelVEC3 = std::make_shared<vec3::File>(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<model::Model>(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>(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<ModelCfg>&
) {
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::Model>(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<ModelCfg>& 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<model::Model>(std::move(vcmModel.squash()))
.release();
return [=, &loader](Assets& assets) {
auto model = std::unique_ptr<model::Model>(modelPtr);
request_textures(loader, *model);
assets.store(std::move(model), name);
logger.info() << "store model " << util::quote(name);
};
}
auto vcmModelPtr =
std::make_unique<vcm::VcmModel>(std::move(vcmModel)).release();
return [=](Assets& assets) {
auto vcmModel = std::unique_ptr<vcm::VcmModel>(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<model::Model>(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<rigging::SkeletonConfig>(
std::make_unique<rigging::SkeletonConfig>(
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<AssetCfg>& config
) {
auto cfg = std::dynamic_pointer_cast<ModelCfg>(config);
auto modelConfig = std::dynamic_pointer_cast<ModelCfg>(config);
auto path = paths.find(file + ".vec3");
if (io::exists(path)) {
auto bytes = io::read_bytes_buffer(path);
auto modelVEC3 = std::make_shared<vec3::File>(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<model::Model>(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>(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::Model>(model), name);
};
} catch (const parsing_error& err) {
std::cerr << err.errorLog() << std::endl;
throw;
}
return load_obj_model(path, loader, name, modelConfig);
}
std::array<std::string, 2> 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<model::Model>(std::move(vcmModel.squash())).release();
return [=, &loader](Assets& assets) {
auto model = std::unique_ptr<model::Model>(modelPtr);
request_textures(loader, *model);
assets.store(std::move(model), name);
logger.info() << "store model " << util::quote(name);
};
} else {
auto vcmModelPtr = std::make_unique<vcm::VcmModel>(std::move(vcmModel)).release();
return [=](Assets& assets) {
auto vcmModel = std::unique_ptr<vcm::VcmModel>(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<model::Model>(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<rigging::SkeletonConfig>(
std::make_unique<rigging::SkeletonConfig>(
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(