#include "AssetsLoader.hpp" #include "assetload_funcs.hpp" #include "Assets.hpp" #include "coders/commons.hpp" #include "coders/imageio.hpp" #include "constants.hpp" #include "content/Content.hpp" #include "content/ContentPack.hpp" #include "debug/Logger.hpp" #include "engine/Engine.hpp" #include "engine/EnginePaths.hpp" #include "graphics/core/Texture.hpp" #include "io/io.hpp" #include "items/ItemDef.hpp" #include "logic/scripting/scripting.hpp" #include "objects/EntityDef.hpp" #include "objects/rigging.hpp" #include "util/ThreadPool.hpp" #include "voxels/Block.hpp" #include #include namespace fs = std::filesystem; static debug::Logger logger("assets-loader"); AssetsLoader::AssetsLoader(Engine& engine, Assets& assets, const ResPaths& paths) : engine(engine), assets(assets), paths(paths) { addLoader(AssetType::SHADER, assetload::shader); addLoader(AssetType::TEXTURE, assetload::texture); addLoader(AssetType::FONT, assetload::font); addLoader(AssetType::ATLAS, assetload::atlas); addLoader(AssetType::LAYOUT, assetload::layout); addLoader(AssetType::SOUND, assetload::sound); addLoader(AssetType::MODEL, assetload::model); addLoader(AssetType::POST_EFFECT, assetload::posteffect); addLoader(AssetType::SKELETON, assetload::skeleton); } void AssetsLoader::addLoader(AssetType tag, aloader_func func) { loaders[tag] = std::move(func); } void AssetsLoader::add( AssetType tag, const std::string& filename, const std::string& alias, std::shared_ptr settings, bool overwrite ) { if (!overwrite && enqueued.find({tag, alias}) != enqueued.end()){ return; } entries.push(aloader_entry {tag, filename, alias, std::move(settings)}); enqueued.insert({tag, alias}); } bool AssetsLoader::hasNext() const { return !entries.empty(); } aloader_func AssetsLoader::getLoader(AssetType tag) { auto found = loaders.find(tag); if (found == loaders.end()) { throw std::runtime_error( "unknown asset tag " + std::to_string(static_cast(tag)) ); } return found->second; } void AssetsLoader::loadNext() { const aloader_entry& entry = entries.front(); logger.info() << "loading " << entry.filename << " as " << entry.alias; std::string error {}; try { aloader_func loader = getLoader(entry.tag); auto postfunc = loader(this, paths, entry.filename, entry.alias, entry.config); postfunc(&assets); } catch (const parsing_error& err) { error = err.errorLog(); } catch (const std::runtime_error& err) { error = err.what(); } if (!error.empty()) { logger.error() << error; auto tag = entry.tag; auto filename = entry.filename; entries.pop(); throw assetload::error(tag, std::move(filename), std::move(error)); } entries.pop(); } static void add_layouts( const scriptenv& env, const std::string& prefix, const io::path& folder, AssetsLoader& loader ) { if (!io::is_directory(folder)) { return; } for (const auto& file : io::directory_iterator(folder)) { if (file.extension() != ".xml") continue; std::string name = prefix + ":" + file.stem(); loader.add( AssetType::LAYOUT, file.string(), name, std::make_shared(&loader.getEngine().getGUI(), env) ); } } void AssetsLoader::tryAddSound(const std::string& name) { if (name.empty()) { return; } std::string file = SOUNDS_FOLDER + "/" + name; add(AssetType::SOUND, file, name); } static std::string assets_def_folder(AssetType tag) { switch (tag) { case AssetType::FONT: return FONTS_FOLDER; case AssetType::SHADER: return SHADERS_FOLDER; case AssetType::TEXTURE: return TEXTURES_FOLDER; case AssetType::ATLAS: return TEXTURES_FOLDER; case AssetType::LAYOUT: return LAYOUTS_FOLDER; case AssetType::SOUND: return SOUNDS_FOLDER; case AssetType::MODEL: return MODELS_FOLDER; case AssetType::POST_EFFECT: return POST_EFFECTS_FOLDER; case AssetType::SKELETON: return SKELETONS_FOLDER; } return ""; } void AssetsLoader::processPreload( AssetType tag, const std::string& name, const dv::value& map ) { std::string defFolder = assets_def_folder(tag); std::string path = defFolder + "/" + name; if (map == nullptr) { add(tag, path, name, nullptr, true); return; } std::shared_ptr config = nullptr; map.at("path").get(path); logger.debug() << "processing preload " << util::quote(name) << " path: " << util::quote(path); switch (tag) { case AssetType::SOUND: { bool keepPCM = false; config = std::make_shared(map.at("keep-pcm").get(keepPCM)); break; } case AssetType::ATLAS: { std::string typeName = "atlas"; map.at("type").get(typeName); auto type = AtlasType::ATLAS; if (typeName == "separate") { type = AtlasType::SEPARATE; } config = std::make_shared(type); break; } case AssetType::POST_EFFECT: { bool advanced = false; map.at("advanced").get(advanced); config = std::make_shared(advanced); break; } case AssetType::MODEL: { bool squashed = false; map.at("squash").get(squashed); config = std::make_shared(squashed); break; } case AssetType::FONT: { int size = DEFAULT_FONT_SIZE; map.at("size").get(size); config = std::make_unique(size); break; } default: break; } add(tag, path, name, std::move(config), true); } void AssetsLoader::processPreloadList(AssetType tag, const dv::value& list) { if (list == nullptr) { return; } for (const auto& value : list) { switch (value.getType()) { case dv::value_type::string: processPreload(tag, value.asString(), nullptr); break; case dv::value_type::object: processPreload(tag, value["name"].asString(), value); break; default: throw std::runtime_error("invalid entry type"); } } } void AssetsLoader::processPreloadConfig(const io::path& file) { auto root = io::read_json(file); processPreloadList(AssetType::ATLAS, root["atlases"]); processPreloadList(AssetType::FONT, root["fonts"]); processPreloadList(AssetType::SHADER, root["shaders"]); processPreloadList(AssetType::TEXTURE, root["textures"]); processPreloadList(AssetType::SOUND, root["sounds"]); processPreloadList(AssetType::MODEL, root["models"]); processPreloadList(AssetType::POST_EFFECT, root["post-effects"]); processPreloadList(AssetType::SKELETON, root["skeletons"]); // layouts are loaded automatically } void AssetsLoader::processPreloadConfigs(const Content* content) { io::path preloadFile = "res:preload.json"; if (io::exists(preloadFile)) { processPreloadConfig(preloadFile); } if (content == nullptr) { return; } for (auto& entry : content->getPacks()) { if (entry.first == "core") { continue; } const auto& pack = entry.second; preloadFile = pack->getInfo().folder / "preload.json"; if (io::exists(preloadFile)) { processPreloadConfig(preloadFile); } } } static void add_variant(AssetsLoader& loader, const Variant& variant) { if (!variant.model.name.empty() && variant.model.name.find(':') == std::string::npos) { loader.add( AssetType::MODEL, MODELS_FOLDER + "/" + variant.model.name, variant.model.name ); } } void AssetsLoader::addDefaults(AssetsLoader& loader, const Content* content) { loader.processPreloadConfigs(content); if (content) { for (auto& entry : content->getBlockMaterials()) { auto& material = *entry.second; loader.tryAddSound(material.stepsSound); loader.tryAddSound(material.placeSound); loader.tryAddSound(material.breakSound); loader.tryAddSound(material.hitSound); } for (auto& entry : content->getPacks()) { auto pack = entry.second.get(); auto& info = pack->getInfo(); io::path folder = info.folder / "layouts"; add_layouts(pack->getEnvironment(), info.id, folder, loader); } for (const auto& entry : content->getPacks()) { io::path skeletonsDir = entry.first + ":skeletons"; if (!io::is_directory(skeletonsDir)) { continue; } for (const auto& file : io::directory_iterator(skeletonsDir)) { loader.add( AssetType::SKELETON, (file.parent() / file.stem()).string(), entry.first + ":" + file.stem() ); } } for (const auto& [_, def] : content->blocks.getDefs()) { if (def->variants) { for (const auto& variant : def->variants->variants) { add_variant(loader, variant); } } else { add_variant(loader, def->defaults); } } for (const auto& [_, def] : content->items.getDefs()) { if (def->modelName.find(':') == std::string::npos) { loader.add( AssetType::MODEL, MODELS_FOLDER + "/" + def->modelName, def->modelName ); } } for (const auto& [_, def] : content->entities.getDefs()) { if (def->skeletonName.find(':') == std::string::npos) { // expecting a VCM with skeleton loader.add( AssetType::MODEL, MODELS_FOLDER + "/" + def->skeletonName, def->skeletonName ); } } } } bool AssetsLoader::loadExternalTexture( AssetsLoader& loader, const std::string& name, const std::vector& alternatives ) { if (loader.getAssets().get(name) != nullptr) { return true; } for (auto& path : alternatives) { if (io::exists(path)) { loader.add( AssetType::TEXTURE, (path.parent() / path.stem()).string(), name, nullptr ); return true; } } return false; } Assets& AssetsLoader::getAssets() { return assets; } Engine& AssetsLoader::getEngine() { return engine; } const ResPaths& AssetsLoader::getPaths() const { return paths; } class LoaderWorker : public util::Worker { AssetsLoader* loader; public: LoaderWorker(AssetsLoader* loader) : loader(loader) { } assetload::postfunc operator()(const aloader_entry& entry ) override { aloader_func loadfunc = loader->getLoader(entry.tag); return loadfunc( loader, loader->getPaths(), entry.filename, entry.alias, entry.config ); } }; std::shared_ptr AssetsLoader::startTask(runnable onDone, int maxWorkers) { auto pool = std::make_shared>( "assets-loader-pool", [=]() { return std::make_unique(this); }, [this](const assetload::postfunc& func) { func(&assets); }, maxWorkers ); pool->setOnComplete(std::move(onDone)); pool->setJobsSource([this]() -> std::optional { if (entries.empty()) { return std::nullopt; } aloader_entry entry = std::move(entries.front()); entries.pop(); return entry; }); while (!entries.empty()) { aloader_entry entry = std::move(entries.front()); entries.pop(); pool->enqueueJob(std::move(entry)); } return pool; }