#include "AssetsLoader.hpp" #include "Assets.hpp" #include "assetload_funcs.hpp" #include "../util/ThreadPool.hpp" #include "../constants.hpp" #include "../data/dynamic.hpp" #include "../debug/Logger.hpp" #include "../coders/imageio.hpp" #include "../files/files.hpp" #include "../files/engine_paths.hpp" #include "../content/Content.hpp" #include "../content/ContentPack.hpp" #include "../voxels/Block.hpp" #include "../graphics/core/Texture.hpp" #include "../logic/scripting/scripting.hpp" #include #include #include static debug::Logger logger("assets-loader"); AssetsLoader::AssetsLoader(Assets* assets, const ResPaths* paths) : 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); } 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) { entries.push(aloader_entry{tag, filename, alias, std::move(settings)}); } 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; } bool AssetsLoader::loadNext() { const aloader_entry& entry = entries.front(); logger.info() << "loading " << entry.filename << " as " << entry.alias; try { aloader_func loader = getLoader(entry.tag); auto postfunc = loader(this, paths, entry.filename, entry.alias, entry.config); postfunc(assets); entries.pop(); return true; } catch (std::runtime_error& err) { logger.error() << err.what(); entries.pop(); return false; } } void addLayouts(const scriptenv& env, const std::string& prefix, const fs::path& folder, AssetsLoader& loader) { if (!fs::is_directory(folder)) { return; } for (auto& entry : fs::directory_iterator(folder)) { const fs::path& file = entry.path(); if (file.extension().u8string() != ".xml") continue; std::string name = prefix+":"+file.stem().u8string(); loader.add(AssetType::layout, file.u8string(), name, std::make_shared(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; } return ""; } void AssetsLoader::processPreload( AssetType tag, const std::string& name, dynamic::Map* map ) { std::string defFolder = assets_def_folder(tag); std::string path = defFolder+"/"+name; if (map == nullptr) { add(tag, path, name); return; } map->str("path", path); switch (tag) { case AssetType::sound: add(tag, path, name, std::make_shared( map->get("keep-pcm", false) )); break; default: add(tag, path, name); break; } } void AssetsLoader::processPreloadList(AssetType tag, dynamic::List* list) { if (list == nullptr) { return; } for (uint i = 0; i < list->size(); i++) { auto value = list->get(i); switch (static_cast(value.index())) { case dynamic::Type::string: processPreload(tag, std::get(value), nullptr); break; case dynamic::Type::map: { auto map = std::get(value); auto name = map->get("name"); processPreload(tag, name, map.get()); break; } default: throw std::runtime_error("invalid entry type"); } } } void AssetsLoader::processPreloadConfig(const fs::path& file) { auto root = files::read_json(file); processPreloadList(AssetType::font, root->list("fonts")); processPreloadList(AssetType::shader, root->list("shaders")); processPreloadList(AssetType::texture, root->list("textures")); processPreloadList(AssetType::sound, root->list("sounds")); // layouts are loaded automatically } void AssetsLoader::processPreloadConfigs(const Content* content) { for (auto& entry : content->getPacks()) { const auto& pack = entry.second; auto preloadFile = pack->getInfo().folder / fs::path("preload.json"); if (fs::exists(preloadFile)) { processPreloadConfig(preloadFile); } } auto preloadFile = paths->getMainRoot()/fs::path("preload.json"); if (fs::exists(preloadFile)) { processPreloadConfig(preloadFile); } } void AssetsLoader::addDefaults(AssetsLoader& loader, const Content* content) { loader.add(AssetType::font, FONTS_FOLDER+"/font", "normal"); loader.add(AssetType::shader, SHADERS_FOLDER+"/ui", "ui"); loader.add(AssetType::shader, SHADERS_FOLDER+"/main", "main"); loader.add(AssetType::shader, SHADERS_FOLDER+"/lines", "lines"); loader.add(AssetType::texture, TEXTURES_FOLDER+"/gui/menubg", "gui/menubg"); loader.add(AssetType::texture, TEXTURES_FOLDER+"/gui/delete_icon", "gui/delete_icon"); loader.add(AssetType::texture, TEXTURES_FOLDER+"/gui/no_icon", "gui/no_icon"); loader.add(AssetType::texture, TEXTURES_FOLDER+"/gui/no_world_icon", "gui/no_world_icon"); loader.add(AssetType::texture, TEXTURES_FOLDER+"/gui/warning", "gui/warning"); loader.add(AssetType::texture, TEXTURES_FOLDER+"/gui/error", "gui/error"); loader.add(AssetType::texture, TEXTURES_FOLDER+"/gui/cross", "gui/cross"); loader.add(AssetType::texture, TEXTURES_FOLDER+"/gui/refresh", "gui/refresh"); if (content) { loader.processPreloadConfigs(content); for (auto& entry : content->getBlockMaterials()) { auto& material = *entry.second; loader.tryAddSound(material.stepsSound); loader.tryAddSound(material.placeSound); loader.tryAddSound(material.breakSound); } addLayouts(0, "core", loader.getPaths()->getMainRoot()/fs::path("layouts"), loader); for (auto& entry : content->getPacks()) { auto pack = entry.second.get(); auto& info = pack->getInfo(); fs::path folder = info.folder / fs::path("layouts"); addLayouts(pack->getEnvironment(), info.id, folder, loader); } } loader.add(AssetType::atlas, TEXTURES_FOLDER+"/blocks", "blocks"); loader.add(AssetType::atlas, TEXTURES_FOLDER+"/items", "items"); } bool AssetsLoader::loadExternalTexture( Assets* assets, const std::string& name, const std::vector& alternatives ) { if (assets->get(name) != nullptr) { return true; } for (auto& path : alternatives) { if (fs::exists(path)) { try { auto image = imageio::read(path.string()); assets->store(Texture::from(image.get()), name); return true; } catch (const std::exception& err) { logger.error() << "error while loading external " << path.u8string() << ": " << err.what(); } } } return false; } const ResPaths* AssetsLoader::getPaths() const { return paths; } class LoaderWorker : public util::Worker { AssetsLoader* loader; public: LoaderWorker(AssetsLoader* loader) : loader(loader) { } assetload::postfunc operator()(const std::shared_ptr& 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) { auto pool = std::make_shared< util::ThreadPool >( "assets-loader-pool", [=](){return std::make_shared(this);}, [=](assetload::postfunc& func) { func(assets); } ); pool->setOnComplete(std::move(onDone)); while (!entries.empty()) { const aloader_entry& entry = entries.front(); auto ptr = std::make_shared(entry); pool->enqueueJob(ptr); entries.pop(); } return pool; }