diff --git a/src/assets/AssetsLoader.cpp b/src/assets/AssetsLoader.cpp index 3f050055..14d1298f 100644 --- a/src/assets/AssetsLoader.cpp +++ b/src/assets/AssetsLoader.cpp @@ -9,8 +9,8 @@ #include "content/Content.hpp" #include "content/ContentPack.hpp" #include "debug/Logger.hpp" -#include "files/engine_paths.hpp" -#include "files/files.hpp" +#include "io/engine_paths.hpp" +#include "io/io.hpp" #include "graphics/core/Texture.hpp" #include "logic/scripting/scripting.hpp" #include "objects/rigging.hpp" @@ -20,6 +20,8 @@ #include "Assets.hpp" #include "assetload_funcs.hpp" +namespace fs = std::filesystem; + static debug::Logger logger("assets-loader"); AssetsLoader::AssetsLoader(Assets* assets, const ResPaths* paths) @@ -83,22 +85,21 @@ void AssetsLoader::loadNext() { } } -void addLayouts( +static void add_layouts( const scriptenv& env, const std::string& prefix, - const fs::path& folder, + const io::path& folder, AssetsLoader& loader ) { - if (!fs::is_directory(folder)) { + if (!io::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(); + for (const auto& file : io::directory_iterator(folder)) { + if (file.extension() != ".xml") continue; + std::string name = prefix + ":" + file.stem(); loader.add( AssetType::LAYOUT, - file.u8string(), + file.string(), name, std::make_shared(env) ); @@ -186,8 +187,8 @@ void AssetsLoader::processPreloadList(AssetType tag, const dv::value& list) { } } -void AssetsLoader::processPreloadConfig(const fs::path& file) { - auto root = files::read_json(file); +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"]); @@ -198,8 +199,8 @@ void AssetsLoader::processPreloadConfig(const fs::path& file) { } void AssetsLoader::processPreloadConfigs(const Content* content) { - auto preloadFile = paths->getMainRoot() / fs::path("preload.json"); - if (fs::exists(preloadFile)) { + auto preloadFile = paths->getMainRoot() / "preload.json"; + if (io::exists(preloadFile)) { processPreloadConfig(preloadFile); } if (content == nullptr) { @@ -210,8 +211,8 @@ void AssetsLoader::processPreloadConfigs(const Content* content) { continue; } const auto& pack = entry.second; - auto preloadFile = pack->getInfo().folder / fs::path("preload.json"); - if (fs::exists(preloadFile)) { + auto preloadFile = pack->getInfo().folder / "preload.json"; + if (io::exists(preloadFile)) { processPreloadConfig(preloadFile); } } @@ -230,8 +231,8 @@ void AssetsLoader::addDefaults(AssetsLoader& loader, const Content* content) { 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); + io::path folder = info.folder / "layouts"; + add_layouts(pack->getEnvironment(), info.id, folder, loader); } for (auto& entry : content->getSkeletons()) { @@ -274,20 +275,20 @@ void AssetsLoader::addDefaults(AssetsLoader& loader, const Content* content) { bool AssetsLoader::loadExternalTexture( Assets* assets, const std::string& name, - const std::vector& alternatives + const std::vector& alternatives ) { if (assets->get(name) != nullptr) { return true; } for (auto& path : alternatives) { - if (fs::exists(path)) { + if (io::exists(path)) { try { auto image = imageio::read(path); assets->store(Texture::from(image.get()), name); return true; } catch (const std::exception& err) { logger.error() << "error while loading external " - << path.u8string() << ": " << err.what(); + << path.string() << ": " << err.what(); } } } diff --git a/src/assets/AssetsLoader.hpp b/src/assets/AssetsLoader.hpp index cd6db950..bf117a3d 100644 --- a/src/assets/AssetsLoader.hpp +++ b/src/assets/AssetsLoader.hpp @@ -1,6 +1,5 @@ #pragma once -#include #include #include #include @@ -14,6 +13,7 @@ #include "typedefs.hpp" #include "Assets.hpp" #include "data/dv.hpp" +#include "io/fwd.hpp" class ResPaths; class AssetsLoader; @@ -73,7 +73,7 @@ class AssetsLoader { AssetType tag, const std::string& name, const dv::value& map ); void processPreloadList(AssetType tag, const dv::value& list); - void processPreloadConfig(const std::filesystem::path& file); + void processPreloadConfig(const io::path& file); void processPreloadConfigs(const Content* content); public: AssetsLoader(Assets* assets, const ResPaths* paths); @@ -109,6 +109,6 @@ public: static bool loadExternalTexture( Assets* assets, const std::string& name, - const std::vector& alternatives + const std::vector& alternatives ); }; diff --git a/src/assets/assetload_funcs.cpp b/src/assets/assetload_funcs.cpp index 399e7959..f8bcd84d 100644 --- a/src/assets/assetload_funcs.cpp +++ b/src/assets/assetload_funcs.cpp @@ -13,8 +13,8 @@ #include "coders/vec3.hpp" #include "constants.hpp" #include "debug/Logger.hpp" -#include "files/engine_paths.hpp" -#include "files/files.hpp" +#include "io/engine_paths.hpp" +#include "io/io.hpp" #include "frontend/UiDocument.hpp" #include "graphics/core/Atlas.hpp" #include "graphics/core/Font.hpp" @@ -48,16 +48,14 @@ assetload::postfunc assetload::texture( const std::string& name, const std::shared_ptr& ) { - auto actualFile = paths->find(filename + ".png").u8string(); + auto actualFile = paths->find(filename + ".png"); try { - std::shared_ptr image( - imageio::read(fs::u8path(actualFile)).release() - ); + std::shared_ptr image(imageio::read(actualFile).release()); return [name, image, actualFile](auto assets) { assets->store(Texture::from(image.get()), name); }; } catch (const std::runtime_error& err) { - logger.error() << actualFile << ": " << err.what(); + logger.error() << actualFile.string() << ": " << err.what(); return [](auto) {}; } } @@ -69,11 +67,11 @@ assetload::postfunc assetload::shader( const std::string& name, const std::shared_ptr& ) { - fs::path vertexFile = paths->find(filename + ".glslv"); - fs::path fragmentFile = paths->find(filename + ".glslf"); + io::path vertexFile = paths->find(filename + ".glslv"); + io::path fragmentFile = paths->find(filename + ".glslf"); - std::string vertexSource = files::read_string(vertexFile); - std::string fragmentSource = files::read_string(fragmentFile); + std::string vertexSource = io::read_string(vertexFile); + std::string fragmentSource = io::read_string(fragmentFile); vertexSource = Shader::preprocessor->process(vertexFile, vertexSource); fragmentSource = @@ -82,8 +80,8 @@ assetload::postfunc assetload::shader( return [=](auto assets) { assets->store( Shader::create( - vertexFile.u8string(), - fragmentFile.u8string(), + vertexFile.string(), + fragmentFile.string(), vertexSource, fragmentSource ), @@ -92,8 +90,8 @@ assetload::postfunc assetload::shader( }; } -static bool append_atlas(AtlasBuilder& atlas, const fs::path& file) { - std::string name = file.stem().string(); +static bool append_atlas(AtlasBuilder& atlas, const io::path& file) { + std::string name = file.stem(); // skip duplicates if (atlas.has(name)) { return false; @@ -114,19 +112,19 @@ assetload::postfunc assetload::atlas( auto atlasConfig = std::dynamic_pointer_cast(config); if (atlasConfig && atlasConfig->type == AtlasType::SEPARATE) { for (const auto& file : paths->listdir(directory)) { - if (!imageio::is_read_supported(file.extension().u8string())) + if (!imageio::is_read_supported(file.extension())) continue; loader->add( AssetType::TEXTURE, - directory + "/" + file.stem().u8string(), - name + "/" + file.stem().u8string() + directory + "/" + file.stem(), + name + "/" + file.stem() ); } return [](auto){}; } AtlasBuilder builder; for (const auto& file : paths->listdir(directory)) { - if (!imageio::is_read_supported(file.extension().u8string())) continue; + if (!imageio::is_read_supported(file.extension())) continue; if (!append_atlas(builder, file)) continue; } std::set names = builder.getNames(); @@ -151,7 +149,7 @@ assetload::postfunc assetload::font( for (size_t i = 0; i <= 1024; i++) { std::string pagefile = filename + "_" + std::to_string(i) + ".png"; auto file = paths->find(pagefile); - if (fs::exists(file)) { + if (io::exists(file)) { pages->push_back(imageio::read(file)); } else if (i == 0) { throw std::runtime_error("font must have page 0"); @@ -222,13 +220,13 @@ assetload::postfunc assetload::sound( extension = extensions[i]; // looking for 'sound_name' as base sound auto soundFile = paths->find(file + extension); - if (fs::exists(soundFile)) { + if (io::exists(soundFile)) { baseSound = audio::load_sound(soundFile, keepPCM); break; } // looking for 'sound_name_0' as base sound auto variantFile = paths->find(file + "_0" + extension); - if (fs::exists(variantFile)) { + if (io::exists(variantFile)) { baseSound = audio::load_sound(variantFile, keepPCM); break; } @@ -241,7 +239,7 @@ assetload::postfunc assetload::sound( for (uint i = 1;; i++) { auto variantFile = paths->find(file + "_" + std::to_string(i) + extension); - if (!fs::exists(variantFile)) { + if (!io::exists(variantFile)) { break; } baseSound->variants.emplace_back(audio::load_sound(variantFile, keepPCM)); @@ -272,9 +270,9 @@ assetload::postfunc assetload::model( const std::shared_ptr& ) { auto path = paths->find(file + ".vec3"); - if (fs::exists(path)) { - auto bytes = files::read_bytes_buffer(path); - auto modelVEC3 = std::make_shared(vec3::load(path.u8string(), bytes)); + if (io::exists(path)) { + auto bytes = io::read_bytes_buffer(path); + auto modelVEC3 = std::make_shared(vec3::load(path.string(), bytes)); return [loader, name, modelVEC3=std::move(modelVEC3)](Assets* assets) { for (auto& [modelName, model] : modelVEC3->models) { request_textures(loader, model.model); @@ -292,9 +290,9 @@ assetload::postfunc assetload::model( }; } path = paths->find(file + ".obj"); - auto text = files::read_string(path); + auto text = io::read_string(path); try { - auto model = obj::parse(path.u8string(), text).release(); + auto model = obj::parse(path.string(), text).release(); return [=](Assets* assets) { request_textures(loader, *model); assets->store(std::unique_ptr(model), name); @@ -309,7 +307,7 @@ static void read_anim_file( const std::string& animFile, std::vector>& frameList ) { - auto root = files::read_json(animFile); + auto root = io::read_json(animFile); float frameDuration = DEFAULT_FRAME_DURATION; std::string frameName; @@ -394,21 +392,21 @@ static bool load_animation( std::string animsDir = directory + "/animation"; for (const auto& folder : paths->listdir(animsDir)) { - if (!fs::is_directory(folder)) continue; - if (folder.filename().u8string() != name) continue; - if (fs::is_empty(folder)) continue; + if (!io::is_directory(folder)) continue; + if (folder.name() != name) continue; + //FIXME: if (fs::is_empty(folder)) continue; AtlasBuilder builder; append_atlas(builder, paths->find(directory + "/" + name + ".png")); std::vector> frameList; - std::string animFile = folder.u8string() + "/animation.json"; - if (fs::exists(animFile)) { + std::string animFile = folder.string() + "/animation.json"; + if (io::exists(animFile)) { read_anim_file(animFile, frameList); } for (const auto& file : paths->listdir(animsDir + "/" + name)) { if (!frameList.empty() && - !contains(frameList, file.stem().u8string())) { + !contains(frameList, file.stem())) { continue; } if (!append_atlas(builder, file)) continue; diff --git a/src/audio/audio.cpp b/src/audio/audio.cpp index 8ac8b713..761557af 100644 --- a/src/audio/audio.cpp +++ b/src/audio/audio.cpp @@ -4,6 +4,7 @@ #include #include +#include "io/io.hpp" #include "coders/ogg.hpp" #include "coders/wav.hpp" #include "AL/ALAudio.hpp" @@ -181,11 +182,11 @@ void audio::initialize(bool enabled, AudioSettings& settings) { } } -std::unique_ptr audio::load_PCM(const fs::path& file, bool headerOnly) { - if (!fs::exists(file)) { - throw std::runtime_error("file not found '" + file.u8string() + "'"); +std::unique_ptr audio::load_PCM(const io::path& file, bool headerOnly) { + if (!io::exists(file)) { + throw std::runtime_error("file not found '" + file.string() + "'"); } - std::string ext = file.extension().u8string(); + std::string ext = file.extension(); if (ext == ".wav" || ext == ".WAV") { return wav::load_pcm(file, headerOnly); } else if (ext == ".ogg" || ext == ".OGG") { @@ -194,7 +195,7 @@ std::unique_ptr audio::load_PCM(const fs::path& file, bool headerOnly) { throw std::runtime_error("unsupported audio format"); } -std::unique_ptr audio::load_sound(const fs::path& file, bool keepPCM) { +std::unique_ptr audio::load_sound(const io::path& file, bool keepPCM) { std::shared_ptr pcm( load_PCM(file, !keepPCM && backend->isDummy()).release() ); @@ -207,8 +208,8 @@ std::unique_ptr audio::create_sound( return backend->createSound(std::move(pcm), keepPCM); } -std::unique_ptr audio::open_PCM_stream(const fs::path& file) { - std::string ext = file.extension().u8string(); +std::unique_ptr audio::open_PCM_stream(const io::path& file) { + std::string ext = file.extension(); if (ext == ".wav" || ext == ".WAV") { return wav::create_stream(file); } else if (ext == ".ogg" || ext == ".OGG") { @@ -218,7 +219,7 @@ std::unique_ptr audio::open_PCM_stream(const fs::path& file) { } std::unique_ptr audio::open_stream( - const fs::path& file, bool keepSource + const io::path& file, bool keepSource ) { if (!keepSource && backend->isDummy()) { auto header = load_PCM(file, true); @@ -338,7 +339,7 @@ speakerid_t audio::play( } speakerid_t audio::play_stream( - const fs::path& file, + const io::path& file, glm::vec3 position, bool relative, float volume, diff --git a/src/audio/audio.hpp b/src/audio/audio.hpp index 37ae8e00..5893c334 100644 --- a/src/audio/audio.hpp +++ b/src/audio/audio.hpp @@ -1,14 +1,12 @@ #pragma once -#include #include #include #include #include "typedefs.hpp" #include "settings.hpp" - -namespace fs = std::filesystem; +#include "io/fwd.hpp" namespace audio { /// @brief playing speaker uid @@ -365,7 +363,7 @@ namespace audio { /// @param headerOnly read header only /// @throws std::runtime_error if I/O error ocurred or format is unknown /// @return PCM audio data - std::unique_ptr load_PCM(const fs::path& file, bool headerOnly); + std::unique_ptr load_PCM(const io::path& file, bool headerOnly); /// @brief Load sound from file /// @param file audio file path @@ -373,7 +371,7 @@ namespace audio { /// Sound::getPCM /// @throws std::runtime_error if I/O error ocurred or format is unknown /// @return new Sound instance - std::unique_ptr load_sound(const fs::path& file, bool keepPCM); + std::unique_ptr load_sound(const io::path& file, bool keepPCM); /// @brief Create new sound from PCM data /// @param pcm PCM data @@ -386,14 +384,14 @@ namespace audio { /// @param file audio file path /// @throws std::runtime_error if I/O error ocurred or format is unknown /// @return new PCMStream instance - std::unique_ptr open_PCM_stream(const fs::path& file); + std::unique_ptr open_PCM_stream(const io::path& file); /// @brief Open new audio stream from file /// @param file audio file path /// @param keepSource store PCMStream in stream to make it accessible with /// Stream::getSource /// @return new Stream instance - std::unique_ptr open_stream(const fs::path& file, bool keepSource); + std::unique_ptr open_stream(const io::path& file, bool keepSource); /// @brief Open new audio stream from source /// @param stream PCM data source @@ -464,7 +462,7 @@ namespace audio { /// @param channel channel index /// @return speaker id or 0 speakerid_t play_stream( - const fs::path& file, + const io::path& file, glm::vec3 position, bool relative, float volume, diff --git a/src/coders/GLSLExtension.cpp b/src/coders/GLSLExtension.cpp index 186522a5..b36e7a07 100644 --- a/src/coders/GLSLExtension.cpp +++ b/src/coders/GLSLExtension.cpp @@ -5,13 +5,10 @@ #include #include -#include "files/engine_paths.hpp" -#include "files/files.hpp" +#include "io/engine_paths.hpp" #include "typedefs.hpp" #include "util/stringutil.hpp" -namespace fs = std::filesystem; - void GLSLExtension::setVersion(std::string version) { this->version = std::move(version); } @@ -21,8 +18,8 @@ void GLSLExtension::setPaths(const ResPaths* paths) { } void GLSLExtension::loadHeader(const std::string& name) { - fs::path file = paths->find("shaders/lib/" + name + ".glsl"); - std::string source = files::read_string(file); + io::path file = paths->find("shaders/lib/" + name + ".glsl"); + std::string source = io::read_string(file); addHeader(name, ""); addHeader(name, process(file, source, true)); } @@ -66,7 +63,7 @@ void GLSLExtension::undefine(const std::string& name) { } inline std::runtime_error parsing_error( - const fs::path& file, uint linenum, const std::string& message + const io::path& file, uint linenum, const std::string& message ) { return std::runtime_error( "file " + file.string() + ": " + message + " at line " + @@ -75,7 +72,7 @@ inline std::runtime_error parsing_error( } inline void parsing_warning( - const fs::path& file, uint linenum, const std::string& message + const io::path& file, uint linenum, const std::string& message ) { std::cerr << "file " + file.string() + ": warning: " + message + " at line " + std::to_string(linenum) @@ -87,7 +84,7 @@ inline void source_line(std::stringstream& ss, uint linenum) { } std::string GLSLExtension::process( - const fs::path& file, const std::string& source, bool header + const io::path& file, const std::string& source, bool header ) { std::stringstream ss; size_t pos = 0; diff --git a/src/coders/GLSLExtension.hpp b/src/coders/GLSLExtension.hpp index 6187d7d4..c619c9d0 100644 --- a/src/coders/GLSLExtension.hpp +++ b/src/coders/GLSLExtension.hpp @@ -1,10 +1,11 @@ #pragma once -#include #include #include #include +#include "io/io.hpp" + class ResPaths; class GLSLExtension { @@ -29,7 +30,7 @@ public: bool hasDefine(const std::string& name) const; std::string process( - const std::filesystem::path& file, + const io::path& file, const std::string& source, bool header = false ); diff --git a/src/coders/imageio.cpp b/src/coders/imageio.cpp index a80f422c..36e1f76d 100644 --- a/src/coders/imageio.cpp +++ b/src/coders/imageio.cpp @@ -1,15 +1,12 @@ #include "imageio.hpp" -#include #include #include #include "graphics/core/ImageData.hpp" -#include "files/files.hpp" +#include "io/io.hpp" #include "png.hpp" -namespace fs = std::filesystem; - using image_reader = std::function(const ubyte*, size_t)>; using image_writer = std::function; @@ -30,33 +27,29 @@ bool imageio::is_write_supported(const std::string& extension) { return writers.find(extension) != writers.end(); } -inline std::string extensionOf(const std::string& filename) { - return fs::u8path(filename).extension().u8string(); -} - -std::unique_ptr imageio::read(const fs::path& filename) { - auto found = readers.find(extensionOf(filename.u8string())); +std::unique_ptr imageio::read(const io::path& file) { + auto found = readers.find(file.extension()); if (found == readers.end()) { throw std::runtime_error( - "file format is not supported (read): " + filename.u8string() + "file format is not supported (read): " + file.string() ); } - auto bytes = files::read_bytes_buffer(filename); + auto bytes = io::read_bytes_buffer(file); try { return std::unique_ptr(found->second(bytes.data(), bytes.size())); } catch (const std::runtime_error& err) { throw std::runtime_error( - "could not to load image " + filename.u8string() + ": " + err.what() + "could not to load image " + file.string() + ": " + err.what() ); } } -void imageio::write(const std::string& filename, const ImageData* image) { - auto found = writers.find(extensionOf(filename)); +void imageio::write(const io::path& file, const ImageData* image) { + auto found = writers.find(file.extension()); if (found == writers.end()) { throw std::runtime_error( - "file format is not supported (write): " + filename + "file format is not supported (write): " + file.string() ); } - return found->second(filename, image); + return found->second(io::resolve(file).u8string(), image); } diff --git a/src/coders/imageio.hpp b/src/coders/imageio.hpp index beab88ad..0e59ae37 100644 --- a/src/coders/imageio.hpp +++ b/src/coders/imageio.hpp @@ -2,7 +2,8 @@ #include #include -#include + +#include "io/fwd.hpp" class ImageData; @@ -12,6 +13,6 @@ namespace imageio { bool is_read_supported(const std::string& extension); bool is_write_supported(const std::string& extension); - std::unique_ptr read(const std::filesystem::path& file); - void write(const std::string& filename, const ImageData* image); + std::unique_ptr read(const io::path& file); + void write(const io::path& file, const ImageData* image); } diff --git a/src/coders/ogg.cpp b/src/coders/ogg.cpp index df5f9826..c5c27f49 100644 --- a/src/coders/ogg.cpp +++ b/src/coders/ogg.cpp @@ -5,6 +5,7 @@ #include +#include "io/io.hpp" #include "audio/audio.hpp" #include "debug/Logger.hpp" #include "typedefs.hpp" @@ -43,11 +44,11 @@ static inline std::string vorbis_error_message(int code) { } std::unique_ptr ogg::load_pcm( - const fs::path& file, bool headerOnly + const io::path& file, bool headerOnly ) { OggVorbis_File vf; int code; - if ((code = ov_fopen(file.u8string().c_str(), &vf))) { + if ((code = ov_fopen(io::resolve(file).u8string().c_str(), &vf))) { throw std::runtime_error("vorbis: " + vorbis_error_message(code)); } std::vector data; @@ -166,10 +167,10 @@ public: } }; -std::unique_ptr ogg::create_stream(const fs::path& file) { +std::unique_ptr ogg::create_stream(const io::path& file) { OggVorbis_File vf; int code; - if ((code = ov_fopen(file.u8string().c_str(), &vf))) { + if ((code = ov_fopen(io::resolve(file).u8string().c_str(), &vf))) { throw std::runtime_error("vorbis: " + vorbis_error_message(code)); } return std::make_unique(vf); diff --git a/src/coders/ogg.hpp b/src/coders/ogg.hpp index 67ea2458..86a807e7 100644 --- a/src/coders/ogg.hpp +++ b/src/coders/ogg.hpp @@ -1,6 +1,8 @@ #pragma once -#include +#include + +#include "io/fwd.hpp" namespace audio { struct PCM; @@ -9,9 +11,9 @@ namespace audio { namespace ogg { std::unique_ptr load_pcm( - const std::filesystem::path& file, bool headerOnly + const io::path& file, bool headerOnly ); std::unique_ptr create_stream( - const std::filesystem::path& file + const io::path& file ); } diff --git a/src/coders/png.cpp b/src/coders/png.cpp index b29afd34..ddf5fea8 100644 --- a/src/coders/png.cpp +++ b/src/coders/png.cpp @@ -6,7 +6,7 @@ #include #include "debug/Logger.hpp" -#include "files/files.hpp" +#include "io/io.hpp" #include "graphics/core/GLTexture.hpp" #include "graphics/core/ImageData.hpp" @@ -212,7 +212,7 @@ std::unique_ptr png::load_texture(const ubyte* bytes, size_t size) { } std::unique_ptr png::load_texture(const std::string& filename) { - auto bytes = files::read_bytes_buffer(fs::u8path(filename)); + auto bytes = io::read_bytes_buffer(filename); try { return load_texture(bytes.data(), bytes.size()); } catch (const std::runtime_error& err) { diff --git a/src/coders/toml.cpp b/src/coders/toml.cpp index 080df90e..3896bcec 100644 --- a/src/coders/toml.cpp +++ b/src/coders/toml.cpp @@ -7,7 +7,7 @@ #include #include "data/setting.hpp" -#include "files/settings_io.hpp" +#include "io/settings_io.hpp" #include "util/stringutil.hpp" #include "commons.hpp" diff --git a/src/coders/wav.cpp b/src/coders/wav.cpp index 6ba337fd..1e260a0c 100644 --- a/src/coders/wav.cpp +++ b/src/coders/wav.cpp @@ -6,6 +6,7 @@ #include #include +#include "io/io.hpp" #include "audio/audio.hpp" #include "debug/Logger.hpp" @@ -118,11 +119,11 @@ public: } }; -std::unique_ptr wav::create_stream(const fs::path& file) { - std::ifstream in(file, std::ios::binary); +std::unique_ptr wav::create_stream(const io::path& file) { + std::ifstream in(io::resolve(file), std::ios::binary); if (!in.is_open()) { throw std::runtime_error( - "could not to open file '" + file.u8string() + "'" + "could not to open file '" + file.string() + "'" ); } @@ -234,7 +235,7 @@ std::unique_ptr wav::create_stream(const fs::path& file) { } std::unique_ptr wav::load_pcm( - const fs::path& file, bool headerOnly + const io::path& file, bool headerOnly ) { auto stream = wav::create_stream(file); diff --git a/src/coders/wav.hpp b/src/coders/wav.hpp index 5f5270c2..218b2447 100644 --- a/src/coders/wav.hpp +++ b/src/coders/wav.hpp @@ -1,6 +1,8 @@ #pragma once -#include +#include + +#include "io/fwd.hpp" namespace audio { struct PCM; @@ -9,9 +11,9 @@ namespace audio { namespace wav { std::unique_ptr load_pcm( - const std::filesystem::path& file, bool headerOnly + const io::path& file, bool headerOnly ); std::unique_ptr create_stream( - const std::filesystem::path& file + const io::path& file ); } diff --git a/src/content/ContentLoader.cpp b/src/content/ContentLoader.cpp index ce3f5060..d3df18f1 100644 --- a/src/content/ContentLoader.cpp +++ b/src/content/ContentLoader.cpp @@ -12,7 +12,7 @@ #include "coders/json.hpp" #include "core_defs.hpp" #include "debug/Logger.hpp" -#include "files/files.hpp" +#include "io/io.hpp" #include "items/ItemDef.hpp" #include "logic/scripting/scripting.hpp" #include "objects/rigging.hpp" @@ -43,55 +43,55 @@ ContentLoader::ContentLoader( } static void detect_defs( - const fs::path& folder, + const io::path& folder, const std::string& prefix, std::vector& detected ) { - if (fs::is_directory(folder)) { - for (const auto& entry : fs::directory_iterator(folder)) { - const fs::path& file = entry.path(); - std::string name = file.stem().string(); - if (name[0] == '_') { - continue; - } - if (fs::is_regular_file(file) && files::is_data_file(file)) { - auto map = files::read_object(file); - std::string id = prefix.empty() ? name : prefix + ":" + name; - detected.emplace_back(id); - } else if (fs::is_directory(file) && - file.extension() != fs::u8path(".files")) { - detect_defs(file, name, detected); - } + if (!io::is_directory(folder)) { + return; + } + for (const auto& file : io::directory_iterator(folder)) { + std::string name = file.stem(); + if (name[0] == '_') { + continue; + } + if (io::is_regular_file(file) && io::is_data_file(file)) { + auto map = io::read_object(file); + std::string id = prefix.empty() ? name : prefix + ":" + name; + detected.emplace_back(id); + } else if (io::is_directory(file) && + file.extension() != fs::u8path(".files")) { + detect_defs(file, name, detected); } } } static void detect_defs_pairs( - const fs::path& folder, + const io::path& folder, const std::string& prefix, std::vector>& detected ) { - if (fs::is_directory(folder)) { - for (const auto& entry : fs::directory_iterator(folder)) { - const fs::path& file = entry.path(); - std::string name = file.stem().string(); - if (name[0] == '_') { - continue; - } - if (fs::is_regular_file(file) && files::is_data_file(file)) { - try { - auto map = files::read_object(file); - auto id = prefix.empty() ? name : prefix + ":" + name; - auto caption = util::id_to_caption(id); - map.at("caption").get(caption); - detected.emplace_back(id, name); - } catch (const std::runtime_error& err) { - logger.error() << err.what(); - } - } else if (fs::is_directory(file) && - file.extension() != fs::u8path(".files")) { - detect_defs_pairs(file, name, detected); + if (!io::is_directory(folder)) { + return; + } + for (const auto& file : io::directory_iterator(folder)) { + std::string name = file.stem(); + if (name[0] == '_') { + continue; + } + if (io::is_regular_file(file) && io::is_data_file(file)) { + try { + auto map = io::read_object(file); + auto id = prefix.empty() ? name : prefix + ":" + name; + auto caption = util::id_to_caption(id); + map.at("caption").get(caption); + detected.emplace_back(id, name); + } catch (const std::runtime_error& err) { + logger.error() << err.what(); } + } else if (io::is_directory(file) && + file.extension() != fs::u8path(".files")) { + detect_defs_pairs(file, name, detected); } } } @@ -106,7 +106,7 @@ std::vector> ContentLoader::scanContent( } bool ContentLoader::fixPackIndices( - const fs::path& folder, + const io::path& folder, dv::value& indicesRoot, const std::string& contentSection ) { @@ -146,8 +146,8 @@ void ContentLoader::fixPackIndices() { auto entitiesFolder = folder / ContentPack::ENTITIES_FOLDER; dv::value root; - if (fs::is_regular_file(contentFile)) { - root = files::read_json(contentFile); + if (io::is_regular_file(contentFile)) { + root = io::read_json(contentFile); } else { root = dv::object(); } @@ -159,7 +159,7 @@ void ContentLoader::fixPackIndices() { if (modified) { // rewrite modified json - files::write_json(contentFile, root); + io::write_json(contentFile, root); } } @@ -213,9 +213,9 @@ static void process_method( } void ContentLoader::loadBlock( - Block& def, const std::string& name, const fs::path& file + Block& def, const std::string& name, const io::path& file ) { - auto root = files::read_json(file); + auto root = io::read_json(file); if (def.properties == nullptr) { def.properties = dv::object(); def.properties["name"] = name; @@ -402,9 +402,9 @@ void ContentLoader::loadBlock( } void ContentLoader::loadItem( - ItemDef& def, const std::string& name, const fs::path& file + ItemDef& def, const std::string& name, const io::path& file ) { - auto root = files::read_json(file); + auto root = io::read_json(file); def.properties = root; if (root.has("parent")) { @@ -446,9 +446,9 @@ void ContentLoader::loadItem( } void ContentLoader::loadEntity( - EntityDef& def, const std::string& name, const fs::path& file + EntityDef& def, const std::string& name, const io::path& file ) { - auto root = files::read_json(file); + auto root = io::read_json(file); if (root.has("parent")) { const auto& parentName = root["parent"].asString(); @@ -518,16 +518,16 @@ void ContentLoader::loadEntity( EntityDef& def, const std::string& full, const std::string& name ) { auto folder = pack->folder; - auto configFile = folder / fs::path("entities/" + name + ".json"); - if (fs::exists(configFile)) loadEntity(def, full, configFile); + auto configFile = folder / ("entities/" + name + ".json"); + if (io::exists(configFile)) loadEntity(def, full, configFile); } void ContentLoader::loadBlock( Block& def, const std::string& full, const std::string& name ) { auto folder = pack->folder; - auto configFile = folder / fs::path("blocks/" + name + ".json"); - if (fs::exists(configFile)) loadBlock(def, full, configFile); + auto configFile = folder / ("blocks/" + name + ".json"); + if (io::exists(configFile)) loadBlock(def, full, configFile); if (!def.hidden) { bool created; @@ -549,8 +549,8 @@ void ContentLoader::loadItem( ItemDef& def, const std::string& full, const std::string& name ) { auto folder = pack->folder; - auto configFile = folder / fs::path("items/" + name + ".json"); - if (fs::exists(configFile)) loadItem(def, full, configFile); + auto configFile = folder / ("items/" + name + ".json"); + if (io::exists(configFile)) loadItem(def, full, configFile); } static std::tuple create_unit_id( @@ -566,9 +566,9 @@ static std::tuple create_unit_id( } void ContentLoader::loadBlockMaterial( - BlockMaterial& def, const fs::path& file + BlockMaterial& def, const io::path& file ) { - auto root = files::read_json(file); + auto root = io::read_json(file); root.at("steps-sound").get(def.stepsSound); root.at("place-sound").get(def.placeSound); root.at("break-sound").get(def.breakSound); @@ -577,10 +577,10 @@ void ContentLoader::loadBlockMaterial( void ContentLoader::loadContent(const dv::value& root) { std::vector> pendingDefs; auto getJsonParent = [this](const std::string& prefix, const std::string& name) { - auto configFile = pack->folder / fs::path(prefix + "/" + name + ".json"); + auto configFile = pack->folder / (prefix + "/" + name + ".json"); std::string parent; - if (fs::exists(configFile)) { - auto root = files::read_json(configFile); + if (io::exists(configFile)) { + auto root = io::read_json(configFile); root.at("parent").get(parent); } return parent; @@ -740,16 +740,16 @@ void ContentLoader::loadContent(const dv::value& root) { } static inline void foreach_file( - const fs::path& dir, std::function handler + const io::path& dir, std::function handler ) { - if (fs::is_directory(dir)) { - for (const auto& entry : fs::directory_iterator(dir)) { - const auto& path = entry.path(); - if (fs::is_directory(path)) { - continue; - } - handler(path); + if (!io::is_directory(dir)) { + return; + } + for (const auto& path : io::directory_iterator(dir)) { + if (io::is_directory(path)) { + continue; } + handler(path); } } @@ -761,11 +761,10 @@ void ContentLoader::load() { auto folder = pack->folder; // Load world generators - fs::path generatorsDir = folder / fs::u8path("generators"); - foreach_file(generatorsDir, [this](const fs::path& file) { - std::string name = file.stem().u8string(); - auto [packid, full, filename] = - create_unit_id(pack->id, file.stem().u8string()); + io::path generatorsDir = folder / "generators"; + foreach_file(generatorsDir, [this](const io::path& file) { + std::string name = file.stem(); + auto [packid, full, filename] = create_unit_id(pack->id, name); auto& def = builder.generators.create(full); try { @@ -776,9 +775,9 @@ void ContentLoader::load() { }); // Load pack resources.json - fs::path resourcesFile = folder / fs::u8path("resources.json"); - if (fs::exists(resourcesFile)) { - auto resRoot = files::read_json(resourcesFile); + io::path resourcesFile = folder / "resources.json"; + if (io::exists(resourcesFile)) { + auto resRoot = io::read_json(resourcesFile); for (const auto& [key, arr] : resRoot.asObject()) { if (auto resType = ResourceType_from(key)) { loadResources(*resType, arr); @@ -790,9 +789,9 @@ void ContentLoader::load() { } // Load pack resources aliases - fs::path aliasesFile = folder / fs::u8path("resource-aliases.json"); - if (fs::exists(aliasesFile)) { - auto resRoot = files::read_json(aliasesFile); + io::path aliasesFile = folder / "resource-aliases.json"; + if (io::exists(aliasesFile)) { + auto resRoot = io::read_json(aliasesFile); for (const auto& [key, arr] : resRoot.asObject()) { if (auto resType = ResourceType_from(key)) { loadResourceAliases(*resType, arr); @@ -804,33 +803,32 @@ void ContentLoader::load() { } // Load block materials - fs::path materialsDir = folder / fs::u8path("block_materials"); - if (fs::is_directory(materialsDir)) { - for (const auto& entry : fs::directory_iterator(materialsDir)) { - const auto& file = entry.path(); + io::path materialsDir = folder / "block_materials"; + if (io::is_directory(materialsDir)) { + for (const auto& file : io::directory_iterator(materialsDir)) { auto [packid, full, filename] = - create_unit_id(pack->id, file.stem().u8string()); + create_unit_id(pack->id, file.stem()); loadBlockMaterial( builder.createBlockMaterial(full), - materialsDir / fs::u8path(filename + ".json") + materialsDir / (filename + ".json") ); } } // Load skeletons - fs::path skeletonsDir = folder / fs::u8path("skeletons"); - foreach_file(skeletonsDir, [this](const fs::path& file) { - std::string name = pack->id + ":" + file.stem().u8string(); - std::string text = files::read_string(file); + io::path skeletonsDir = folder / "skeletons"; + foreach_file(skeletonsDir, [this](const io::path& file) { + std::string name = pack->id + ":" + file.stem(); + std::string text = io::read_string(file); builder.add( - rigging::SkeletonConfig::parse(text, file.u8string(), name) + rigging::SkeletonConfig::parse(text, file.string(), name) ); }); // Process content.json and load defined content units auto contentFile = pack->getContentFile(); - if (fs::exists(contentFile)) { - loadContent(files::read_json(contentFile)); + if (io::exists(contentFile)) { + loadContent(io::read_json(contentFile)); } } @@ -844,8 +842,8 @@ static void load_scripts(Content& content, ContentUnitDefs& units) { const auto runtime = content.getPackRuntime(name.substr(0, pos)); const auto& pack = runtime->getInfo(); const auto& folder = pack.folder; - auto scriptfile = folder / fs::path("scripts/" + def->scriptName + ".lua"); - if (fs::is_regular_file(scriptfile)) { + auto scriptfile = folder / ("scripts/" + def->scriptName + ".lua"); + if (io::is_regular_file(scriptfile)) { scripting::load_content_script( runtime->getEnvironment(), name, @@ -866,8 +864,8 @@ void ContentLoader::loadScripts(Content& content) { const auto& folder = pack.folder; // Load main world script - fs::path scriptFile = folder / fs::path("scripts/world.lua"); - if (fs::is_regular_file(scriptFile)) { + io::path scriptFile = folder / "scripts/world.lua"; + if (io::is_regular_file(scriptFile)) { scripting::load_world_script( runtime->getEnvironment(), pack.id, @@ -877,13 +875,13 @@ void ContentLoader::loadScripts(Content& content) { ); } // Load entity components - fs::path componentsDir = folder / fs::u8path("scripts/components"); - foreach_file(componentsDir, [&pack](const fs::path& file) { - auto name = pack.id + ":" + file.stem().u8string(); + io::path componentsDir = folder / "scripts/components"; + foreach_file(componentsDir, [&pack](const io::path& file) { + auto name = pack.id + ":" + file.stem(); scripting::load_entity_component( name, file, - pack.id + ":scripts/components/" + file.filename().u8string() + pack.id + ":scripts/components/" + file.name() ); }); } diff --git a/src/content/ContentLoader.hpp b/src/content/ContentLoader.hpp index d99aa622..f1135ee3 100644 --- a/src/content/ContentLoader.hpp +++ b/src/content/ContentLoader.hpp @@ -1,14 +1,12 @@ #pragma once -#include #include #include +#include "io/io.hpp" #include "content_fwd.hpp" #include "data/dv.hpp" -namespace fs = std::filesystem; - class Block; struct BlockMaterial; struct ItemDef; @@ -43,15 +41,15 @@ class ContentLoader { GeneratorDef& def, const std::string& full, const std::string& name ); - static void loadBlockMaterial(BlockMaterial& def, const fs::path& file); + static void loadBlockMaterial(BlockMaterial& def, const io::path& file); void loadBlock( - Block& def, const std::string& name, const fs::path& file + Block& def, const std::string& name, const io::path& file ); void loadItem( - ItemDef& def, const std::string& name, const fs::path& file + ItemDef& def, const std::string& name, const io::path& file ); void loadEntity( - EntityDef& def, const std::string& name, const fs::path& file + EntityDef& def, const std::string& name, const io::path& file ); void loadResources(ResourceType type, const dv::value& list); void loadResourceAliases(ResourceType type, const dv::value& aliases); @@ -66,7 +64,7 @@ public: // Refresh pack content.json static bool fixPackIndices( - const fs::path& folder, + const io::path& folder, dv::value& indicesRoot, const std::string& contentSection ); diff --git a/src/content/ContentPack.cpp b/src/content/ContentPack.cpp index 7135f231..73a9503e 100644 --- a/src/content/ContentPack.cpp +++ b/src/content/ContentPack.cpp @@ -7,15 +7,15 @@ #include "coders/json.hpp" #include "constants.hpp" #include "data/dv.hpp" -#include "files/engine_paths.hpp" -#include "files/files.hpp" +#include "io/engine_paths.hpp" +#include "io/io.hpp" namespace fs = std::filesystem; ContentPack ContentPack::createCore(const EnginePaths& paths) { return ContentPack { - "core", "Core", ENGINE_VERSION_STRING, "", "", paths.getResourcesFolder(), "res:", {} + "core", "Core", ENGINE_VERSION_STRING, "", "", "res:", "res:", {} }; } @@ -23,7 +23,7 @@ const std::vector ContentPack::RESERVED_NAMES = { "res", "abs", "local", "core", "user", "world", "none", "null"}; contentpack_error::contentpack_error( - std::string packId, fs::path folder, const std::string& message + std::string packId, io::path folder, const std::string& message ) : std::runtime_error(message), packId(std::move(packId)), @@ -33,19 +33,19 @@ contentpack_error::contentpack_error( std::string contentpack_error::getPackId() const { return packId; } -fs::path contentpack_error::getFolder() const { +io::path contentpack_error::getFolder() const { return folder; } -fs::path ContentPack::getContentFile() const { - return folder / fs::path(CONTENT_FILENAME); +io::path ContentPack::getContentFile() const { + return folder / CONTENT_FILENAME; } -bool ContentPack::is_pack(const fs::path& folder) { - return fs::is_regular_file(folder / fs::path(PACKAGE_FILENAME)); +bool ContentPack::is_pack(const io::path& folder) { + return io::is_regular_file(folder / PACKAGE_FILENAME); } -static void checkContentPackId(const std::string& id, const fs::path& folder) { +static void checkContentPackId(const std::string& id, const io::path& folder) { if (id.length() < 2 || id.length() > 24) throw contentpack_error( id, folder, "content-pack id length is out of range [2, 24]" @@ -70,8 +70,8 @@ static void checkContentPackId(const std::string& id, const fs::path& folder) { } } -ContentPack ContentPack::read(const std::string& path, const fs::path& folder) { - auto root = files::read_json(folder / fs::path(PACKAGE_FILENAME)); +ContentPack ContentPack::read(const std::string& path, const io::path& folder) { + auto root = io::read_json(folder / PACKAGE_FILENAME); ContentPack pack; root.at("id").get(pack.id); root.at("title").get(pack.title); @@ -124,21 +124,20 @@ ContentPack ContentPack::read(const std::string& path, const fs::path& folder) { } void ContentPack::scanFolder( - const std::string& path, const fs::path& folder, std::vector& packs + const std::string& path, const io::path& folder, std::vector& packs ) { - if (!fs::is_directory(folder)) { + if (!io::is_directory(folder)) { return; } - for (const auto& entry : fs::directory_iterator(folder)) { - const fs::path& packFolder = entry.path(); - if (!fs::is_directory(packFolder)) continue; + for (const auto& packFolder : io::directory_iterator(folder)) { + if (!io::is_directory(packFolder)) continue; if (!is_pack(packFolder)) continue; try { packs.push_back( - read(path + "/" + packFolder.filename().string(), packFolder) + read(path + "/" + packFolder.name(), packFolder) ); } catch (const contentpack_error& err) { - std::cerr << "package.json error at " << err.getFolder().u8string(); + std::cerr << "package.json error at " << err.getFolder().string(); std::cerr << ": " << err.what() << std::endl; } catch (const std::runtime_error& err) { std::cerr << err.what() << std::endl; @@ -146,26 +145,26 @@ void ContentPack::scanFolder( } } -std::vector ContentPack::worldPacksList(const fs::path& folder) { - fs::path listfile = folder / fs::path("packs.list"); - if (!fs::is_regular_file(listfile)) { +std::vector ContentPack::worldPacksList(const io::path& folder) { + io::path listfile = folder / "packs.list"; + if (!io::is_regular_file(listfile)) { throw std::runtime_error("missing file 'packs.list'"); } - return files::read_list(listfile); + return io::read_list(listfile); } -fs::path ContentPack::findPack( - const EnginePaths* paths, const fs::path& worldDir, const std::string& name +io::path ContentPack::findPack( + const EnginePaths* paths, const io::path& worldDir, const std::string& name ) { - fs::path folder = worldDir / fs::path("content") / fs::path(name); - if (fs::is_directory(folder)) { + io::path folder = worldDir / "content" / name; + if (io::is_directory(folder)) { return folder; } - folder = paths->getUserFilesFolder() / fs::path("content") / fs::path(name); - if (fs::is_directory(folder)) { + folder = io::path("user:content") / name; + if (io::is_directory(folder)) { return folder; } - return paths->getResourcesFolder() / fs::path("content") / fs::path(name); + return io::path("res:content") / name; } ContentPackRuntime::ContentPackRuntime(ContentPack info, scriptenv env) diff --git a/src/content/ContentPack.hpp b/src/content/ContentPack.hpp index e4360bdc..ed7c3eb8 100644 --- a/src/content/ContentPack.hpp +++ b/src/content/ContentPack.hpp @@ -1,27 +1,27 @@ #pragma once -#include #include #include #include #include "typedefs.hpp" #include "content_fwd.hpp" +#include "io/io.hpp" class EnginePaths; class contentpack_error : public std::runtime_error { std::string packId; - std::filesystem::path folder; + io::path folder; public: contentpack_error( std::string packId, - std::filesystem::path folder, + io::path folder, const std::string& message ); std::string getPackId() const; - std::filesystem::path getFolder() const; + io::path getFolder() const; }; enum class DependencyLevel { @@ -42,52 +42,52 @@ struct ContentPack { std::string version = "0.0"; std::string creator = ""; std::string description = "no description"; - std::filesystem::path folder; + io::path folder; std::string path; std::vector dependencies; std::string source = ""; - std::filesystem::path getContentFile() const; + io::path getContentFile() const; static inline const std::string PACKAGE_FILENAME = "package.json"; static inline const std::string CONTENT_FILENAME = "content.json"; - static inline const std::filesystem::path BLOCKS_FOLDER = "blocks"; - static inline const std::filesystem::path ITEMS_FOLDER = "items"; - static inline const std::filesystem::path ENTITIES_FOLDER = "entities"; - static inline const std::filesystem::path GENERATORS_FOLDER = "generators"; + static inline const io::path BLOCKS_FOLDER = "blocks"; + static inline const io::path ITEMS_FOLDER = "items"; + static inline const io::path ENTITIES_FOLDER = "entities"; + static inline const io::path GENERATORS_FOLDER = "generators"; static const std::vector RESERVED_NAMES; - static bool is_pack(const std::filesystem::path& folder); + static bool is_pack(const io::path& folder); static ContentPack read( - const std::string& path, const std::filesystem::path& folder + const std::string& path, const io::path& folder ); static void scanFolder( const std::string& path, - const std::filesystem::path& folder, + const io::path& folder, std::vector& packs ); static std::vector worldPacksList( - const std::filesystem::path& folder + const io::path& folder ); - static std::filesystem::path findPack( + static io::path findPack( const EnginePaths* paths, - const std::filesystem::path& worldDir, + const io::path& worldDir, const std::string& name ); static ContentPack createCore(const EnginePaths&); - static inline std::filesystem::path getFolderFor(ContentType type) { + static inline io::path getFolderFor(ContentType type) { switch (type) { case ContentType::BLOCK: return ContentPack::BLOCKS_FOLDER; case ContentType::ITEM: return ContentPack::ITEMS_FOLDER; case ContentType::ENTITY: return ContentPack::ENTITIES_FOLDER; case ContentType::GENERATOR: return ContentPack::GENERATORS_FOLDER; - case ContentType::NONE: return std::filesystem::u8path(""); - default: return std::filesystem::u8path(""); + case ContentType::NONE: return ""; + default: return ""; } } }; diff --git a/src/content/ContentReport.cpp b/src/content/ContentReport.cpp index 7de04be5..0d8a20e2 100644 --- a/src/content/ContentReport.cpp +++ b/src/content/ContentReport.cpp @@ -4,11 +4,11 @@ #include "coders/json.hpp" #include "constants.hpp" -#include "files/files.hpp" +#include "io/io.hpp" #include "items/ItemDef.hpp" #include "voxels/Block.hpp" #include "world/World.hpp" -#include "files/WorldFiles.hpp" +#include "world/files/WorldFiles.hpp" #include "Content.hpp" ContentReport::ContentReport( @@ -67,7 +67,7 @@ static void process_blocks_data( std::shared_ptr ContentReport::create( const std::shared_ptr& worldFiles, - const fs::path& filename, + const io::path& filename, const Content* content ) { auto worldInfo = worldFiles->readWorldInfo(); @@ -75,7 +75,7 @@ std::shared_ptr ContentReport::create( return nullptr; } - auto root = files::read_json(filename); + auto root = io::read_json(filename); uint regionsVersion = 2U; // old worlds compatibility (pre 0.23) root.at("region-version").get(regionsVersion); auto& blocklist = root["blocks"]; diff --git a/src/content/ContentReport.hpp b/src/content/ContentReport.hpp index 3476b872..325e5f29 100644 --- a/src/content/ContentReport.hpp +++ b/src/content/ContentReport.hpp @@ -1,6 +1,5 @@ #pragma once -#include #include #include #include @@ -10,10 +9,10 @@ #include "data/dv.hpp" #include "typedefs.hpp" #include "Content.hpp" +#include "io/io.hpp" #include "data/StructLayout.hpp" -#include "files/world_regions_fwd.hpp" +#include "world/files/world_regions_fwd.hpp" -namespace fs = std::filesystem; enum class ContentIssueType { REORDER, @@ -139,7 +138,7 @@ public: static std::shared_ptr create( const std::shared_ptr& worldFiles, - const fs::path& filename, + const io::path& filename, const Content* content ); diff --git a/src/content/PacksManager.cpp b/src/content/PacksManager.cpp index a1e7b5ff..4eeaa5eb 100644 --- a/src/content/PacksManager.cpp +++ b/src/content/PacksManager.cpp @@ -8,7 +8,7 @@ PacksManager::PacksManager() = default; -void PacksManager::setSources(std::vector> sources) { +void PacksManager::setSources(std::vector> sources) { this->sources = std::move(sources); } @@ -43,7 +43,7 @@ std::vector PacksManager::getAll( for (auto& name : names) { auto found = packs.find(name); if (found == packs.end()) { - throw contentpack_error(name, fs::path(""), "pack not found"); + throw contentpack_error(name, io::path(), "pack not found"); } packsList.push_back(found->second); } @@ -93,7 +93,7 @@ static bool resolve_dependencies( bool exists = found != packs.end(); if (!exists && dep.level == DependencyLevel::required) { throw contentpack_error( - dep.id, fs::path(), "dependency of '" + pack->id + "'" + dep.id, io::path(), "dependency of '" + pack->id + "'" ); } if (!exists) { @@ -130,7 +130,7 @@ std::vector PacksManager::assemble( for (auto& name : allNames) { auto found = packs.find(name); if (found == packs.end()) { - throw contentpack_error(name, fs::path(""), "pack not found"); + throw contentpack_error(name, io::path(), "pack not found"); } queue.push(&found->second); } diff --git a/src/content/PacksManager.hpp b/src/content/PacksManager.hpp index b60b06ce..32ac43d6 100644 --- a/src/content/PacksManager.hpp +++ b/src/content/PacksManager.hpp @@ -1,21 +1,19 @@ #pragma once -#include #include #include +#include "io/io.hpp" #include "ContentPack.hpp" -namespace fs = std::filesystem; - class PacksManager { std::unordered_map packs; - std::vector> sources; + std::vector> sources; public: PacksManager(); /// @brief Set content packs sources (search folders) - void setSources(std::vector> sources); + void setSources(std::vector> sources); /// @brief Scan sources and collect all found packs excluding duplication. /// Scanning order depends on sources order diff --git a/src/content/loading/GeneratorLoader.cpp b/src/content/loading/GeneratorLoader.cpp index f9f06d80..b14b0b2f 100644 --- a/src/content/loading/GeneratorLoader.cpp +++ b/src/content/loading/GeneratorLoader.cpp @@ -4,8 +4,8 @@ #include "../ContentPack.hpp" -#include "files/files.hpp" -#include "files/engine_paths.hpp" +#include "io/io.hpp" +#include "io/engine_paths.hpp" #include "logic/scripting/scripting.hpp" #include "world/generator/GeneratorDef.hpp" #include "world/generator/VoxelFragment.hpp" @@ -132,21 +132,21 @@ static VoxelStructureMeta load_structure_meta( } static std::vector> load_structures( - const dv::value& map, const fs::path& filesFolder, const ResPaths& paths + const dv::value& map, const io::path& filesFolder, const ResPaths& paths ) { - auto structuresDir = filesFolder / fs::path("fragments"); + auto structuresDir = filesFolder / "fragments"; std::vector> structures; for (auto& [name, config] : map.asObject()) { - auto structFile = structuresDir / fs::u8path(name + ".vox"); - structFile = paths.find(structFile.u8string()); - logger.debug() << "loading voxel fragment " << structFile.u8string(); - if (!fs::exists(structFile)) { + auto structFile = structuresDir / (name + ".vox"); + structFile = paths.find(structFile.string()); + logger.debug() << "loading voxel fragment " << structFile.string(); + if (!io::exists(structFile)) { throw std::runtime_error("structure file does not exist (" + - structFile.u8string()); + structFile.string()); } auto fragment = std::make_unique(); - fragment->deserialize(files::read_binary_json(structFile)); + fragment->deserialize(io::read_binary_json(structFile)); logger.info() << "fragment " << name << " has size [" << fragment->getSize().x << ", " << fragment->getSize().y << ", " << fragment->getSize().z << "]"; @@ -162,7 +162,7 @@ static std::vector> load_structures( static void load_structures( GeneratorDef& def, const dv::value& map, - const fs::path& filesFolder, + const io::path& filesFolder, const ResPaths& paths ) { auto rawStructures = load_structures(map, filesFolder, paths); @@ -178,9 +178,9 @@ static void load_structures( } } -static inline const auto STRUCTURES_FILE = fs::u8path("structures.toml"); -static inline const auto BIOMES_FILE = fs::u8path("biomes.toml"); -static inline const auto GENERATORS_DIR = fs::u8path("generators"); +static inline const io::path STRUCTURES_FILE = "structures.toml"; +static inline const io::path BIOMES_FILE = "biomes.toml"; +static inline const io::path GENERATORS_DIR = "generators"; static void load_biomes(GeneratorDef& def, const dv::value& root) { for (const auto& [biomeName, biomeMap] : root.asObject()) { @@ -198,11 +198,11 @@ void ContentLoader::loadGenerator( ) { auto packDir = pack->folder; auto generatorsDir = packDir / GENERATORS_DIR; - auto generatorFile = generatorsDir / fs::u8path(name + ".toml"); - if (!fs::exists(generatorFile)) { + auto generatorFile = generatorsDir / (name + ".toml"); + if (!io::exists(generatorFile)) { return; } - auto map = files::read_toml(generatorsDir / fs::u8path(name + ".toml")); + auto map = io::read_toml(generatorsDir / (name + ".toml")); map.at("caption").get(def.caption); map.at("biome-parameters").get(def.biomeParameters); map.at("biome-bpd").get(def.biomesBPD); @@ -233,15 +233,15 @@ void ContentLoader::loadGenerator( logger.warning() << "generator has heightmap-inputs but biomes-bpd " "is not equal to heights-bpd, generator will work slower!"; } - auto folder = generatorsDir / fs::u8path(name + ".files"); - auto scriptFile = folder / fs::u8path("script.lua"); + auto folder = generatorsDir / (name + ".files"); + auto scriptFile = folder / "script.lua"; - auto structuresFile = GENERATORS_DIR / fs::u8path(name + ".files") / STRUCTURES_FILE; - auto structuresMap = paths.readCombinedObject(structuresFile.u8string()); - load_structures(def, structuresMap, structuresFile.parent_path(), paths); + auto structuresFile = GENERATORS_DIR / (name + ".files") / STRUCTURES_FILE; + auto structuresMap = paths.readCombinedObject(structuresFile.string()); + load_structures(def, structuresMap, structuresFile.parent(), paths); - auto biomesFile = GENERATORS_DIR / fs::u8path(name + ".files") / BIOMES_FILE; - auto biomesMap = paths.readCombinedObject(biomesFile.u8string()); + auto biomesFile = GENERATORS_DIR / (name + ".files") / BIOMES_FILE; + auto biomesMap = paths.readCombinedObject(biomesFile.string()); if (biomesMap.empty()) { throw std::runtime_error( "generator " + util::quote(def.name) + diff --git a/src/core_defs.cpp b/src/core_defs.cpp index fb8540ba..def96d69 100644 --- a/src/core_defs.cpp +++ b/src/core_defs.cpp @@ -3,15 +3,15 @@ #include "items/ItemDef.hpp" #include "content/Content.hpp" #include "content/ContentBuilder.hpp" -#include "files/files.hpp" -#include "files/engine_paths.hpp" +#include "io/io.hpp" +#include "io/engine_paths.hpp" #include "window/Window.hpp" #include "window/Events.hpp" #include "window/input.hpp" #include "voxels/Block.hpp" // All in-game definitions (blocks, items, etc..) -void corecontent::setup(const EnginePaths& paths, ContentBuilder& builder) { +void corecontent::setup(ContentBuilder& builder) { { Block& block = builder.blocks.create(CORE_AIR); block.replaceable = true; @@ -28,10 +28,10 @@ void corecontent::setup(const EnginePaths& paths, ContentBuilder& builder) { item.iconType = ItemIconType::NONE; } - auto bindsFile = paths.getResourcesFolder()/fs::path("bindings.toml"); - if (fs::is_regular_file(bindsFile)) { + auto bindsFile = "res:bindings.toml"; + if (io::is_regular_file(bindsFile)) { Events::loadBindings( - bindsFile.u8string(), files::read_string(bindsFile), BindType::BIND + bindsFile, io::read_string(bindsFile), BindType::BIND ); } diff --git a/src/core_defs.hpp b/src/core_defs.hpp index 8042ca36..f269af10 100644 --- a/src/core_defs.hpp +++ b/src/core_defs.hpp @@ -28,9 +28,8 @@ inline const std::string BIND_PLAYER_FAST_INTERACTOIN = "player.fast_interaction"; inline const std::string BIND_HUD_INVENTORY = "hud.inventory"; -class EnginePaths; class ContentBuilder; namespace corecontent { - void setup(const EnginePaths& paths, ContentBuilder& builder); + void setup(ContentBuilder& builder); } diff --git a/src/engine/Engine.cpp b/src/engine/Engine.cpp index 73704468..80d774be 100644 --- a/src/engine/Engine.cpp +++ b/src/engine/Engine.cpp @@ -16,7 +16,7 @@ #include "content/ContentBuilder.hpp" #include "content/ContentLoader.hpp" #include "core_defs.hpp" -#include "files/files.hpp" +#include "io/io.hpp" #include "frontend/locale.hpp" #include "frontend/menu.hpp" #include "frontend/screens/Screen.hpp" @@ -52,10 +52,10 @@ static debug::Logger logger("engine"); namespace fs = std::filesystem; -static std::unique_ptr load_icon(const fs::path& resdir) { +static std::unique_ptr load_icon() { try { - auto file = resdir / fs::u8path("textures/misc/icon.png"); - if (fs::exists(file)) { + auto file = "res:textures/misc/icon.png"; + if (io::exists(file)) { return imageio::read(file); } } catch (const std::exception& err) { @@ -101,7 +101,7 @@ void Engine::initialize(CoreParameters coreParameters) { throw initialize_error("could not initialize window"); } time.set(Window::time()); - if (auto icon = load_icon(resdir)) { + if (auto icon = load_icon()) { icon->flipY(); Window::setIcon(icon.get()); } @@ -118,7 +118,7 @@ void Engine::initialize(CoreParameters coreParameters) { if (langNotSet) { settings.ui.language.set(langs::locale_by_envlocale( platform::detect_locale(), - paths.getResourcesFolder() + "res:" )); } scripting::initialize(this); @@ -128,14 +128,14 @@ void Engine::initialize(CoreParameters coreParameters) { keepAlive(settings.ui.language.observe([this](auto lang) { setLanguage(lang); }, true)); - basePacks = files::read_list(resdir/fs::path("config/builtins.list")); + basePacks = io::read_list("res:config/builtins.list"); } void Engine::loadSettings() { - fs::path settings_file = paths.getSettingsFile(); - if (fs::is_regular_file(settings_file)) { + io::path settings_file = paths.getSettingsFile(); + if (io::is_regular_file(settings_file)) { logger.info() << "loading settings"; - std::string text = files::read_string(settings_file); + std::string text = io::read_string(settings_file); try { toml::parse(*settingsHandler, settings_file.string(), text); } catch (const parsing_error& err) { @@ -146,11 +146,11 @@ void Engine::loadSettings() { } void Engine::loadControls() { - fs::path controls_file = paths.getControlsFile(); - if (fs::is_regular_file(controls_file)) { + io::path controls_file = paths.getControlsFile(); + if (io::is_regular_file(controls_file)) { logger.info() << "loading controls"; - std::string text = files::read_string(controls_file); - Events::loadBindings(controls_file.u8string(), text, BindType::BIND); + std::string text = io::read_string(controls_file); + Events::loadBindings(controls_file.string(), text, BindType::BIND); } } @@ -171,9 +171,9 @@ void Engine::updateHotkeys() { void Engine::saveScreenshot() { auto image = Window::takeScreenshot(); image->flipY(); - fs::path filename = paths.getNewScreenshotFile("png"); + io::path filename = paths.getNewScreenshotFile("png"); imageio::write(filename.string(), image.get()); - logger.info() << "saved screenshot as " << filename.u8string(); + logger.info() << "saved screenshot as " << filename.string(); } void Engine::run() { @@ -219,10 +219,10 @@ void Engine::renderFrame() { void Engine::saveSettings() { logger.info() << "saving settings"; - files::write_string(paths.getSettingsFile(), toml::stringify(*settingsHandler)); + io::write_string(paths.getSettingsFile(), toml::stringify(*settingsHandler)); if (!params.headless) { logger.info() << "saving bindings"; - files::write_string(paths.getControlsFile(), Events::writeBindings()); + io::write_string(paths.getControlsFile(), Events::writeBindings()); } } @@ -264,12 +264,12 @@ cmd::CommandsInterpreter* Engine::getCommandsInterpreter() { return interpreter.get(); } -PacksManager Engine::createPacksManager(const fs::path& worldFolder) { +PacksManager Engine::createPacksManager(const io::path& worldFolder) { PacksManager manager; manager.setSources({ - {"world:content", worldFolder.empty() ? worldFolder : worldFolder/fs::path("content")}, - {"user:content", paths.getUserFilesFolder()/fs::path("content")}, - {"res:content", paths.getResourcesFolder()/fs::path("content")} + {"world:content", worldFolder.empty() ? worldFolder : worldFolder / "content"}, + {"user:content", "user:content"}, + {"res:content", "res:content"} }); return manager; } @@ -325,12 +325,12 @@ void Engine::loadAssets() { } } -static void load_configs(const fs::path& root) { - auto configFolder = root/fs::path("config"); - auto bindsFile = configFolder/fs::path("bindings.toml"); - if (fs::is_regular_file(bindsFile)) { +static void load_configs(const io::path& root) { + auto configFolder = root / "config"; + auto bindsFile = configFolder / "bindings.toml"; + if (io::is_regular_file(bindsFile)) { Events::loadBindings( - bindsFile.u8string(), files::read_string(bindsFile), BindType::BIND + bindsFile.string(), io::read_string(bindsFile), BindType::BIND ); } } @@ -338,15 +338,13 @@ static void load_configs(const fs::path& root) { void Engine::loadContent() { scripting::cleanup(); - auto resdir = paths.getResourcesFolder(); - std::vector names; for (auto& pack : contentPacks) { names.push_back(pack.id); } ContentBuilder contentBuilder; - corecontent::setup(paths, contentBuilder); + corecontent::setup(contentBuilder); paths.setContentPacks(&contentPacks); PacksManager manager = createPacksManager(paths.getCurrentWorldFolder()); @@ -363,7 +361,7 @@ void Engine::loadContent() { for (auto& pack : contentPacks) { resRoots.push_back({pack.id, pack.folder}); } - resPaths = std::make_unique(resdir, resRoots); + resPaths = std::make_unique("res:", resRoots); // Load content { @@ -380,7 +378,7 @@ void Engine::loadContent() { ContentLoader::loadScripts(*content); - langs::setup(resdir, langs::current->getId(), contentPacks); + langs::setup("res:", langs::current->getId(), contentPacks); if (!isHeadless()) { loadAssets(); onAssetsLoaded(); @@ -389,23 +387,22 @@ void Engine::loadContent() { void Engine::resetContent() { scripting::cleanup(); - auto resdir = paths.getResourcesFolder(); std::vector resRoots; { auto pack = ContentPack::createCore(paths); resRoots.push_back({"core", pack.folder}); load_configs(pack.folder); } - auto manager = createPacksManager(fs::path()); + auto manager = createPacksManager(io::path()); manager.scan(); for (const auto& pack : manager.getAll(basePacks)) { resRoots.push_back({pack.id, pack.folder}); } - resPaths = std::make_unique(resdir, resRoots); + resPaths = std::make_unique("res:", resRoots); contentPacks.clear(); content.reset(); - langs::setup(resdir, langs::current->getId(), contentPacks); + langs::setup("res:", langs::current->getId(), contentPacks); if (!isHeadless()) { loadAssets(); onAssetsLoaded(); @@ -414,15 +411,14 @@ void Engine::resetContent() { contentPacks = manager.getAll(basePacks); } -void Engine::loadWorldContent(const fs::path& folder) { +void Engine::loadWorldContent(const io::path& folder) { contentPacks.clear(); auto packNames = ContentPack::worldPacksList(folder); PacksManager manager; manager.setSources( - {{"world:content", - folder.empty() ? folder : folder / fs::path("content")}, - {"user:content", paths.getUserFilesFolder() / fs::path("content")}, - {"res:content", paths.getResourcesFolder() / fs::path("content")}} + {{"world:content", folder.empty() ? folder : folder / "content"}, + {"user:content", "user:content"}, + {"res:content", "res:content"}} ); manager.scan(); contentPacks = manager.getAll(manager.assemble(packNames)); @@ -445,7 +441,7 @@ void Engine::setScreen(std::shared_ptr screen) { } void Engine::setLanguage(std::string locale) { - langs::setup(paths.getResourcesFolder(), std::move(locale), contentPacks); + langs::setup("res:", std::move(locale), contentPacks); } void Engine::onWorldOpen(std::unique_ptr level, int64_t localPlayer) { diff --git a/src/engine/Engine.hpp b/src/engine/Engine.hpp index d4cbf40a..b05881f6 100644 --- a/src/engine/Engine.hpp +++ b/src/engine/Engine.hpp @@ -8,13 +8,12 @@ #include "content/content_fwd.hpp" #include "content/ContentPack.hpp" #include "content/PacksManager.hpp" -#include "files/engine_paths.hpp" -#include "files/settings_io.hpp" +#include "io/engine_paths.hpp" +#include "io/settings_io.hpp" #include "util/ObjectsKeeper.hpp" #include "PostRunnables.hpp" #include "Time.hpp" -#include #include #include #include @@ -48,8 +47,8 @@ public: struct CoreParameters { bool headless = false; bool testMode = false; - std::filesystem::path resFolder {"res"}; - std::filesystem::path userFolder {"."}; + std::filesystem::path resFolder = "res"; + std::filesystem::path userFolder = "."; std::filesystem::path scriptFile; }; @@ -122,7 +121,7 @@ public: /// @brief Collect world content-packs and load content /// @see loadContent /// @param folder world folder - void loadWorldContent(const fs::path& folder); + void loadWorldContent(const io::path& folder); /// @brief Collect all available content-packs from res/content void loadAllPacks(); @@ -172,7 +171,7 @@ public: EngineController* getController(); cmd::CommandsInterpreter* getCommandsInterpreter(); - PacksManager createPacksManager(const fs::path& worldFolder); + PacksManager createPacksManager(const io::path& worldFolder); void setLevelConsumer(OnWorldOpen levelConsumer); diff --git a/src/engine/ServerMainloop.cpp b/src/engine/ServerMainloop.cpp index 3651d23a..27c35bdd 100644 --- a/src/engine/ServerMainloop.cpp +++ b/src/engine/ServerMainloop.cpp @@ -34,8 +34,10 @@ void ServerMainloop::run() { setLevel(std::move(level)); }); - logger.info() << "starting test " << coreParams.scriptFile; - auto process = scripting::start_coroutine(coreParams.scriptFile); + logger.info() << "starting test " << coreParams.scriptFile.string(); + auto process = scripting::start_coroutine( + "script:" + coreParams.scriptFile.filename().u8string() + ); double targetDelta = 1.0 / static_cast(TPS); double delta = targetDelta; @@ -79,7 +81,7 @@ void ServerMainloop::run() { void ServerMainloop::setLevel(std::unique_ptr level) { if (level == nullptr) { controller->onWorldQuit(); - engine.getPaths().setCurrentWorldFolder(fs::path()); + engine.getPaths().setCurrentWorldFolder(""); controller = nullptr; } else { controller = std::make_unique( diff --git a/src/files/engine_paths.cpp b/src/files/engine_paths.cpp deleted file mode 100644 index 2c8cd02c..00000000 --- a/src/files/engine_paths.cpp +++ /dev/null @@ -1,340 +0,0 @@ -#include "engine_paths.hpp" - -#include -#include -#include -#include -#include -#include "typedefs.hpp" -#include "util/stringutil.hpp" -#include - -#include "WorldFiles.hpp" -#include "debug/Logger.hpp" - -static debug::Logger logger("engine-paths"); - -static inline auto SCREENSHOTS_FOLDER = std::filesystem::u8path("screenshots"); -static inline auto CONTENT_FOLDER = std::filesystem::u8path("content"); -static inline auto WORLDS_FOLDER = std::filesystem::u8path("worlds"); -static inline auto CONFIG_FOLDER = std::filesystem::u8path("config"); -static inline auto EXPORT_FOLDER = std::filesystem::u8path("export"); -static inline auto CONTROLS_FILE = std::filesystem::u8path("controls.toml"); -static inline auto SETTINGS_FILE = std::filesystem::u8path("settings.toml"); - -static std::filesystem::path toCanonic(std::filesystem::path path) { - std::stack parts; - path = path.lexically_normal(); - do { - parts.push(path.filename().u8string()); - path = path.parent_path(); - } while (!path.empty()); - - path = fs::u8path(""); - - while (!parts.empty()) { - const std::string part = parts.top(); - parts.pop(); - if (part == ".") { - continue; - } - if (part == "..") { - throw files_access_error("entry point reached"); - } - - path = path / std::filesystem::path(part); - } - return path; -} - -void EnginePaths::prepare() { - if (!fs::is_directory(resourcesFolder)) { - throw std::runtime_error( - resourcesFolder.u8string() + " is not a directory" - ); - } - if (!fs::is_directory(userFilesFolder)) { - fs::create_directories(userFilesFolder); - } - - logger.info() << "resources folder: " << fs::canonical(resourcesFolder).u8string(); - logger.info() << "user files folder: " << fs::canonical(userFilesFolder).u8string(); - - auto contentFolder = userFilesFolder / CONTENT_FOLDER; - if (!fs::is_directory(contentFolder)) { - fs::create_directories(contentFolder); - } - auto exportFolder = userFilesFolder / EXPORT_FOLDER; - if (!fs::is_directory(exportFolder)) { - fs::create_directories(exportFolder); - } - auto configFolder = userFilesFolder / CONFIG_FOLDER; - if (!fs::is_directory(configFolder)) { - fs::create_directories(configFolder); - } -} - -std::filesystem::path EnginePaths::getUserFilesFolder() const { - return userFilesFolder; -} - -std::filesystem::path EnginePaths::getResourcesFolder() const { - return resourcesFolder; -} - -std::filesystem::path EnginePaths::getNewScreenshotFile(const std::string& ext) { - auto folder = userFilesFolder / SCREENSHOTS_FOLDER; - if (!fs::is_directory(folder)) { - fs::create_directory(folder); - } - - auto t = std::time(nullptr); - auto tm = *std::localtime(&t); - - const char* format = "%Y-%m-%d_%H-%M-%S"; - std::stringstream ss; - ss << std::put_time(&tm, format); - std::string datetimestr = ss.str(); - - auto filename = folder / fs::u8path("screenshot-" + datetimestr + "." + ext); - uint index = 0; - while (fs::exists(filename)) { - filename = folder / fs::u8path( - "screenshot-" + datetimestr + "-" + - std::to_string(index) + "." + ext - ); - index++; - } - return filename; -} - -std::filesystem::path EnginePaths::getWorldsFolder() const { - return userFilesFolder / WORLDS_FOLDER; -} - -std::filesystem::path EnginePaths::getConfigFolder() const { - return userFilesFolder / CONFIG_FOLDER; -} - -std::filesystem::path EnginePaths::getCurrentWorldFolder() { - return currentWorldFolder; -} - -std::filesystem::path EnginePaths::getWorldFolderByName(const std::string& name) { - return getWorldsFolder() / std::filesystem::path(name); -} - -std::filesystem::path EnginePaths::getControlsFile() const { - return userFilesFolder / CONTROLS_FILE; -} - -std::filesystem::path EnginePaths::getSettingsFile() const { - return userFilesFolder / SETTINGS_FILE; -} - -std::vector EnginePaths::scanForWorlds() const { - std::vector folders; - - auto folder = getWorldsFolder(); - if (!fs::is_directory(folder)) return folders; - - for (const auto& entry : fs::directory_iterator(folder)) { - if (!entry.is_directory()) { - continue; - } - const auto& worldFolder = entry.path(); - auto worldFile = worldFolder / fs::u8path(WorldFiles::WORLD_FILE); - if (!fs::is_regular_file(worldFile)) { - continue; - } - folders.push_back(worldFolder); - } - std::sort( - folders.begin(), - folders.end(), - [](std::filesystem::path a, std::filesystem::path b) { - a = a / fs::u8path(WorldFiles::WORLD_FILE); - b = b / fs::u8path(WorldFiles::WORLD_FILE); - return fs::last_write_time(a) > fs::last_write_time(b); - } - ); - return folders; -} - -void EnginePaths::setUserFilesFolder(std::filesystem::path folder) { - this->userFilesFolder = std::move(folder); -} - -void EnginePaths::setResourcesFolder(std::filesystem::path folder) { - this->resourcesFolder = std::move(folder); -} - -void EnginePaths::setScriptFolder(std::filesystem::path folder) { - this->scriptFolder = std::move(folder); -} - -void EnginePaths::setCurrentWorldFolder(std::filesystem::path folder) { - this->currentWorldFolder = std::move(folder); -} - -void EnginePaths::setContentPacks(std::vector* contentPacks) { - this->contentPacks = contentPacks; -} - -std::tuple EnginePaths::parsePath(std::string_view path) { - size_t separator = path.find(':'); - if (separator == std::string::npos) { - return {"", std::string(path)}; - } - auto prefix = std::string(path.substr(0, separator)); - auto filename = std::string(path.substr(separator + 1)); - return {prefix, filename}; -} - -std::filesystem::path EnginePaths::resolve( - const std::string& path, bool throwErr -) const { - auto [prefix, filename] = EnginePaths::parsePath(path); - if (prefix.empty()) { - throw files_access_error("no entry point specified"); - } - filename = toCanonic(fs::u8path(filename)).u8string(); - - if (prefix == "res" || prefix == "core") { - return resourcesFolder / fs::u8path(filename); - } - if (prefix == "user") { - return userFilesFolder / fs::u8path(filename); - } - if (prefix == "config") { - return getConfigFolder() / fs::u8path(filename); - } - if (prefix == "world") { - return currentWorldFolder / fs::u8path(filename); - } - if (prefix == "export") { - return userFilesFolder / EXPORT_FOLDER / fs::u8path(filename); - } - if (prefix == "script" && scriptFolder) { - return scriptFolder.value() / fs::u8path(filename); - } - if (contentPacks) { - for (auto& pack : *contentPacks) { - if (pack.id == prefix) { - return pack.folder / fs::u8path(filename); - } - } - } - if (throwErr) { - throw files_access_error("unknown entry point '" + prefix + "'"); - } - return std::filesystem::path(filename); -} - -ResPaths::ResPaths(std::filesystem::path mainRoot, std::vector roots) - : mainRoot(std::move(mainRoot)), roots(std::move(roots)) { -} - -std::filesystem::path ResPaths::find(const std::string& filename) const { - for (int i = roots.size() - 1; i >= 0; i--) { - auto& root = roots[i]; - auto file = root.path / fs::u8path(filename); - if (fs::exists(file)) { - return file; - } - } - return mainRoot / fs::u8path(filename); -} - -std::string ResPaths::findRaw(const std::string& filename) const { - for (int i = roots.size() - 1; i >= 0; i--) { - auto& root = roots[i]; - if (fs::exists(root.path / std::filesystem::path(filename))) { - return root.name + ":" + filename; - } - } - throw std::runtime_error("could not to find file " + util::quote(filename)); -} - -std::vector ResPaths::listdirRaw(const std::string& folderName) const { - std::vector entries; - for (int i = roots.size() - 1; i >= 0; i--) { - auto& root = roots[i]; - auto folder = root.path / fs::u8path(folderName); - if (!fs::is_directory(folder)) continue; - for (const auto& entry : fs::directory_iterator(folder)) { - auto name = entry.path().filename().u8string(); - entries.emplace_back(root.name + ":" + folderName + "/" + name); - } - } - return entries; -} - -std::vector ResPaths::listdir( - const std::string& folderName -) const { - std::vector entries; - for (int i = roots.size() - 1; i >= 0; i--) { - auto& root = roots[i]; - std::filesystem::path folder = root.path / fs::u8path(folderName); - if (!fs::is_directory(folder)) continue; - for (const auto& entry : fs::directory_iterator(folder)) { - entries.push_back(entry.path()); - } - } - return entries; -} - -dv::value ResPaths::readCombinedList(const std::string& filename) const { - dv::value list = dv::list(); - for (const auto& root : roots) { - auto path = root.path / fs::u8path(filename); - if (!fs::exists(path)) { - continue; - } - try { - auto value = files::read_object(path); - if (!value.isList()) { - logger.warning() << "reading combined list " << root.name << ":" - << filename << " is not a list (skipped)"; - continue; - } - for (const auto& elem : value) { - list.add(elem); - } - } catch (const std::runtime_error& err) { - logger.warning() << "reading combined list " << root.name << ":" - << filename << ": " << err.what(); - } - } - return list; -} - -dv::value ResPaths::readCombinedObject(const std::string& filename) const { - dv::value object = dv::object(); - for (const auto& root : roots) { - auto path = root.path / fs::u8path(filename); - if (!fs::exists(path)) { - continue; - } - try { - auto value = files::read_object(path); - if (!value.isObject()) { - logger.warning() - << "reading combined object " << root.name << ": " - << filename << " is not an object (skipped)"; - } - for (const auto& [key, element] : value.asObject()) { - object[key] = element; - } - } catch (const std::runtime_error& err) { - logger.warning() << "reading combined object " << root.name << ":" - << filename << ": " << err.what(); - } - } - return object; -} - -const std::filesystem::path& ResPaths::getMainRoot() const { - return mainRoot; -} diff --git a/src/files/files.cpp b/src/files/files.cpp deleted file mode 100644 index f7d3f460..00000000 --- a/src/files/files.cpp +++ /dev/null @@ -1,199 +0,0 @@ -#include "files.hpp" - -#include - -#include -#include -#include -#include - -#include "coders/commons.hpp" -#include "coders/gzip.hpp" -#include "coders/json.hpp" -#include "coders/toml.hpp" -#include "util/stringutil.hpp" - -namespace fs = std::filesystem; - -files::rafile::rafile(const fs::path& filename) - : file(filename, std::ios::binary | std::ios::ate) { - if (!file) { - throw std::runtime_error("could not to open file " + filename.string()); - } - filelength = file.tellg(); - file.seekg(0); -} - -size_t files::rafile::length() const { - return filelength; -} - -void files::rafile::seekg(std::streampos pos) { - file.seekg(pos); -} - -void files::rafile::read(char* buffer, std::streamsize size) { - file.read(buffer, size); -} - -bool files::write_bytes( - const fs::path& filename, const ubyte* data, size_t size -) { - std::ofstream output(filename, std::ios::binary); - if (!output.is_open()) return false; - output.write((const char*)data, size); - output.close(); - return true; -} - -uint files::append_bytes( - const fs::path& filename, const ubyte* data, size_t size -) { - std::ofstream output(filename, std::ios::binary | std::ios::app); - if (!output.is_open()) return 0; - uint position = output.tellp(); - output.write((const char*)data, size); - output.close(); - return position; -} - -bool files::read(const fs::path& filename, char* data, size_t size) { - std::ifstream output(filename, std::ios::binary); - if (!output.is_open()) return false; - output.read(data, size); - output.close(); - return true; -} - -util::Buffer files::read_bytes_buffer(const fs::path& path) { - size_t size; - auto bytes = files::read_bytes(path, size); - return util::Buffer(std::move(bytes), size); -} - -std::unique_ptr files::read_bytes( - const fs::path& filename, size_t& length -) { - std::ifstream input(filename, std::ios::binary); - if (!input.is_open()) { - throw std::runtime_error( - "could not to load file '" + filename.string() + "'" - ); - } - input.seekg(0, std::ios_base::end); - length = input.tellg(); - input.seekg(0, std::ios_base::beg); - - auto data = std::make_unique(length); - input.read((char*)data.get(), length); - input.close(); - return data; -} - -std::vector files::read_bytes(const fs::path& filename) { - std::ifstream input(filename, std::ios::binary); - if (!input.is_open()) return {}; - input.seekg(0, std::ios_base::end); - size_t length = input.tellg(); - input.seekg(0, std::ios_base::beg); - - std::vector data(length); - data.resize(length); - input.read((char*)data.data(), length); - input.close(); - return data; -} - -std::string files::read_string(const fs::path& filename) { - size_t size; - auto bytes = read_bytes(filename, size); - return std::string((const char*)bytes.get(), size); -} - -bool files::write_string(const fs::path& filename, std::string_view content) { - std::ofstream file(filename); - if (!file) { - return false; - } - file << content; - return true; -} - -bool files::write_json( - const fs::path& filename, const dv::value& obj, bool nice -) { - return files::write_string(filename, json::stringify(obj, nice, " ")); -} - -bool files::write_binary_json( - const fs::path& filename, const dv::value& obj, bool compression -) { - auto bytes = json::to_binary(obj, compression); - return files::write_bytes(filename, bytes.data(), bytes.size()); -} - -dv::value files::read_json(const fs::path& filename) { - std::string text = files::read_string(filename); - return json::parse(filename.string(), text); -} - -dv::value files::read_binary_json(const fs::path& file) { - size_t size; - auto bytes = files::read_bytes(file, size); - return json::from_binary(bytes.get(), size); -} - -dv::value files::read_toml(const fs::path& file) { - return toml::parse(file.u8string(), files::read_string(file)); -} - -std::vector files::read_list(const fs::path& filename) { - std::ifstream file(filename); - if (!file) { - throw std::runtime_error( - "could not to open file " + filename.u8string() - ); - } - std::vector lines; - std::string line; - while (std::getline(file, line)) { - util::trim(line); - if (line.length() == 0) continue; - if (line[0] == '#') continue; - lines.push_back(line); - } - return lines; -} - -#include - -#include "coders/json.hpp" -#include "coders/toml.hpp" - -using DecodeFunc = dv::value(*)(std::string_view, std::string_view); - -static std::map data_decoders { - {fs::u8path(".json"), json::parse}, - {fs::u8path(".toml"), toml::parse}, -}; - -bool files::is_data_file(const fs::path& file) { - return is_data_interchange_format(file.extension()); -} - -bool files::is_data_interchange_format(const fs::path& ext) { - return data_decoders.find(ext) != data_decoders.end(); -} - -dv::value files::read_object(const fs::path& file) { - const auto& found = data_decoders.find(file.extension()); - if (found == data_decoders.end()) { - throw std::runtime_error("unknown file format"); - } - auto text = read_string(file); - try { - return found->second(file.u8string(), text); - } catch (const parsing_error& err) { - throw std::runtime_error(err.errorLog()); - } -} diff --git a/src/files/files.hpp b/src/files/files.hpp deleted file mode 100644 index 4cf509d4..00000000 --- a/src/files/files.hpp +++ /dev/null @@ -1,80 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include - -#include "typedefs.hpp" -#include "data/dv.hpp" -#include "util/Buffer.hpp" - -namespace fs = std::filesystem; - -namespace files { - /// @brief Read-only random access file - class rafile { - std::ifstream file; - size_t filelength; - public: - rafile(const fs::path& filename); - - void seekg(std::streampos pos); - void read(char* buffer, std::streamsize size); - size_t length() const; - }; - - /// @brief Write bytes array to the file without any extra data - /// @param file target file - /// @param data data bytes array - /// @param size size of data bytes array - bool write_bytes(const fs::path& file, const ubyte* data, size_t size); - - /// @brief Append bytes array to the file without any extra data - /// @param file target file - /// @param data data bytes array - /// @param size size of data bytes array - uint append_bytes(const fs::path& file, const ubyte* data, size_t size); - - /// @brief Write string to the file - bool write_string(const fs::path& filename, std::string_view content); - - /// @brief Write dynamic data to the JSON file - /// @param nice if true, human readable format will be used, otherwise - /// minimal - bool write_json( - const fs::path& filename, const dv::value& obj, bool nice = true - ); - - /// @brief Write dynamic data to the binary JSON file - /// (see src/coders/binary_json_spec.md) - /// @param compressed use gzip compression - bool write_binary_json( - const fs::path& filename, - const dv::value& obj, - bool compressed = false - ); - - bool read(const fs::path&, char* data, size_t size); - util::Buffer read_bytes_buffer(const fs::path&); - std::unique_ptr read_bytes(const fs::path&, size_t& length); - std::vector read_bytes(const fs::path&); - std::string read_string(const fs::path& filename); - - /// @brief Read JSON or BJSON file - /// @param file *.json or *.bjson file - dv::value read_json(const fs::path& file); - - dv::value read_binary_json(const fs::path& file); - - /// @brief Read TOML file - /// @param file *.toml file - dv::value read_toml(const fs::path& file); - - std::vector read_list(const fs::path& file); - - bool is_data_file(const fs::path& file); - bool is_data_interchange_format(const fs::path& ext); - dv::value read_object(const fs::path& file); -} diff --git a/src/frontend/UiDocument.cpp b/src/frontend/UiDocument.cpp index 2108b46e..1dfa18aa 100644 --- a/src/frontend/UiDocument.cpp +++ b/src/frontend/UiDocument.cpp @@ -2,7 +2,7 @@ #include -#include "files/files.hpp" +#include "io/io.hpp" #include "graphics/ui/elements/UINode.hpp" #include "graphics/ui/elements/InventoryView.hpp" #include "graphics/ui/gui_xml.hpp" @@ -56,22 +56,22 @@ scriptenv UiDocument::getEnvironment() const { std::unique_ptr UiDocument::read( const scriptenv& penv, const std::string& name, - const fs::path& file, + const io::path& file, const std::string& fileName ) { - const std::string text = files::read_string(file); - auto xmldoc = xml::parse(file.u8string(), text); + const std::string text = io::read_string(file); + auto xmldoc = xml::parse(file.string(), text); auto env = penv == nullptr ? scripting::create_doc_environment(scripting::get_root_environment(), name) : scripting::create_doc_environment(penv, name); gui::UiXmlReader reader(env); - auto view = reader.readXML(file.u8string(), *xmldoc->getRoot()); + auto view = reader.readXML(file.string(), *xmldoc->getRoot()); view->setId("root"); uidocscript script {}; - auto scriptFile = fs::path(file.u8string()+".lua"); - if (fs::is_regular_file(scriptFile)) { + auto scriptFile = io::path(file.string()+".lua"); + if (io::is_regular_file(scriptFile)) { scripting::load_layout_script( env, name, scriptFile, fileName + ".lua", script ); @@ -80,8 +80,8 @@ std::unique_ptr UiDocument::read( } std::shared_ptr UiDocument::readElement( - const fs::path& file, const std::string& fileName + const io::path& file, const std::string& fileName ) { - auto document = read(nullptr, file.filename().u8string(), file, fileName); + auto document = read(nullptr, file.name(), file, fileName); return document->getRoot(); } diff --git a/src/frontend/UiDocument.hpp b/src/frontend/UiDocument.hpp index dbf91ef1..298e63bc 100644 --- a/src/frontend/UiDocument.hpp +++ b/src/frontend/UiDocument.hpp @@ -4,10 +4,9 @@ #include #include -#include #include -namespace fs = std::filesystem; +#include "io/fwd.hpp" namespace gui { class UINode; @@ -48,10 +47,10 @@ public: static std::unique_ptr read( const scriptenv& parent_env, const std::string& name, - const fs::path& file, + const io::path& file, const std::string& fileName ); static std::shared_ptr readElement( - const fs::path& file, const std::string& fileName + const io::path& file, const std::string& fileName ); }; diff --git a/src/frontend/locale.cpp b/src/frontend/locale.cpp index 2af78076..d199cb49 100644 --- a/src/frontend/locale.cpp +++ b/src/frontend/locale.cpp @@ -5,7 +5,7 @@ #include "coders/json.hpp" #include "coders/commons.hpp" #include "content/ContentPack.hpp" -#include "files/files.hpp" +#include "io/io.hpp" #include "util/stringutil.hpp" #include "data/dv.hpp" #include "debug/Logger.hpp" @@ -69,9 +69,9 @@ namespace { }; } -void langs::loadLocalesInfo(const fs::path& resdir, std::string& fallback) { - auto file = resdir/fs::u8path(langs::TEXTS_FOLDER)/fs::u8path("langs.json"); - auto root = files::read_json(file); +void langs::loadLocalesInfo(const io::path& resdir, std::string& fallback) { + auto file = resdir / langs::TEXTS_FOLDER / "langs.json"; + auto root = io::read_json(file); langs::locales_info.clear(); root.at("fallback").get(fallback); @@ -94,7 +94,7 @@ void langs::loadLocalesInfo(const fs::path& resdir, std::string& fallback) { } } -std::string langs::locale_by_envlocale(const std::string& envlocale, const fs::path& resdir){ +std::string langs::locale_by_envlocale(const std::string& envlocale, const io::path& resdir){ std::string fallback = FALLBACK_DEFAULT; if (locales_info.size() == 0) { loadLocalesInfo(resdir, fallback); @@ -115,29 +115,29 @@ std::string langs::locale_by_envlocale(const std::string& envlocale, const fs::p } } -void langs::load(const fs::path& resdir, +void langs::load(const io::path& resdir, const std::string& locale, const std::vector& packs, Lang& lang) { - fs::path filename = fs::path(TEXTS_FOLDER)/fs::path(locale + LANG_FILE_EXT); - fs::path core_file = resdir/filename; + io::path filename = io::path(TEXTS_FOLDER) / (locale + LANG_FILE_EXT); + io::path core_file = resdir / filename; - if (fs::is_regular_file(core_file)) { - std::string text = files::read_string(core_file); + if (io::is_regular_file(core_file)) { + std::string text = io::read_string(core_file); Reader reader(core_file.string(), text); reader.read(lang, ""); } for (auto pack : packs) { - fs::path file = pack.folder/filename; - if (fs::is_regular_file(file)) { - std::string text = files::read_string(file); + io::path file = pack.folder / filename; + if (io::is_regular_file(file)) { + std::string text = io::read_string(file); Reader reader(file.string(), text); reader.read(lang, ""); } } } -void langs::load(const fs::path& resdir, +void langs::load(const io::path& resdir, const std::string& locale, const std::string& fallback, const std::vector& packs) { @@ -149,7 +149,7 @@ void langs::load(const fs::path& resdir, current = std::move(lang); } -void langs::setup(const fs::path& resdir, +void langs::setup(const io::path& resdir, std::string locale, const std::vector& packs) { std::string fallback = langs::FALLBACK_DEFAULT; diff --git a/src/frontend/locale.hpp b/src/frontend/locale.hpp index b3e200c9..1daaca21 100644 --- a/src/frontend/locale.hpp +++ b/src/frontend/locale.hpp @@ -3,9 +3,10 @@ #include #include #include -#include #include +#include "io/fwd.hpp" + struct ContentPack; namespace langs { @@ -44,17 +45,17 @@ namespace langs { extern std::unordered_map locales_info; extern void loadLocalesInfo( - const std::filesystem::path& resdir, + const io::path& resdir, std::string& fallback); extern std::string locale_by_envlocale(const std::string& envlocale, - const std::filesystem::path& resdir); + const io::path& resdir); - extern void load(const std::filesystem::path& resdir, + extern void load(const io::path& resdir, const std::string& locale, const std::vector& packs, Lang& lang); - extern void load(const std::filesystem::path& resdir, + extern void load(const io::path& resdir, const std::string& locale, const std::string& fallback, const std::vector& packs); @@ -63,7 +64,7 @@ namespace langs { extern const std::wstring& get(const std::wstring& key, const std::wstring& context); - extern void setup(const std::filesystem::path& resdir, + extern void setup(const io::path& resdir, std::string locale, const std::vector& packs); } diff --git a/src/frontend/menu.cpp b/src/frontend/menu.cpp index 4a36410c..da2e6ba2 100644 --- a/src/frontend/menu.cpp +++ b/src/frontend/menu.cpp @@ -8,7 +8,7 @@ #include "engine/Engine.hpp" #include "data/dv.hpp" #include "interfaces/Task.hpp" -#include "files/engine_paths.hpp" +#include "io/engine_paths.hpp" #include "graphics/ui/elements/Menu.hpp" #include "graphics/ui/gui_util.hpp" #include "graphics/ui/GUI.hpp" diff --git a/src/frontend/screens/LevelScreen.cpp b/src/frontend/screens/LevelScreen.cpp index 0e9eb609..08e1b41e 100644 --- a/src/frontend/screens/LevelScreen.cpp +++ b/src/frontend/screens/LevelScreen.cpp @@ -6,7 +6,7 @@ #include "core_defs.hpp" #include "debug/Logger.hpp" #include "engine/Engine.hpp" -#include "files/files.hpp" +#include "io/io.hpp" #include "frontend/LevelFrontend.hpp" #include "frontend/hud.hpp" #include "graphics/core/DrawContext.hpp" @@ -105,8 +105,8 @@ void LevelScreen::initializeContent() { void LevelScreen::initializePack(ContentPackRuntime* pack) { const ContentPack& info = pack->getInfo(); - fs::path scriptFile = info.folder/fs::path("scripts/hud.lua"); - if (fs::is_regular_file(scriptFile)) { + io::path scriptFile = info.folder / "scripts/hud.lua"; + if (io::is_regular_file(scriptFile)) { scripting::load_hud_script( pack->getEnvironment(), info.id, @@ -124,7 +124,7 @@ LevelScreen::~LevelScreen() { // unblock all bindings Events::enableBindings(); controller->onWorldQuit(); - engine.getPaths().setCurrentWorldFolder(fs::path()); + engine.getPaths().setCurrentWorldFolder(""); } void LevelScreen::saveWorldPreview() { @@ -147,7 +147,7 @@ void LevelScreen::saveWorldPreview() { worldRenderer->draw(ctx, camera, false, true, 0.0f, postProcessing.get()); auto image = postProcessing->toImage(); image->flipY(); - imageio::write(paths.resolve("world:preview.png").u8string(), image.get()); + imageio::write("world:preview.png", image.get()); } catch (const std::exception& err) { logger.error() << err.what(); } diff --git a/src/graphics/render/Decorator.cpp b/src/graphics/render/Decorator.cpp index e6331820..605e9ec6 100644 --- a/src/graphics/render/Decorator.cpp +++ b/src/graphics/render/Decorator.cpp @@ -16,7 +16,7 @@ #include "logic/LevelController.hpp" #include "util/stringutil.hpp" #include "engine/Engine.hpp" -#include "files/files.hpp" +#include "io/io.hpp" namespace fs = std::filesystem; diff --git a/src/io/devices/Device.cpp b/src/io/devices/Device.cpp new file mode 100644 index 00000000..600f9c26 --- /dev/null +++ b/src/io/devices/Device.cpp @@ -0,0 +1,16 @@ +#include "Device.hpp" + +#include "../io.hpp" + +using namespace io; + +SubDevice::SubDevice( + std::shared_ptr parent, + const std::string& path, + bool createDirectory +) + : parent(std::move(parent)), root(path) { + if (createDirectory && !this->parent->exists(path)) { + this->parent->mkdirs(path); + } +} diff --git a/src/io/devices/Device.hpp b/src/io/devices/Device.hpp new file mode 100644 index 00000000..a3addaf5 --- /dev/null +++ b/src/io/devices/Device.hpp @@ -0,0 +1,120 @@ +#pragma once + +#include +#include +#include +#include + +#include "../path.hpp" + +namespace io { + + /// @brief Device interface for file system operations + class Device { + public: + virtual ~Device() = default; + + /// @brief Resolve path to the filesystem path + virtual std::filesystem::path resolve(std::string_view path) = 0; + + /// @brief Open file for writing + /// @throw std::runtime_error if file cannot be opened + virtual std::unique_ptr write(std::string_view path) = 0; + + /// @brief Open file for reading + /// @throw std::runtime_error if file cannot be opened + virtual std::unique_ptr read(std::string_view path) = 0; + + /// @brief Get file size in bytes + virtual size_t size(std::string_view path) = 0; + + /// @brief Check if file or directory exists + virtual bool exists(std::string_view path) = 0; + + /// @brief Check if path is a directory + virtual bool isdir(std::string_view path) = 0; + + /// @brief Check if path is a regular file + virtual bool isfile(std::string_view path) = 0; + + /// @brief Create directory + /// @return true if directory was created + virtual bool mkdir(std::string_view path) = 0; + + /// @brief Create directories recursively + /// @return true if directory was created + virtual bool mkdirs(std::string_view path) = 0; + + /// @brief Remove file or empty directory + /// @return true if file or directory was removed + virtual bool remove(std::string_view path) = 0; + + /// @brief Remove all files and directories in the folder recursively + /// @return number of removed files and directories + virtual uint64_t removeAll(std::string_view path) = 0; + + /// @brief List directory contents + virtual std::unique_ptr list(std::string_view path) = 0; + }; + + /// @brief Subdevice is a wrapper around another device limited to a directory + class SubDevice : public Device { + public: + SubDevice( + std::shared_ptr parent, + const std::string& path, + bool createDirectory = true + ); + + std::filesystem::path resolve(std::string_view path) override { + return parent->resolve((root / path).pathPart()); + } + + std::unique_ptr write(std::string_view path) override { + return parent->write((root / path).pathPart()); + } + + std::unique_ptr read(std::string_view path) override { + return parent->read((root / path).pathPart()); + } + + size_t size(std::string_view path) override { + return parent->size((root / path).pathPart()); + } + + bool exists(std::string_view path) override { + return parent->exists((root / path).pathPart()); + } + + bool isdir(std::string_view path) override { + return parent->isdir((root / path).pathPart()); + } + + bool isfile(std::string_view path) override { + return parent->isfile((root / path).pathPart()); + } + + bool mkdir(std::string_view path) override { + return parent->mkdir((root / path).pathPart()); + } + + bool mkdirs(std::string_view path) override { + return parent->mkdirs((root / path).pathPart()); + } + + bool remove(std::string_view path) override { + return parent->remove((root / path).pathPart()); + } + + uint64_t removeAll(std::string_view path) override { + return parent->removeAll((root / path).pathPart()); + } + + std::unique_ptr list(std::string_view path) override { + return parent->list((root / path).pathPart()); + } + private: + std::shared_ptr parent; + path root; + }; +} diff --git a/src/io/devices/StdfsDevice.cpp b/src/io/devices/StdfsDevice.cpp new file mode 100644 index 00000000..cb47c2e6 --- /dev/null +++ b/src/io/devices/StdfsDevice.cpp @@ -0,0 +1,127 @@ +#include "StdfsDevice.hpp" + +#include +#include + +#include "debug/Logger.hpp" + +using namespace io; +namespace fs = std::filesystem; + +static debug::Logger logger("io-stdfs"); + +StdfsDevice::StdfsDevice(fs::path root, bool createDirectory) + : root(std::move(root)) { + if (createDirectory && !fs::is_directory(this->root)) { + std::error_code ec; + fs::create_directories(this->root, ec); + if (ec) { + logger.error() << "error creating root directory " << this->root + << ": " << ec.message(); + } + } +} + +fs::path StdfsDevice::resolve(std::string_view path) { + return root / fs::u8path(io::path(std::string(path)).normalized().string()); +} + +std::unique_ptr StdfsDevice::write(std::string_view path) { + auto resolved = resolve(path); + auto output = std::make_unique(resolved, std::ios::binary); + if (!output->is_open()) { + throw std::runtime_error("could not to open file " + resolved.u8string()); + } + return output; +} + +std::unique_ptr StdfsDevice::read(std::string_view path) { + auto resolved = resolve(path); + auto input = std::make_unique(resolved, std::ios::binary); + if (!*input) { + throw std::runtime_error("could not to open file " + resolved.u8string()); + } + return input; +} + +size_t StdfsDevice::size(std::string_view path) { + auto resolved = resolve(path); + return fs::file_size(resolved); +} + +bool StdfsDevice::exists(std::string_view path) { + auto resolved = resolve(path); + return fs::exists(resolved); +} + +bool StdfsDevice::isdir(std::string_view path) { + auto resolved = resolve(path); + return fs::is_directory(resolved); +} + +bool StdfsDevice::isfile(std::string_view path) { + auto resolved = resolve(path); + return fs::is_regular_file(resolved); +} + +bool StdfsDevice::mkdir(std::string_view path) { + auto resolved = resolve(path); + + std::error_code ec; + bool created = fs::create_directory(resolved, ec); + if (ec) { + logger.error() << "error creating directory " << resolved << ": " + << ec.message(); + } + return created; +} + +bool StdfsDevice::mkdirs(std::string_view path) { + auto resolved = resolve(path); + + std::error_code ec; + bool created = fs::create_directories(resolved, ec); + if (ec) { + logger.error() << "error creating directories " << resolved << ": " + << ec.message(); + } + return created; +} + +bool StdfsDevice::remove(std::string_view path) { + auto resolved = resolve(path); + return fs::remove(resolved); +} + +uint64_t StdfsDevice::removeAll(std::string_view path) { + auto resolved = resolve(path); + if (fs::exists(resolved)) { + logger.info() << "removeAll " << resolved; + return fs::remove_all(resolved); + } else { + return 0; + } +} + +class StdfsPathsGenerator : public PathsGenerator { +public: + StdfsPathsGenerator(fs::path root) : root(std::move(root)) { + it = fs::directory_iterator(this->root); + } + + bool next(io::path& path) override { + if (it == fs::directory_iterator()) { + return false; + } + path = it->path().filename().u8string(); + it++; + return true; + } +private: + fs::path root; + fs::directory_iterator it; +}; + +std::unique_ptr StdfsDevice::list(std::string_view path) { + return std::make_unique(resolve(path)); +} diff --git a/src/io/devices/StdfsDevice.hpp b/src/io/devices/StdfsDevice.hpp new file mode 100644 index 00000000..ec1a9526 --- /dev/null +++ b/src/io/devices/StdfsDevice.hpp @@ -0,0 +1,24 @@ +#include "Device.hpp" + +namespace io { + /// @brief Device based on the standard filesystem + class StdfsDevice : public Device { + public: + StdfsDevice(std::filesystem::path root, bool createDirectory = true); + + std::filesystem::path resolve(std::string_view path) override; + std::unique_ptr write(std::string_view path) override; + std::unique_ptr read(std::string_view path) override; + size_t size(std::string_view path) override; + bool exists(std::string_view path) override; + bool isdir(std::string_view path) override; + bool isfile(std::string_view path) override; + bool mkdir(std::string_view path) override; + bool mkdirs(std::string_view path) override; + bool remove(std::string_view path) override; + uint64_t removeAll(std::string_view path) override; + std::unique_ptr list(std::string_view path) override; + private: + std::filesystem::path root; + }; +} diff --git a/src/io/engine_paths.cpp b/src/io/engine_paths.cpp new file mode 100644 index 00000000..76485031 --- /dev/null +++ b/src/io/engine_paths.cpp @@ -0,0 +1,283 @@ +#include "engine_paths.hpp" + +#include +#include +#include +#include +#include +#include "typedefs.hpp" +#include "util/stringutil.hpp" +#include + +#include "io/devices/StdfsDevice.hpp" +#include "world/files/WorldFiles.hpp" +#include "debug/Logger.hpp" + +namespace fs = std::filesystem; + +static debug::Logger logger("engine-paths"); + +static inline io::path SCREENSHOTS_FOLDER = "screenshots"; +static inline io::path CONTENT_FOLDER = "content"; +static inline io::path WORLDS_FOLDER = "worlds"; +static inline io::path CONFIG_FOLDER = "config"; +static inline io::path EXPORT_FOLDER = "export"; +static inline io::path CONTROLS_FILE = "controls.toml"; +static inline io::path SETTINGS_FILE = "settings.toml"; + +void EnginePaths::prepare() { + io::set_device("res", std::make_shared(resourcesFolder, false)); + io::set_device("user", std::make_shared(userFilesFolder)); + + if (!io::is_directory("res:")) { + throw std::runtime_error( + resourcesFolder.string() + " is not a directory" + ); + } + logger.info() << "resources folder: " << fs::canonical(resourcesFolder).u8string(); + logger.info() << "user files folder: " << fs::canonical(userFilesFolder).u8string(); + + auto contentFolder = io::path("user:") / CONTENT_FOLDER; + if (!io::is_directory(contentFolder)) { + io::create_directories(contentFolder); + } + + io::create_subdevice("core", "res", ""); + io::create_subdevice("export", "user", EXPORT_FOLDER); + io::create_subdevice("config", "user", CONFIG_FOLDER); +} + +const std::filesystem::path& EnginePaths::getUserFilesFolder() const { + return userFilesFolder; +} + +const std::filesystem::path& EnginePaths::getResourcesFolder() const { + return resourcesFolder; +} + +io::path EnginePaths::getNewScreenshotFile(const std::string& ext) { + auto folder = io::path("user:") / SCREENSHOTS_FOLDER; + if (!io::is_directory(folder)) { + io::create_directories(folder); + } + + auto t = std::time(nullptr); + auto tm = *std::localtime(&t); + + const char* format = "%Y-%m-%d_%H-%M-%S"; + std::stringstream ss; + ss << std::put_time(&tm, format); + std::string datetimestr = ss.str(); + + auto file = folder / ("screenshot-" + datetimestr + "." + ext); + uint index = 0; + while (io::exists(file)) { + file = folder / ("screenshot-" + datetimestr + "-" + + std::to_string(index) + "." + ext); + index++; + } + return file; +} + +io::path EnginePaths::getWorldsFolder() const { + return io::path("user:") / WORLDS_FOLDER; +} + +io::path EnginePaths::getConfigFolder() const { + return io::path("user:") / CONFIG_FOLDER; +} + +io::path EnginePaths::getCurrentWorldFolder() { + return currentWorldFolder; +} + +io::path EnginePaths::getWorldFolderByName(const std::string& name) { + return getWorldsFolder() / name; +} + +io::path EnginePaths::getControlsFile() const { + return io::path("user:") / CONTROLS_FILE; +} + +io::path EnginePaths::getSettingsFile() const { + return io::path("user:") / SETTINGS_FILE; +} + +std::vector EnginePaths::scanForWorlds() const { + std::vector folders; + + auto folder = getWorldsFolder(); + if (!io::is_directory(folder)) return folders; + + for (const auto& worldFolder : io::directory_iterator(folder)) { + if (!io::is_directory(worldFolder)) { + continue; + } + auto worldFile = worldFolder / WorldFiles::WORLD_FILE; + if (!io::is_regular_file(worldFile)) { + continue; + } + folders.push_back(worldFolder); + } + std::sort( + folders.begin(), + folders.end(), + [](io::path a, io::path b) { + a = a / WorldFiles::WORLD_FILE; + b = b / WorldFiles::WORLD_FILE; + return fs::last_write_time(io::resolve(a)) > + fs::last_write_time(io::resolve(b)); + } + ); + return folders; +} + +void EnginePaths::setUserFilesFolder(std::filesystem::path folder) { + this->userFilesFolder = std::move(folder); +} + +void EnginePaths::setResourcesFolder(std::filesystem::path folder) { + this->resourcesFolder = std::move(folder); +} + +void EnginePaths::setScriptFolder(std::filesystem::path folder) { + io::set_device("script", std::make_shared(folder)); + this->scriptFolder = std::move(folder); +} + +void EnginePaths::setCurrentWorldFolder(io::path folder) { + this->currentWorldFolder = std::move(folder); + io::create_subdevice("world", "user", currentWorldFolder); +} + +void EnginePaths::setContentPacks(std::vector* contentPacks) { + // Remove previous content entry-points + for (const auto& id : contentEntryPoints) { + io::remove_device(id); + } + contentEntryPoints.clear(); + this->contentPacks = contentPacks; + // Create content devices + for (const auto& pack : *contentPacks) { + auto parent = pack.folder.entryPoint(); + io::create_subdevice(pack.id, parent, pack.folder); + contentEntryPoints.push_back(pack.id); + } +} + +std::tuple EnginePaths::parsePath(std::string_view path) { + size_t separator = path.find(':'); + if (separator == std::string::npos) { + return {"", std::string(path)}; + } + auto prefix = std::string(path.substr(0, separator)); + auto filename = std::string(path.substr(separator + 1)); + return {prefix, filename}; +} + +ResPaths::ResPaths(io::path mainRoot, std::vector roots) + : mainRoot(std::move(mainRoot)), roots(std::move(roots)) { +} + +io::path ResPaths::find(const std::string& filename) const { + for (int i = roots.size() - 1; i >= 0; i--) { + auto& root = roots[i]; + auto file = root.path / filename; + if (io::exists(file)) { + return file; + } + } + return mainRoot / filename; +} + +std::string ResPaths::findRaw(const std::string& filename) const { + for (int i = roots.size() - 1; i >= 0; i--) { + auto& root = roots[i]; + if (io::exists(root.path / filename)) { + return root.name + ":" + filename; + } + } + throw std::runtime_error("could not to find file " + util::quote(filename)); +} + +std::vector ResPaths::listdirRaw(const std::string& folderName) const { + std::vector entries; + for (int i = roots.size() - 1; i >= 0; i--) { + auto& root = roots[i]; + auto folder = root.path / folderName; + if (!io::is_directory(folder)) continue; + for (const auto& file : io::directory_iterator(folder)) { + entries.emplace_back(root.name + ":" + folderName + "/" + file.name()); + } + } + return entries; +} + +std::vector ResPaths::listdir( + const std::string& folderName +) const { + std::vector entries; + for (int i = roots.size() - 1; i >= 0; i--) { + auto& root = roots[i]; + io::path folder = root.path / folderName; + if (!io::is_directory(folder)) continue; + for (const auto& entry : io::directory_iterator(folder)) { + entries.push_back(entry); + } + } + return entries; +} + +dv::value ResPaths::readCombinedList(const std::string& filename) const { + dv::value list = dv::list(); + for (const auto& root : roots) { + auto path = root.path / filename; + if (!io::exists(path)) { + continue; + } + try { + auto value = io::read_object(path); + if (!value.isList()) { + logger.warning() << "reading combined list " << root.name << ":" + << filename << " is not a list (skipped)"; + continue; + } + for (const auto& elem : value) { + list.add(elem); + } + } catch (const std::runtime_error& err) { + logger.warning() << "reading combined list " << root.name << ":" + << filename << ": " << err.what(); + } + } + return list; +} + +dv::value ResPaths::readCombinedObject(const std::string& filename) const { + dv::value object = dv::object(); + for (const auto& root : roots) { + auto path = root.path / filename; + if (!io::exists(path)) { + continue; + } + try { + auto value = io::read_object(path); + if (!value.isObject()) { + logger.warning() + << "reading combined object " << root.name << ": " + << filename << " is not an object (skipped)"; + } + for (const auto& [key, element] : value.asObject()) { + object[key] = element; + } + } catch (const std::runtime_error& err) { + logger.warning() << "reading combined object " << root.name << ":" + << filename << ": " << err.what(); + } + } + return object; +} + +const io::path& ResPaths::getMainRoot() const { + return mainRoot; +} diff --git a/src/files/engine_paths.hpp b/src/io/engine_paths.hpp similarity index 51% rename from src/files/engine_paths.hpp rename to src/io/engine_paths.hpp index 5ec34704..65e5e067 100644 --- a/src/files/engine_paths.hpp +++ b/src/io/engine_paths.hpp @@ -1,50 +1,41 @@ #pragma once -#include #include #include #include #include #include +#include "io.hpp" #include "data/dv.hpp" #include "content/ContentPack.hpp" - -class files_access_error : public std::runtime_error { -public: - files_access_error(const std::string& msg) : std::runtime_error(msg) { - } -}; - class EnginePaths { public: void prepare(); void setUserFilesFolder(std::filesystem::path folder); - std::filesystem::path getUserFilesFolder() const; + const std::filesystem::path& getUserFilesFolder() const; void setResourcesFolder(std::filesystem::path folder); - std::filesystem::path getResourcesFolder() const; + const std::filesystem::path& getResourcesFolder() const; void setScriptFolder(std::filesystem::path folder); - std::filesystem::path getWorldFolderByName(const std::string& name); - std::filesystem::path getWorldsFolder() const; - std::filesystem::path getConfigFolder() const; + io::path getWorldFolderByName(const std::string& name); + io::path getWorldsFolder() const; + io::path getConfigFolder() const; - void setCurrentWorldFolder(std::filesystem::path folder); - std::filesystem::path getCurrentWorldFolder(); + void setCurrentWorldFolder(io::path folder); + io::path getCurrentWorldFolder(); - std::filesystem::path getNewScreenshotFile(const std::string& ext); - std::filesystem::path getControlsFile() const; - std::filesystem::path getSettingsFile() const; + io::path getNewScreenshotFile(const std::string& ext); + io::path getControlsFile() const; + io::path getSettingsFile() const; void setContentPacks(std::vector* contentPacks); - std::vector scanForWorlds() const; - - std::filesystem::path resolve(const std::string& path, bool throwErr = true) const; + std::vector scanForWorlds() const; static std::tuple parsePath(std::string_view view); @@ -53,23 +44,24 @@ public: private: std::filesystem::path userFilesFolder {"."}; std::filesystem::path resourcesFolder {"res"}; - std::filesystem::path currentWorldFolder; + io::path currentWorldFolder; std::optional scriptFolder; std::vector* contentPacks = nullptr; + std::vector contentEntryPoints; }; struct PathsRoot { std::string name; - std::filesystem::path path; + io::path path; }; class ResPaths { public: - ResPaths(std::filesystem::path mainRoot, std::vector roots); + ResPaths(io::path mainRoot, std::vector roots); - std::filesystem::path find(const std::string& filename) const; + io::path find(const std::string& filename) const; std::string findRaw(const std::string& filename) const; - std::vector listdir(const std::string& folder) const; + std::vector listdir(const std::string& folder) const; std::vector listdirRaw(const std::string& folder) const; /// @brief Read all found list versions from all packs and combine into a @@ -79,9 +71,9 @@ public: dv::value readCombinedObject(const std::string& file) const; - const std::filesystem::path& getMainRoot() const; + const io::path& getMainRoot() const; private: - std::filesystem::path mainRoot; + io::path mainRoot; std::vector roots; }; diff --git a/src/io/fwd.hpp b/src/io/fwd.hpp new file mode 100644 index 00000000..d6ce72f0 --- /dev/null +++ b/src/io/fwd.hpp @@ -0,0 +1,5 @@ +#pragma once + +namespace io { + class path; +} diff --git a/src/io/io.cpp b/src/io/io.cpp new file mode 100644 index 00000000..bef794b4 --- /dev/null +++ b/src/io/io.cpp @@ -0,0 +1,299 @@ +#include "io.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include "coders/commons.hpp" +#include "coders/gzip.hpp" +#include "coders/json.hpp" +#include "coders/toml.hpp" +#include "util/stringutil.hpp" + +#include "devices/Device.hpp" + +namespace fs = std::filesystem; + +static std::map> devices; + +void io::set_device(const std::string& name, std::shared_ptr device) { + devices[name] = device; +} + +void io::remove_device(const std::string& name) { + devices.erase(name); +} + +std::shared_ptr io::get_device(const std::string& name) { + const auto& found = devices.find(name); + if (found == devices.end()) { + return nullptr; + } + return found->second; +} + +io::Device& io::require_device(const std::string& name) { + auto device = get_device(name); + if (!device) { + throw std::runtime_error("io-device not found: " + name); + } + return *device; +} + +void io::create_subdevice( + const std::string& name, const std::string& parent, const io::path& root +) { + auto parentDevice = get_device(parent); + if (!parentDevice) { + throw std::runtime_error("parent device not found for entry-point: " + parent); + } + set_device(name, std::make_shared(parentDevice, root.pathPart())); +} + +io::directory_iterator::directory_iterator(const io::path& folder) + : folder(folder) { + auto& device = io::require_device(folder.entryPoint()); + generator = device.list(folder.pathPart()); +} + +io::rafile::rafile(const io::path& filename) + : file(std::make_unique(io::resolve(filename), std::ios::binary | std::ios::ate)) { + if (!*file) { + throw std::runtime_error("could not to open file " + filename.string()); + } + filelength = file->tellg(); + file->seekg(0); +} + +io::rafile::rafile(std::unique_ptr file, size_t length) + : file(std::move(file)), filelength(length) { +} + +size_t io::rafile::length() const { + return filelength; +} + +void io::rafile::seekg(std::streampos pos) { + file->seekg(pos); +} + +void io::rafile::read(char* buffer, std::streamsize size) { + file->read(buffer, size); +} + +bool io::write_bytes( + const io::path& filename, const ubyte* data, size_t size +) { + auto device = io::get_device(filename.entryPoint()); + if (device == nullptr) { + throw std::runtime_error("io-device not found: " + filename.entryPoint()); + } + auto stream = device->write(filename.pathPart()); + stream->write(reinterpret_cast(data), size); + return stream->good(); +} + +bool io::read(const io::path& filename, char* data, size_t size) { + auto device = io::get_device(filename.entryPoint()); + if (device == nullptr) { + return false; + } + auto stream = io::read(filename); + stream->read(data, size); + return stream->good(); +} + +std::unique_ptr io::read(const io::path& filename) { + auto device = io::get_device(filename.entryPoint()); + if (device == nullptr) { + throw std::runtime_error("io-device not found: " + filename.entryPoint()); + } + return device->read(filename.pathPart()); +} + +util::Buffer io::read_bytes_buffer(const path& file) { + size_t size; + auto bytes = io::read_bytes(file, size); + return util::Buffer(std::move(bytes), size); +} + +std::unique_ptr io::read_bytes( + const io::path& filename, size_t& length +) { + auto& device = io::require_device(filename.entryPoint()); + length = device.size(filename.pathPart()); + auto data = std::make_unique(length); + auto stream = io::read(filename); + stream->read(reinterpret_cast(data.get()), length); + return stream->good() ? std::move(data) : nullptr; +} + +std::vector io::read_bytes(const path& filename) { + auto& device = io::require_device(filename.entryPoint()); + size_t length = device.size(filename.pathPart()); + std::vector data(length); + auto stream = io::read(filename); + stream->read(reinterpret_cast(data.data()), length); + return data; +} + +std::string io::read_string(const path& filename) { + size_t size; + auto bytes = read_bytes(filename, size); + return std::string((const char*)bytes.get(), size); +} + +bool io::write_string(const io::path& file, std::string_view content) { + return io::write_bytes(file, (const ubyte*)content.data(), content.size()); +} + +bool io::write_json( + const io::path& file, const dv::value& obj, bool nice +) { + return io::write_string(file, json::stringify(obj, nice, " ")); +} + +bool io::write_binary_json( + const io::path& file, const dv::value& obj, bool compression +) { + auto bytes = json::to_binary(obj, compression); + return io::write_bytes(file, bytes.data(), bytes.size()); +} + +dv::value io::read_json(const path& filename) { + std::string text = io::read_string(filename); + return json::parse(filename.string(), text); +} + +dv::value io::read_binary_json(const path& file) { + size_t size; + auto bytes = io::read_bytes(file, size); + return json::from_binary(bytes.get(), size); +} + +dv::value io::read_toml(const path& file) { + return toml::parse(file.string(), io::read_string(file)); +} + +std::vector io::read_list(const io::path& filename) { + auto stream = io::read(filename); + std::vector lines; + std::string line; + while (std::getline(*stream, line)) { + util::trim(line); + if (line.length() == 0) continue; + if (line[0] == '#') continue; + lines.push_back(line); + } + return lines; +} + +bool io::is_regular_file(const io::path& file) { + if (file.emptyOrInvalid()) { + return false; + } + auto device = io::get_device(file.entryPoint()); + if (device == nullptr) { + return false; + } + return device->isfile(file.pathPart()); +} + +bool io::is_directory(const io::path& file) { + if (file.emptyOrInvalid()) { + return false; + } + auto device = io::get_device(file.entryPoint()); + if (device == nullptr) { + return false; + } + return device->isdir(file.pathPart()); +} + +bool io::exists(const io::path& file) { + if (file.emptyOrInvalid()) { + return false; + } + auto device = io::get_device(file.entryPoint()); + if (device == nullptr) { + return false; + } + return device->exists(file.pathPart()); +} + +bool io::create_directory(const io::path& file) { + auto& device = io::require_device(file.entryPoint()); + if (device.isdir(file.pathPart())) { + return false; + } + return device.mkdirs(file.pathPart()); +} + + +bool io::create_directories(const io::path& file) { + auto& device = io::require_device(file.entryPoint()); + if (device.isdir(file.pathPart())) { + return false; + } + return device.mkdirs(file.pathPart()); +} + +bool io::remove(const io::path& file) { + auto& device = io::require_device(file.entryPoint()); + return device.remove(file.pathPart()); +} + +uint64_t io::remove_all(const io::path& file) { + auto& device = io::require_device(file.entryPoint()); + return device.removeAll(file.pathPart()); +} + +size_t io::file_size(const io::path& file) { + auto& device = io::require_device(file.entryPoint()); + return device.size(file.pathPart()); +} + +std::filesystem::path io::resolve(const io::path& file) { + auto device = io::get_device(file.entryPoint()); + if (device == nullptr) { + return {}; + } + return device->resolve(file.pathPart()); +} + +#include + +#include "coders/json.hpp" +#include "coders/toml.hpp" + +using DecodeFunc = dv::value(*)(std::string_view, std::string_view); + +static std::map data_decoders { + {fs::u8path(".json"), json::parse}, + {fs::u8path(".toml"), toml::parse}, +}; + +bool io::is_data_file(const io::path& file) { + return is_data_interchange_format(file.extension()); +} + +bool io::is_data_interchange_format(const std::string& ext) { + return data_decoders.find(ext) != data_decoders.end(); +} + +dv::value io::read_object(const path& file) { + const auto& found = data_decoders.find(file.extension()); + if (found == data_decoders.end()) { + throw std::runtime_error("unknown file format"); + } + auto text = read_string(file); + try { + return found->second(file.string(), text); + } catch (const parsing_error& err) { + throw std::runtime_error(err.errorLog()); + } +} diff --git a/src/io/io.hpp b/src/io/io.hpp new file mode 100644 index 00000000..46582400 --- /dev/null +++ b/src/io/io.hpp @@ -0,0 +1,205 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "typedefs.hpp" +#include "data/dv.hpp" +#include "util/Buffer.hpp" +#include "path.hpp" + +namespace io { + class Device; + + /// @brief Set device for the entry-point + void set_device(const std::string& name, std::shared_ptr device); + + /// @brief Remove device by entry-point + void remove_device(const std::string& name); + + /// @brief Get device by entry-point + std::shared_ptr get_device(const std::string& name); + + /// @brief Get device by entry-point or throw exception + Device& require_device(const std::string& name); + + /// @brief Create subdevice for the entry-point + /// @param name subdevice entry-point + /// @param parent parent device entry-point + /// @param root root path for the subdevice + void create_subdevice( + const std::string& name, const std::string& parent, const path& root + ); + + /// @brief Read-only random access file + class rafile { + std::unique_ptr file; + size_t filelength; + public: + rafile(const path& filename); + rafile(std::unique_ptr file, size_t length); + + void seekg(std::streampos pos); + void read(char* buffer, std::streamsize size); + size_t length() const; + }; + + class directory_iterator_impl { + public: + using iterator_category = std::input_iterator_tag; + using value_type = path; + using difference_type = std::ptrdiff_t; + using pointer = path*; + using reference = path&; + + directory_iterator_impl( + PathsGenerator& generator, const path& folder, bool end = false + ) + : generator(generator), folder(folder), isend(end) { + if (!isend && this->generator.next(current)) { + isend = false; + current = folder / current; + } else { + isend = true; + } + } + + reference operator*() { + return current; + } + + pointer operator->() { + return ¤t; + } + + directory_iterator_impl& operator++() { + if (isend) { + return *this; + } + if (generator.next(current)) { + current = folder / current; + } else { + isend = true; + } + return *this; + } + + bool operator==(const directory_iterator_impl& other) const { + return isend == other.isend; + } + + bool operator!=(const directory_iterator_impl& other) const { + return !(*this == other); + } + private: + PathsGenerator& generator; + path folder; + path current; + bool isend = false; + }; + + class directory_iterator { + std::unique_ptr generator; + path folder; + public: + directory_iterator(const path& folder); + + directory_iterator_impl begin() { + return directory_iterator_impl(*generator, folder); + } + + directory_iterator_impl end() { + return directory_iterator_impl(*generator, "", true); + } + }; + + /// @brief Write bytes array to the file without any extra data + /// @param file target file + /// @param data data bytes array + /// @param size size of data bytes array + bool write_bytes(const io::path& file, const ubyte* data, size_t size); + + /// @brief Write string to the file + bool write_string(const io::path& file, std::string_view content); + + /// @brief Write dynamic data to the JSON file + /// @param nice if true, human readable format will be used, otherwise + /// minimal + bool write_json( + const io::path& file, const dv::value& obj, bool nice = true + ); + + /// @brief Write dynamic data to the binary JSON file + /// (see src/coders/binary_json_spec.md) + /// @param compressed use gzip compression + bool write_binary_json( + const io::path& file, + const dv::value& obj, + bool compressed = false + ); + + /// @brief Open file for reading + /// @throw std::runtime_error if file cannot be opened + std::unique_ptr read(const io::path& file); + + /// @brief Read bytes array from the file + bool read(const io::path& file, char* data, size_t size); + util::Buffer read_bytes_buffer(const path& file); + std::unique_ptr read_bytes(const path& file, size_t& length); + std::vector read_bytes(const path& file); + + /// @brief Read string from the file + std::string read_string(const path& file); + + /// @brief Read JSON or BJSON file + /// @param file *.json or *.bjson file + dv::value read_json(const path& file); + + /// @brief Read BJSON file + dv::value read_binary_json(const path& file); + + /// @brief Read TOML file + /// @param file *.toml file + dv::value read_toml(const path& file); + + /// @brief Read list of strings from the file + std::vector read_list(const io::path& file); + + /// @brief Check if path is a regular file + bool is_regular_file(const io::path& file); + + /// @brief Check if path is a directory + bool is_directory(const io::path& path); + + /// @brief Check if file or directory exists + bool exists(const io::path& path); + + /// @brief Create directory + bool create_directory(const io::path& file); + + /// @brief Create directories recursively + bool create_directories(const io::path& file); + + /// @brief Remove file or empty directory + bool remove(const io::path& file); + + /// @brief Remove all files and directories in the folder recursively + uint64_t remove_all(const io::path& file); + + /// @brief Get file size in bytes + size_t file_size(const io::path& file); + + std::filesystem::path resolve(const io::path& file); + + /// @brief Check if file is one of the supported data interchange formats + bool is_data_file(const io::path& file); + + /// @brief Check if file extension is one of the supported data interchange formats + bool is_data_interchange_format(const std::string& ext); + + dv::value read_object(const path& file); +} diff --git a/src/io/path.cpp b/src/io/path.cpp new file mode 100644 index 00000000..08333c34 --- /dev/null +++ b/src/io/path.cpp @@ -0,0 +1,38 @@ +#include "path.hpp" + +#include + +using namespace io; + +void path::checkValid() const { + if (colonPos == std::string::npos) { + throw std::runtime_error("path entry point is not specified: " + str); + } +} + +path path::normalized() const { + io::path path = pathPart(); + + std::stack parts; + do { + parts.push(path.name()); + path.str = path.parent().string(); + } while (!path.empty()); + + while (!parts.empty()) { + const std::string part = parts.top(); + parts.pop(); + if (part == ".") { + continue; + } + if (part == "..") { + throw access_error("entry point reached"); + } + + path = path / part; + } + if (path.colonPos != std::string::npos) { + path = path.entryPoint() + ":" + path.string(); + } + return path; +} diff --git a/src/io/path.hpp b/src/io/path.hpp new file mode 100644 index 00000000..4c35d824 --- /dev/null +++ b/src/io/path.hpp @@ -0,0 +1,154 @@ +#pragma once + +#include +#include +#include + +namespace io { + /// @brief Access violation error + class access_error : public std::runtime_error { + public: + access_error(const std::string& msg) : std::runtime_error(msg) { + } + }; + + /// @brief std::filesystem::path project-specific alternative having + /// `entry_point:path` scheme and solving std::filesystem::path problems: + /// - implicit std::string conversions depending on compiler + /// - unicode path construction must be done with std::filesystem::u8path + class path { + public: + path() = default; + + path(std::string str) : str(std::move(str)) { + colonPos = this->str.find(':'); + + size_t len = this->str.length(); + for (size_t i = 0; i < len; i++) { + if (this->str[i] == '\\') { + this->str[i] = '/'; + } + } + } + + path(const char* str) : path(std::string(str)) {} + + bool operator==(const std::string& other) const { + return str == other; + } + + bool operator<(const path& other) const { + return str < other.str; + } + + bool operator==(const path& other) const { + return str == other.str; + } + + bool operator==(const char* other) const { + return str == other; + } + + path operator/(const char* child) const { + if (str.empty() || str[str.length()-1] == ':') { + return str + std::string(child); + } + return str + "/" + std::string(child); + } + + path operator/(const std::string& child) const { + if (str.empty() || str[str.length()-1] == ':') { + return str + child; + } + return str + "/" + child; + } + + path operator/(std::string_view child) const { + if (str.empty() || str[str.length()-1] == ':') { + return str + std::string(child); + } + return str + "/" + std::string(child); + } + + path operator/(const path& child) const { + if (str.empty() || str[str.length()-1] == ':') { + return str + child.pathPart(); + } + return str + "/" + child.pathPart(); + } + + std::string pathPart() const { + if (colonPos == std::string::npos) { + return str; + } + return str.substr(colonPos + 1); + } + + std::string name() const { + size_t slashpos = str.rfind('/'); + if (slashpos == std::string::npos) { + return colonPos == std::string::npos ? str + : str.substr(colonPos + 1); + } + return str.substr(slashpos + 1); + } + + std::string stem() const { + return name().substr(0, name().rfind('.')); + } + + /// @brief Get extension + std::string extension() const { + size_t slashpos = str.rfind('/'); + size_t dotpos = str.rfind('.'); + if (dotpos == std::string::npos || + (slashpos != std::string::npos && dotpos < slashpos)) { + return ""; + } + return str.substr(dotpos); + } + + /// @brief Get entry point + std::string entryPoint() const { + checkValid(); + return str.substr(0, colonPos); + } + + /// @brief Get parent path + path parent() const { + size_t slashpos = str.rfind('/'); + if (slashpos == std::string::npos) { + return colonPos == std::string::npos ? "" : str; + } + return str.substr(0, slashpos); + } + + path normalized() const; + + std::string string() const { + return str; + } + + /// @brief Check if path is not initialized with 'entry_point:path' + bool empty() const { + return str.empty(); + } + + bool emptyOrInvalid() const { + return str.empty() || colonPos == std::string::npos; + } + private: + /// @brief UTF-8 string contains entry_point:path or empty string + std::string str; + /// @brief Precalculated position of colon character + size_t colonPos = std::string::npos; + + void checkValid() const; + }; + + class PathsGenerator { + public: + virtual ~PathsGenerator() = default; + virtual bool next(path& dst) = 0; + }; +} diff --git a/src/files/settings_io.cpp b/src/io/settings_io.cpp similarity index 100% rename from src/files/settings_io.cpp rename to src/io/settings_io.cpp diff --git a/src/files/settings_io.hpp b/src/io/settings_io.hpp similarity index 100% rename from src/files/settings_io.hpp rename to src/io/settings_io.hpp diff --git a/src/files/util.hpp b/src/io/util.hpp similarity index 95% rename from src/files/util.hpp rename to src/io/util.hpp index 5967299e..df7682b9 100644 --- a/src/files/util.hpp +++ b/src/io/util.hpp @@ -3,7 +3,7 @@ #include #include -namespace files { +namespace io { inline bool is_valid_name(std::string_view name) { static std::string illegalChars = "\\/%?!<>:; "; for (char c : illegalChars) { diff --git a/src/logic/ChunksController.cpp b/src/logic/ChunksController.cpp index 0f341322..ccdd59b1 100644 --- a/src/logic/ChunksController.cpp +++ b/src/logic/ChunksController.cpp @@ -4,7 +4,7 @@ #include #include "content/Content.hpp" -#include "files/WorldFiles.hpp" +#include "world/files/WorldFiles.hpp" #include "graphics/core/Mesh.hpp" #include "lighting/Lighting.hpp" #include "maths/voxmaths.hpp" diff --git a/src/logic/EngineController.cpp b/src/logic/EngineController.cpp index 90106ec9..72ce6e90 100644 --- a/src/logic/EngineController.cpp +++ b/src/logic/EngineController.cpp @@ -9,8 +9,8 @@ #include "debug/Logger.hpp" #include "coders/json.hpp" #include "content/ContentReport.hpp" -#include "files/WorldConverter.hpp" -#include "files/WorldFiles.hpp" +#include "world/files/WorldConverter.hpp" +#include "world/files/WorldFiles.hpp" #include "frontend/locale.hpp" #include "frontend/menu.hpp" #include "frontend/screens/LevelScreen.hpp" @@ -32,11 +32,11 @@ EngineController::EngineController(Engine& engine) : engine(engine) { } void EngineController::deleteWorld(const std::string& name) { - fs::path folder = engine.getPaths().getWorldFolderByName(name); + io::path folder = engine.getPaths().getWorldFolderByName(name); auto deletion = [this, folder]() { - logger.info() << "deleting " << folder; - fs::remove_all(folder); + logger.info() << "deleting " << folder.string(); + io::remove_all(folder); if (!engine.isHeadless()) { engine.getGUI()->getMenu()->back(); } @@ -49,7 +49,7 @@ void EngineController::deleteWorld(const std::string& name) { guiutil::confirm( engine, langs::get(L"delete-confirm", L"world") + L" (" + - util::str2wstr_utf8(folder.u8string()) + L")", + util::str2wstr_utf8(folder.string()) + L")", deletion ); } @@ -74,9 +74,6 @@ std::shared_ptr create_converter( content, report, [&engine, postRunnable]() { - //auto menu = engine.getGUI()->getMenu(); - //menu->reset(); - //menu->setPage("main", false); engine.postRunnable([=]() { postRunnable(); }); }, mode, @@ -131,7 +128,7 @@ static void show_convert_request( ); } -static bool load_world_content(Engine& engine, const fs::path& folder) { +static bool load_world_content(Engine& engine, const io::path& folder) { if (engine.isHeadless()) { engine.loadWorldContent(folder); return true; @@ -195,10 +192,10 @@ void EngineController::onMissingContent(const std::shared_ptr& re void EngineController::openWorld(const std::string& name, bool confirmConvert) { const auto& paths = engine.getPaths(); - auto folder = paths.getWorldsFolder() / fs::u8path(name); - auto worldFile = folder / fs::u8path("world.json"); - if (!fs::exists(worldFile)) { - throw std::runtime_error(worldFile.u8string() + " does not exists"); + auto folder = paths.getWorldsFolder() / name; + auto worldFile = folder / "world.json"; + if (!io::exists(worldFile)) { + throw std::runtime_error(worldFile.string() + " does not exists"); } if (!load_world_content(engine, folder)) { @@ -260,7 +257,7 @@ void EngineController::createWorld( uint64_t seed = str2seed(seedstr); EnginePaths& paths = engine.getPaths(); - auto folder = paths.getWorldsFolder() / fs::u8path(name); + auto folder = paths.getWorldsFolder() / name; if (engine.isHeadless()) { engine.loadContent(); @@ -291,7 +288,7 @@ void EngineController::setLocalPlayer(int64_t player) { } void EngineController::reopenWorld(World* world) { - std::string name = world->wfile->getFolder().filename().u8string(); + std::string name = world->wfile->getFolder().name(); engine.onWorldClosed(); openWorld(name, true); } @@ -321,7 +318,7 @@ void EngineController::reconfigPacks( runnable removeFunc = [this, controller, packsToAdd, packsToRemove]() { if (controller == nullptr) { try { - auto manager = engine.createPacksManager(fs::path("")); + auto manager = engine.createPacksManager(""); manager.scan(); auto names = PacksManager::getNames(engine.getContentPacks()); for (const auto& id : packsToAdd) { diff --git a/src/logic/LevelController.cpp b/src/logic/LevelController.cpp index f99d7fd6..4404fb3a 100644 --- a/src/logic/LevelController.cpp +++ b/src/logic/LevelController.cpp @@ -4,7 +4,7 @@ #include "debug/Logger.hpp" #include "engine/Engine.hpp" -#include "files/WorldFiles.hpp" +#include "world/files/WorldFiles.hpp" #include "maths/voxmaths.hpp" #include "objects/Entities.hpp" #include "objects/Players.hpp" diff --git a/src/logic/scripting/lua/libs/libcore.cpp b/src/logic/scripting/lua/libs/libcore.cpp index 80128971..7b8c152c 100644 --- a/src/logic/scripting/lua/libs/libcore.cpp +++ b/src/logic/scripting/lua/libs/libcore.cpp @@ -7,9 +7,9 @@ #include "content/Content.hpp" #include "debug/Logger.hpp" #include "engine/Engine.hpp" -#include "files/engine_paths.hpp" -#include "files/files.hpp" -#include "files/settings_io.hpp" +#include "io/engine_paths.hpp" +#include "io/io.hpp" +#include "io/settings_io.hpp" #include "frontend/menu.hpp" #include "frontend/screens/MenuScreen.hpp" #include "graphics/core/Texture.hpp" @@ -259,8 +259,7 @@ static int l_load_texture(lua::State* L) { } static int l_open_folder(lua::State* L) { - auto path = engine->getPaths().resolve(lua::require_string(L, 1)); - platform::open_folder(path); + platform::open_folder(io::resolve(lua::require_string(L, 1))); return 0; } diff --git a/src/logic/scripting/lua/libs/libfile.cpp b/src/logic/scripting/lua/libs/libfile.cpp index 7fc70f46..f95c0679 100644 --- a/src/logic/scripting/lua/libs/libfile.cpp +++ b/src/logic/scripting/lua/libs/libfile.cpp @@ -4,8 +4,8 @@ #include "coders/gzip.hpp" #include "engine/Engine.hpp" -#include "files/engine_paths.hpp" -#include "files/files.hpp" +#include "io/engine_paths.hpp" +#include "io/io.hpp" #include "util/stringutil.hpp" #include "api_lua.hpp" #include "../lua_engine.hpp" @@ -13,17 +13,6 @@ namespace fs = std::filesystem; using namespace scripting; -static fs::path resolve_path(const std::string& path) { - return engine->getPaths().resolve(path); -} - -static fs::path resolve_path_soft(const std::string& path) { - if (path.find(':') == std::string::npos) { - return fs::u8path(""); - } - return engine->getPaths().resolve(path, false); -} - static int l_find(lua::State* L) { auto path = lua::require_string(L, 1); try { @@ -34,17 +23,17 @@ static int l_find(lua::State* L) { } static int l_resolve(lua::State* L) { - fs::path path = resolve_path(lua::require_string(L, 1)); - return lua::pushstring(L, path.u8string()); + io::path path = lua::require_string(L, 1); + return lua::pushstring(L, path.string()); } static int l_read(lua::State* L) { - fs::path path = resolve_path(lua::require_string(L, 1)); - if (fs::is_regular_file(path)) { - return lua::pushstring(L, files::read_string(path)); + io::path path = lua::require_string(L, 1); + if (io::is_regular_file(path)) { + return lua::pushstring(L, io::read_string(path)); } throw std::runtime_error( - "file does not exists " + util::quote(path.u8string()) + "file does not exists " + util::quote(path.string()) ); } @@ -52,10 +41,9 @@ static std::set writeable_entry_points { "world", "export", "config" }; -static fs::path get_writeable_path(lua::State* L) { - std::string rawpath = lua::require_string(L, 1); - fs::path path = resolve_path(rawpath); - auto entryPoint = rawpath.substr(0, rawpath.find(':')); +static io::path get_writeable_path(lua::State* L) { + io::path path = lua::require_string(L, 1); + auto entryPoint = path.entryPoint(); if (writeable_entry_points.find(entryPoint) == writeable_entry_points.end()) { throw std::runtime_error("access denied"); } @@ -63,72 +51,67 @@ static fs::path get_writeable_path(lua::State* L) { } static int l_write(lua::State* L) { - fs::path path = get_writeable_path(L); + io::path path = get_writeable_path(L); std::string text = lua::require_string(L, 2); - files::write_string(path, text); + io::write_string(path, text); return 1; } static int l_remove(lua::State* L) { - std::string rawpath = lua::require_string(L, 1); - fs::path path = resolve_path(rawpath); - auto entryPoint = rawpath.substr(0, rawpath.find(':')); + io::path path = lua::require_string(L, 1); + auto entryPoint = path.entryPoint(); if (writeable_entry_points.find(entryPoint) == writeable_entry_points.end()) { throw std::runtime_error("access denied"); } - return lua::pushboolean(L, fs::remove(path)); + return lua::pushboolean(L, io::remove(path)); } static int l_remove_tree(lua::State* L) { - std::string rawpath = lua::require_string(L, 1); - fs::path path = resolve_path(rawpath); - auto entryPoint = rawpath.substr(0, rawpath.find(':')); + io::path path = lua::require_string(L, 1); + auto entryPoint = path.entryPoint(); if (writeable_entry_points.find(entryPoint) == writeable_entry_points.end()) { throw std::runtime_error("access denied"); } - return lua::pushinteger(L, fs::remove_all(path)); + return lua::pushinteger(L, io::remove_all(path)); } static int l_exists(lua::State* L) { - fs::path path = resolve_path_soft(lua::require_string(L, 1)); - return lua::pushboolean(L, fs::exists(path)); + return lua::pushboolean(L, io::exists(lua::require_string(L, 1))); } static int l_isfile(lua::State* L) { - fs::path path = resolve_path_soft(lua::require_string(L, 1)); - return lua::pushboolean(L, fs::is_regular_file(path)); + return lua::pushboolean(L, io::is_regular_file(lua::require_string(L, 1))); } static int l_isdir(lua::State* L) { - fs::path path = resolve_path_soft(lua::require_string(L, 1)); - return lua::pushboolean(L, fs::is_directory(path)); + return lua::pushboolean(L, io::is_directory(lua::require_string(L, 1))); } static int l_length(lua::State* L) { - fs::path path = resolve_path(lua::require_string(L, 1)); - if (fs::exists(path)) { - return lua::pushinteger(L, fs::file_size(path)); + io::path path = lua::require_string(L, 1); + if (io::exists(path)) { + return lua::pushinteger(L, io::file_size(path)); } else { return lua::pushinteger(L, -1); } } static int l_mkdir(lua::State* L) { - fs::path path = resolve_path(lua::require_string(L, 1)); - return lua::pushboolean(L, fs::create_directory(path)); + io::path path = lua::require_string(L, 1); + return lua::pushboolean(L, io::create_directory(path)); } static int l_mkdirs(lua::State* L) { - fs::path path = resolve_path(lua::require_string(L, 1)); - return lua::pushboolean(L, fs::create_directories(path)); + io::path path = lua::require_string(L, 1); + return lua::pushboolean(L, io::create_directories(path)); } static int l_read_bytes(lua::State* L) { - fs::path path = resolve_path(lua::require_string(L, 1)); - if (fs::is_regular_file(path)) { - size_t length = static_cast(fs::file_size(path)); + io::path path = lua::require_string(L, 1); + if (io::is_regular_file(path)) { + size_t length = static_cast(io::file_size(path)); - auto bytes = files::read_bytes(path, length); + auto bytes = io::read_bytes(path, length); lua::createtable(L, length, 0); int newTable = lua::gettop(L); @@ -140,24 +123,24 @@ static int l_read_bytes(lua::State* L) { return 1; } throw std::runtime_error( - "file does not exists " + util::quote(path.u8string()) + "file does not exists " + util::quote(path.string()) ); } static int l_write_bytes(lua::State* L) { - fs::path path = get_writeable_path(L); + io::path path = get_writeable_path(L); if (auto bytearray = lua::touserdata(L, 2)) { auto& bytes = bytearray->data(); return lua::pushboolean( - L, files::write_bytes(path, bytes.data(), bytes.size()) + L, io::write_bytes(path, bytes.data(), bytes.size()) ); } std::vector bytes; lua::read_bytes_from_table(L, 2, bytes); return lua::pushboolean( - L, files::write_bytes(path, bytes.data(), bytes.size()) + L, io::write_bytes(path, bytes.data(), bytes.size()) ); } @@ -176,18 +159,16 @@ static int l_list(lua::State* L) { if (dirname.find(':') == std::string::npos) { return l_list_all_res(L, dirname); } - fs::path path = resolve_path(dirname); - if (!fs::is_directory(path)) { + io::path path = dirname; + if (!io::is_directory(path)) { throw std::runtime_error( - util::quote(path.u8string()) + " is not a directory" + util::quote(path.string()) + " is not a directory" ); } lua::createtable(L, 0, 0); size_t index = 1; - for (auto& entry : fs::directory_iterator(path)) { - auto name = entry.path().filename().u8string(); - auto file = dirname + "/" + name; - lua::pushstring(L, file); + for (const auto& file : io::directory_iterator(path)) { + lua::pushstring(L, file.string()); lua::rawseti(L, index); index++; } @@ -239,9 +220,8 @@ static int l_read_combined_object(lua::State* L) { } static int l_is_writeable(lua::State* L) { - std::string rawpath = lua::require_string(L, 1); - fs::path path = resolve_path(rawpath); - auto entryPoint = rawpath.substr(0, rawpath.find(':')); + io::path path = lua::require_string(L, 1); + auto entryPoint = path.entryPoint(); if (writeable_entry_points.find(entryPoint) == writeable_entry_points.end()) { return lua::pushboolean(L, false); } diff --git a/src/logic/scripting/lua/libs/libgeneration.cpp b/src/logic/scripting/lua/libs/libgeneration.cpp index 90b681c3..855700a9 100644 --- a/src/logic/scripting/lua/libs/libgeneration.cpp +++ b/src/logic/scripting/lua/libs/libgeneration.cpp @@ -1,7 +1,7 @@ #include "api_lua.hpp" -#include "files/files.hpp" -#include "files/util.hpp" +#include "io/io.hpp" +#include "io/util.hpp" #include "coders/binary_json.hpp" #include "world/Level.hpp" #include "world/generator/VoxelFragment.hpp" @@ -14,10 +14,10 @@ using namespace scripting; static int l_save_fragment(lua::State* L) { const auto& paths = engine->getPaths(); auto fragment = lua::touserdata(L, 1); - auto file = paths.resolve(lua::require_string(L, 2), true); + auto file = lua::require_string(L, 2); auto map = fragment->getFragment()->serialize(); auto bytes = json::to_binary(map, true); - files::write_bytes(file, bytes.data(), bytes.size()); + io::write_bytes(file, bytes.data(), bytes.size()); return 0; } @@ -35,13 +35,11 @@ static int l_create_fragment(lua::State* L) { } static int l_load_fragment(lua::State* L) { - const auto& paths = engine->getPaths(); - auto filename = lua::require_string(L, 1); - auto path = paths.resolve(filename); - if (!std::filesystem::exists(path)) { - throw std::runtime_error("file "+path.u8string()+" does not exist"); + io::path path = lua::require_string(L, 1); + if (!io::exists(path)) { + throw std::runtime_error("file "+path.string()+" does not exist"); } - auto map = files::read_binary_json(path); + auto map = io::read_binary_json(path); auto fragment = std::make_shared(); fragment->deserialize(map); diff --git a/src/logic/scripting/lua/libs/libgui.cpp b/src/logic/scripting/lua/libs/libgui.cpp index 995cb625..d3b9c93a 100644 --- a/src/logic/scripting/lua/libs/libgui.cpp +++ b/src/logic/scripting/lua/libs/libgui.cpp @@ -808,15 +808,15 @@ static int l_gui_alert(lua::State* L) { } static int l_gui_load_document(lua::State* L) { - auto filename = lua::require_string(L, 1); + io::path filename = lua::require_string(L, 1); auto alias = lua::require_string(L, 2); auto args = lua::tovalue(L, 3); auto documentPtr = UiDocument::read( scripting::get_root_environment(), alias, - engine->getPaths().resolve(filename), - filename + filename, + filename.string() ); auto document = documentPtr.get(); engine->getAssets()->store(std::move(documentPtr), alias); diff --git a/src/logic/scripting/lua/libs/libinput.cpp b/src/logic/scripting/lua/libs/libinput.cpp index 4121a384..09f762ef 100644 --- a/src/logic/scripting/lua/libs/libinput.cpp +++ b/src/logic/scripting/lua/libs/libinput.cpp @@ -1,7 +1,7 @@ #include #include "engine/Engine.hpp" -#include "files/files.hpp" +#include "io/io.hpp" #include "frontend/hud.hpp" #include "frontend/screens/Screen.hpp" #include "graphics/ui/GUI.hpp" @@ -135,23 +135,22 @@ static int l_is_pressed(lua::State* L) { } } -static void resetPackBindings(fs::path& packFolder) { - auto configFolder = packFolder/fs::path("config"); - auto bindsFile = configFolder/fs::path("bindings.toml"); - if (fs::is_regular_file(bindsFile)) { +static void reset_pack_bindings(const io::path& packFolder) { + auto configFolder = packFolder / "config"; + auto bindsFile = configFolder / "bindings.toml"; + if (io::is_regular_file(bindsFile)) { Events::loadBindings( - bindsFile.u8string(), - files::read_string(bindsFile), + bindsFile.string(), + io::read_string(bindsFile), BindType::REBIND ); } } static int l_reset_bindings(lua::State*) { - auto resFolder = engine->getPaths().getResourcesFolder(); - resetPackBindings(resFolder); + reset_pack_bindings("res:"); for (auto& pack : engine->getContentPacks()) { - resetPackBindings(pack.folder); + reset_pack_bindings(pack.folder); } return 0; } diff --git a/src/logic/scripting/lua/libs/libpack.cpp b/src/logic/scripting/lua/libs/libpack.cpp index 5b3977a4..dcff659a 100644 --- a/src/logic/scripting/lua/libs/libpack.cpp +++ b/src/logic/scripting/lua/libs/libpack.cpp @@ -7,8 +7,8 @@ #include "assets/AssetsLoader.hpp" #include "content/Content.hpp" #include "engine/Engine.hpp" -#include "files/WorldFiles.hpp" -#include "files/engine_paths.hpp" +#include "world/files/WorldFiles.hpp" +#include "io/engine_paths.hpp" #include "world/Level.hpp" #include "world/World.hpp" #include "api_lua.hpp" @@ -21,7 +21,7 @@ static int l_pack_get_folder(lua::State* L) { for (auto& pack : packs) { if (pack.id == packName) { - return lua::pushstring(L, pack.folder.u8string() + "/"); + return lua::pushstring(L, pack.folder.string() + "/"); } } return lua::pushstring(L, ""); @@ -40,7 +40,7 @@ static int l_pack_get_installed(lua::State* L) { /// @brief pack.get_available() -> array static int l_pack_get_available(lua::State* L) { - fs::path worldFolder(""); + io::path worldFolder; if (level) { worldFolder = level->getWorld()->wfile->getFolder(); } @@ -88,7 +88,7 @@ static int l_pack_get_info( auto assets = engine->getAssets(); std::string icon = pack.id + ".icon"; if (!AssetsLoader::loadExternalTexture( - assets, icon, {pack.folder / fs::path("icon.png")} + assets, icon, {pack.folder / "icon.png"} )) { icon = "gui/no_icon"; } @@ -146,7 +146,7 @@ static int pack_get_infos(lua::State* L) { } } if (!ids.empty()) { - fs::path worldFolder(""); + io::path worldFolder; if (level) { worldFolder = level->getWorld()->wfile->getFolder(); } @@ -188,7 +188,7 @@ static int l_pack_get_info(lua::State* L) { return pack.id == packid; }); if (found == packs.end()) { - fs::path worldFolder(""); + io::path worldFolder; if (level) { worldFolder = level->getWorld()->wfile->getFolder(); } @@ -225,7 +225,7 @@ static int l_pack_assemble(lua::State* L) { ids.push_back(lua::require_string(L, -1)); lua::pop(L); } - fs::path worldFolder(""); + io::path worldFolder; if (level) { worldFolder = level->getWorld()->wfile->getFolder(); } diff --git a/src/logic/scripting/lua/libs/libworld.cpp b/src/logic/scripting/lua/libs/libworld.cpp index c5abf6be..f54ce4e3 100644 --- a/src/logic/scripting/lua/libs/libworld.cpp +++ b/src/logic/scripting/lua/libs/libworld.cpp @@ -7,9 +7,9 @@ #include "coders/json.hpp" #include "content/Content.hpp" #include "engine/Engine.hpp" -#include "files/WorldFiles.hpp" -#include "files/engine_paths.hpp" -#include "files/files.hpp" +#include "world/files/WorldFiles.hpp" +#include "io/engine_paths.hpp" +#include "io/io.hpp" #include "lighting/Lighting.hpp" #include "voxels/Chunk.hpp" #include "voxels/Chunks.hpp" @@ -45,12 +45,12 @@ static int l_get_list(lua::State* L) { const auto& folder = worlds[i]; auto root = - json::parse(files::read_string(folder / fs::u8path("world.json"))); + json::parse(io::read_string(folder / "world.json")); const auto& versionMap = root["version"]; int versionMajor = versionMap["major"].asInteger(); int versionMinor = versionMap["minor"].asInteger(); - auto name = folder.filename().u8string(); + auto name = folder.name(); lua::pushstring(L, name); lua::setfield(L, "name"); @@ -59,8 +59,8 @@ static int l_get_list(lua::State* L) { if (!engine->isHeadless() && !AssetsLoader::loadExternalTexture( assets, icon, - {worlds[i] / fs::path("icon.png"), - worlds[i] / fs::path("preview.png")} + {worlds[i] / "icon.png", + worlds[i] / "preview.png"} )) { icon = "gui/no_world_icon"; } @@ -106,7 +106,7 @@ static int l_get_seed(lua::State* L) { static int l_exists(lua::State* L) { auto name = lua::require_string(L, 1); auto worldsDir = engine->getPaths().getWorldFolderByName(name); - return lua::pushboolean(L, fs::is_directory(worldsDir)); + return lua::pushboolean(L, io::is_directory(worldsDir)); } static int l_is_day(lua::State* L) { diff --git a/src/logic/scripting/lua/lua_engine.cpp b/src/logic/scripting/lua/lua_engine.cpp index 1f4bde7d..251f789b 100644 --- a/src/logic/scripting/lua/lua_engine.cpp +++ b/src/logic/scripting/lua/lua_engine.cpp @@ -3,8 +3,8 @@ #include #include -#include "files/files.hpp" -#include "files/engine_paths.hpp" +#include "io/io.hpp" +#include "io/engine_paths.hpp" #include "debug/Logger.hpp" #include "util/stringutil.hpp" #include "libs/api_lua.hpp" @@ -159,9 +159,8 @@ State* lua::create_state(const EnginePaths& paths, StateType stateType) { } init_state(L, stateType); - auto resDir = paths.getResourcesFolder(); - auto file = resDir / fs::u8path("scripts/stdmin.lua"); - auto src = files::read_string(file); + auto file = "res:scripts/stdmin.lua"; + auto src = io::read_string(file); lua::pop(L, lua::execute(L, 0, src, "core:scripts/stdmin.lua")); return L; } diff --git a/src/logic/scripting/lua/lua_extensions.cpp b/src/logic/scripting/lua/lua_extensions.cpp index e1d7a230..37ed5d01 100644 --- a/src/logic/scripting/lua/lua_extensions.cpp +++ b/src/logic/scripting/lua/lua_extensions.cpp @@ -1,4 +1,5 @@ #include +#include #include "libs/api_lua.hpp" #include "debug/Logger.hpp" diff --git a/src/logic/scripting/lua/lua_util.hpp b/src/logic/scripting/lua/lua_util.hpp index 9539fbbf..656bd504 100644 --- a/src/logic/scripting/lua/lua_util.hpp +++ b/src/logic/scripting/lua/lua_util.hpp @@ -1,13 +1,13 @@ #pragma once +#include #include #include -#include #include #include "data/dv.hpp" -#include "lua_wrapper.hpp" #include "lua_custom_types.hpp" +#include "lua_wrapper.hpp" #define GLM_ENABLE_EXPERIMENTAL #include @@ -68,7 +68,7 @@ namespace lua { return 1; } - template + template inline int pushvec_stack(lua::State* L, const glm::vec& vec) { for (int i = 0; i < n; i++) { pushnumber(L, vec[i]); @@ -76,7 +76,7 @@ namespace lua { return n; } - template + template inline int pushivec_stack(lua::State* L, const glm::vec& vec) { for (int i = 0; i < n; i++) { pushinteger(L, vec[i]); @@ -108,10 +108,10 @@ namespace lua { inline int pushquat(lua::State* L, glm::quat quat) { createtable(L, 4, 0); - + pushnumber(L, quat.w); rawseti(L, 1); - + pushnumber(L, quat.x); rawseti(L, 2); @@ -127,7 +127,7 @@ namespace lua { pushnumber(L, quat.w); rawseti(L, 1); - + pushnumber(L, quat.x); rawseti(L, 2); @@ -287,8 +287,9 @@ namespace lua { } template - inline std::enable_if_t> - newusertype(lua::State* L) { + inline std::enable_if_t> newusertype( + lua::State* L + ) { const std::string& name = T::TYPENAME; usertypeNames[typeid(T)] = name; T::createMetatable(L); @@ -386,7 +387,7 @@ namespace lua { rawgeti(L, 4); auto z = tonumber(L, -1); pop(L); - + pop(L); return glm::quat(x, y, z, w); } @@ -445,8 +446,7 @@ namespace lua { int pushvalue(lua::State*, const dv::value& value); - [[nodiscard]] - dv::value tovalue(lua::State*, int idx); + [[nodiscard]] dv::value tovalue(lua::State*, int idx); inline bool getfield(lua::State* L, const std::string& name, int idx = -1) { lua_getfield(L, idx, name.c_str()); @@ -457,11 +457,13 @@ namespace lua { return true; } - inline int requirefield(lua::State* L, const std::string& name, int idx = -1) { + inline int requirefield( + lua::State* L, const std::string& name, int idx = -1 + ) { if (getfield(L, name, idx)) { return 1; } - throw std::runtime_error("object has no member '"+name+"'"); + throw std::runtime_error("object has no member '" + name + "'"); } inline bool hasfield(lua::State* L, const std::string& name, int idx = -1) { @@ -637,7 +639,7 @@ namespace lua { } inline const char* require_string_field( - lua::State* L, const std::string& name, int idx=-1 + lua::State* L, const std::string& name, int idx = -1 ) { requirefield(L, name, idx); auto value = require_string(L, -1); @@ -646,7 +648,7 @@ namespace lua { } inline Integer require_integer_field( - lua::State* L, const std::string& name, int idx=-1 + lua::State* L, const std::string& name, int idx = -1 ) { requirefield(L, name, idx); auto value = tointeger(L, -1); @@ -655,7 +657,7 @@ namespace lua { } inline Number require_number_field( - lua::State* L, const std::string& name, int idx=-1 + lua::State* L, const std::string& name, int idx = -1 ) { requirefield(L, name, idx); auto value = tonumber(L, -1); @@ -664,7 +666,7 @@ namespace lua { } inline bool get_boolean_field( - lua::State* L, const std::string& name, bool def, int idx=-1 + lua::State* L, const std::string& name, bool def, int idx = -1 ) { if (getfield(L, name, idx)) { bool value = toboolean(L, -1); @@ -675,7 +677,7 @@ namespace lua { } inline Integer get_integer_field( - lua::State* L, const std::string& name, Integer def, int idx=-1 + lua::State* L, const std::string& name, Integer def, int idx = -1 ) { if (getfield(L, name, idx)) { auto value = tointeger(L, -1); @@ -686,7 +688,7 @@ namespace lua { } inline Number get_number_field( - lua::State* L, const std::string& name, Number def, int idx=-1 + lua::State* L, const std::string& name, Number def, int idx = -1 ) { if (getfield(L, name, idx)) { auto value = tonumber(L, -1); @@ -697,15 +699,20 @@ namespace lua { } inline Integer get_integer_field( - lua::State* L, const std::string& name, - Integer def, Integer min, Integer max, int idx=-1 + lua::State* L, + const std::string& name, + Integer def, + Integer min, + Integer max, + int idx = -1 ) { if (getfield(L, name, idx)) { auto value = tointeger(L, -1); if (value < min || value > max) { throw std::runtime_error( - "value is out of range [" - +std::to_string(min)+", "+std::to_string(max)+"]"); + "value is out of range [" + std::to_string(min) + ", " + + std::to_string(max) + "]" + ); } pop(L); return value; diff --git a/src/logic/scripting/lua/usertypes/lua_type_heightmap.cpp b/src/logic/scripting/lua/usertypes/lua_type_heightmap.cpp index 0db89634..9e7cabc8 100644 --- a/src/logic/scripting/lua/usertypes/lua_type_heightmap.cpp +++ b/src/logic/scripting/lua/usertypes/lua_type_heightmap.cpp @@ -9,7 +9,7 @@ #define FNL_IMPL #include "maths/FastNoiseLite.h" #include "coders/imageio.hpp" -#include "files/util.hpp" +#include "io/util.hpp" #include "graphics/core/ImageData.hpp" #include "maths/Heightmap.hpp" #include "engine/Engine.hpp" @@ -52,7 +52,7 @@ const float* LuaHeightmap::getValues() const { static int l_dump(lua::State* L) { const auto& paths = scripting::engine->getPaths(); if (auto heightmap = touserdata(L, 1)) { - auto file = paths.resolve(require_string(L, 2)); + io::path file = require_string(L, 2); uint w = heightmap->getWidth(); uint h = heightmap->getHeight(); ImageData image(ImageFormat::rgb888, w, h); @@ -68,7 +68,7 @@ static int l_dump(lua::State* L) { raster[i*3 + 2] = val; } } - imageio::write(file.u8string(), &image); + imageio::write(file, &image); } return 0; } diff --git a/src/logic/scripting/scripting.cpp b/src/logic/scripting/scripting.cpp index 52eab4cd..92ed66a2 100644 --- a/src/logic/scripting/scripting.cpp +++ b/src/logic/scripting/scripting.cpp @@ -8,8 +8,8 @@ #include "content/ContentPack.hpp" #include "debug/Logger.hpp" #include "engine/Engine.hpp" -#include "files/engine_paths.hpp" -#include "files/files.hpp" +#include "io/engine_paths.hpp" +#include "io/io.hpp" #include "frontend/UiDocument.hpp" #include "items/Inventory.hpp" #include "items/ItemDef.hpp" @@ -41,12 +41,11 @@ const ContentIndices* scripting::indices = nullptr; BlocksController* scripting::blocks = nullptr; LevelController* scripting::controller = nullptr; -void scripting::load_script(const fs::path& name, bool throwable) { - const auto& paths = scripting::engine->getPaths(); - fs::path file = paths.getResourcesFolder() / fs::path("scripts") / name; - std::string src = files::read_string(file); +void scripting::load_script(const io::path& name, bool throwable) { + io::path file = io::path("res:scripts") / name; + std::string src = io::read_string(file); auto L = lua::get_main_state(); - lua::loadbuffer(L, 0, src, "core:scripts/"+name.u8string()); + lua::loadbuffer(L, 0, src, "core:scripts/"+name.string()); if (throwable) { lua::call(L, 0, 0); } else { @@ -57,11 +56,11 @@ void scripting::load_script(const fs::path& name, bool throwable) { int scripting::load_script( int env, const std::string& type, - const fs::path& file, + const io::path& file, const std::string& fileName ) { - std::string src = files::read_string(file); - logger.info() << "script (" << type << ") " << file.u8string(); + std::string src = io::read_string(file); + logger.info() << "script (" << type << ") " << file.string(); return lua::execute(lua::get_main_state(), env, src, fileName); } @@ -69,8 +68,8 @@ void scripting::initialize(Engine* engine) { scripting::engine = engine; lua::initialize(engine->getPaths(), engine->getCoreParameters()); - load_script(fs::path("stdlib.lua"), true); - load_script(fs::path("classes.lua"), true); + load_script(io::path("stdlib.lua"), true); + load_script(io::path("classes.lua"), true); } class LuaCoroutine : public Process { @@ -113,12 +112,12 @@ public: }; std::unique_ptr scripting::start_coroutine( - const std::filesystem::path& script + const io::path& script ) { auto L = lua::get_main_state(); if (lua::getglobal(L, "__vc_start_coroutine")) { - auto source = files::read_string(script); - lua::loadbuffer(L, 0, source, script.filename().u8string()); + auto source = io::read_string(script); + lua::loadbuffer(L, 0, source, script.name()); if (lua::call(L, 1)) { int id = lua::tointeger(L, -1); lua::pop(L, 2); @@ -253,8 +252,8 @@ void scripting::on_content_load(Content* content) { lua::setfield(L, "properties"); lua::pop(L); } - load_script(fs::path("post_content.lua"), true); - load_script(fs::path("stdcmd.lua"), true); + load_script("post_content.lua", true); + load_script("stdcmd.lua", true); } void scripting::on_world_load(LevelController* controller) { @@ -829,7 +828,7 @@ int scripting::get_values_on_stack() { void scripting::load_content_script( const scriptenv& senv, const std::string& prefix, - const fs::path& file, + const io::path& file, const std::string& fileName, BlockFuncsSet& funcsset ) { @@ -854,7 +853,7 @@ void scripting::load_content_script( void scripting::load_content_script( const scriptenv& senv, const std::string& prefix, - const fs::path& file, + const io::path& file, const std::string& fileName, ItemFuncsSet& funcsset ) { @@ -869,11 +868,11 @@ void scripting::load_content_script( } void scripting::load_entity_component( - const std::string& name, const fs::path& file, const std::string& fileName + const std::string& name, const io::path& file, const std::string& fileName ) { auto L = lua::get_main_state(); - std::string src = files::read_string(file); - logger.info() << "script (component) " << file.u8string(); + std::string src = io::read_string(file); + logger.info() << "script (component) " << file.string(); lua::loadbuffer(L, 0, src, fileName); lua::store_in(L, lua::CHUNKS_TABLE, name); } @@ -881,7 +880,7 @@ void scripting::load_entity_component( void scripting::load_world_script( const scriptenv& senv, const std::string& prefix, - const fs::path& file, + const io::path& file, const std::string& fileName, WorldFuncsSet& funcsset ) { @@ -917,7 +916,7 @@ void scripting::load_world_script( void scripting::load_layout_script( const scriptenv& senv, const std::string& prefix, - const fs::path& file, + const io::path& file, const std::string& fileName, uidocscript& script ) { diff --git a/src/logic/scripting/scripting.hpp b/src/logic/scripting/scripting.hpp index b447de28..bdd5d5d2 100644 --- a/src/logic/scripting/scripting.hpp +++ b/src/logic/scripting/scripting.hpp @@ -1,11 +1,11 @@ #pragma once -#include #include #include #include #include +#include "io/fwd.hpp" #include "data/dv.hpp" #include "delegates.hpp" #include "typedefs.hpp" @@ -61,7 +61,7 @@ namespace scripting { void process_post_runnables(); std::unique_ptr start_coroutine( - const std::filesystem::path& script + const io::path& script ); void on_world_load(LevelController* controller); @@ -150,7 +150,7 @@ namespace scripting { void load_content_script( const scriptenv& env, const std::string& prefix, - const std::filesystem::path& file, + const io::path& file, const std::string& fileName, BlockFuncsSet& funcsset ); @@ -164,7 +164,7 @@ namespace scripting { void load_content_script( const scriptenv& env, const std::string& prefix, - const std::filesystem::path& file, + const io::path& file, const std::string& fileName, ItemFuncsSet& funcsset ); @@ -175,13 +175,13 @@ namespace scripting { /// @param fileName script file path using the engine format void load_entity_component( const std::string& name, - const std::filesystem::path& file, + const io::path& file, const std::string& fileName ); std::unique_ptr load_generator( const GeneratorDef& def, - const std::filesystem::path& file, + const io::path& file, const std::string& dirPath ); @@ -193,7 +193,7 @@ namespace scripting { void load_world_script( const scriptenv& env, const std::string& packid, - const std::filesystem::path& file, + const io::path& file, const std::string& fileName, WorldFuncsSet& funcsset ); @@ -207,7 +207,7 @@ namespace scripting { void load_layout_script( const scriptenv& env, const std::string& prefix, - const std::filesystem::path& file, + const io::path& file, const std::string& fileName, uidocscript& script ); diff --git a/src/logic/scripting/scripting_commons.hpp b/src/logic/scripting/scripting_commons.hpp index 3bc31825..493e115c 100644 --- a/src/logic/scripting/scripting_commons.hpp +++ b/src/logic/scripting/scripting_commons.hpp @@ -1,15 +1,16 @@ #pragma once #include -#include + +#include "io/fwd.hpp" namespace scripting { - void load_script(const std::filesystem::path& name, bool throwable); + void load_script(const io::path& name, bool throwable); [[nodiscard]] int load_script( int env, const std::string& type, - const std::filesystem::path& file, + const io::path& file, const std::string& fileName ); } diff --git a/src/logic/scripting/scripting_hud.cpp b/src/logic/scripting/scripting_hud.cpp index c13a0010..d6f618cb 100644 --- a/src/logic/scripting/scripting_hud.cpp +++ b/src/logic/scripting/scripting_hud.cpp @@ -2,7 +2,7 @@ #include "debug/Logger.hpp" #include "engine/Engine.hpp" -#include "files/files.hpp" +#include "io/io.hpp" #include "frontend/hud.hpp" #include "frontend/UiDocument.hpp" #include "graphics/render/WorldRenderer.hpp" @@ -19,11 +19,11 @@ Hud* scripting::hud = nullptr; WorldRenderer* scripting::renderer = nullptr; static void load_script(const std::string& name) { - auto file = engine->getPaths().getResourcesFolder() / "scripts" / name; - std::string src = files::read_string(file); - logger.info() << "loading script " << file.u8string(); + auto file = io::path("res:scripts") / name; + std::string src = io::read_string(file); + logger.info() << "loading script " << file.string(); - lua::execute(lua::get_main_state(), 0, src, file.u8string()); + lua::execute(lua::get_main_state(), 0, src, file.string()); } void scripting::on_frontend_init(Hud* hud, WorldRenderer* renderer) { @@ -80,12 +80,12 @@ void scripting::on_frontend_close() { void scripting::load_hud_script( const scriptenv& senv, const std::string& packid, - const fs::path& file, + const io::path& file, const std::string& fileName ) { int env = *senv; - std::string src = files::read_string(file); - logger.info() << "loading script " << file.u8string(); + std::string src = io::read_string(file); + logger.info() << "loading script " << file.string(); lua::execute(lua::get_main_state(), env, src, fileName); diff --git a/src/logic/scripting/scripting_hud.hpp b/src/logic/scripting/scripting_hud.hpp index f6829976..1df47df2 100644 --- a/src/logic/scripting/scripting_hud.hpp +++ b/src/logic/scripting/scripting_hud.hpp @@ -1,13 +1,11 @@ #pragma once -#include #include #include #include #include "typedefs.hpp" - -namespace fs = std::filesystem; +#include "io/fwd.hpp" class Hud; class WorldRenderer; @@ -33,7 +31,7 @@ namespace scripting { void load_hud_script( const scriptenv& env, const std::string& packid, - const fs::path& file, + const io::path& file, const std::string& fileName ); diff --git a/src/logic/scripting/scripting_world_generation.cpp b/src/logic/scripting/scripting_world_generation.cpp index 1169b64f..1e34122b 100644 --- a/src/logic/scripting/scripting_world_generation.cpp +++ b/src/logic/scripting/scripting_world_generation.cpp @@ -13,7 +13,7 @@ #include "data/dv.hpp" #include "world/generator/GeneratorDef.hpp" #include "util/timeutil.hpp" -#include "files/files.hpp" +#include "io/io.hpp" #include "engine/Engine.hpp" #include "debug/Logger.hpp" @@ -26,10 +26,15 @@ class LuaGeneratorScript : public GeneratorScript { const GeneratorDef& def; scriptenv env = nullptr; - fs::path file; + io::path file; std::string dirPath; public: - LuaGeneratorScript(State* L, const GeneratorDef& def, const fs::path& file, const std::string& dirPath) + LuaGeneratorScript( + State* L, + const GeneratorDef& def, + const io::path& file, + const std::string& dirPath + ) : L(L), def(def), file(file), dirPath(dirPath) { } @@ -54,10 +59,10 @@ public: pop(L); - if (fs::exists(file)) { - std::string src = files::read_string(file); - logger.info() << "script (generator) " << file.u8string(); - pop(L, execute(L, *env, src, file.u8string())); + if (io::exists(file)) { + std::string src = io::read_string(file); + logger.info() << "script (generator) " << file.string(); + pop(L, execute(L, *env, src, file.string())); } else { // Use default (empty) script pop(L, execute(L, *env, "", "")); @@ -252,7 +257,7 @@ public: std::unique_ptr scripting::load_generator( const GeneratorDef& def, - const fs::path& file, + const io::path& file, const std::string& dirPath ) { auto L = create_state(engine->getPaths(), StateType::GENERATOR); diff --git a/src/util/command_line.cpp b/src/util/command_line.cpp index 483b0f8b..ee0ace92 100644 --- a/src/util/command_line.cpp +++ b/src/util/command_line.cpp @@ -1,9 +1,8 @@ #include "command_line.hpp" -#include #include -#include "files/engine_paths.hpp" +#include "io/engine_paths.hpp" #include "util/ArgsReader.hpp" #include "engine/Engine.hpp" @@ -14,10 +13,10 @@ static bool perform_keyword( ) { if (keyword == "--res") { auto token = reader.next(); - params.resFolder = fs::u8path(token); + params.resFolder = token; } else if (keyword == "--dir") { auto token = reader.next(); - params.userFolder = fs::u8path(token); + params.userFolder = token; } else if (keyword == "--help" || keyword == "-h") { std::cout << "VoxelCore v" << ENGINE_VERSION_STRING << "\n\n"; std::cout << "command-line arguments:\n"; @@ -38,11 +37,11 @@ static bool perform_keyword( } else if (keyword == "--test") { auto token = reader.next(); params.testMode = true; - params.scriptFile = fs::u8path(token); + params.scriptFile = token; } else if (keyword == "--script") { auto token = reader.next(); params.testMode = false; - params.scriptFile = fs::u8path(token); + params.scriptFile = token; } else { throw std::runtime_error("unknown argument " + keyword); } diff --git a/src/voxels/Chunks.cpp b/src/voxels/Chunks.cpp index bf9b2990..5b5a2cd9 100644 --- a/src/voxels/Chunks.cpp +++ b/src/voxels/Chunks.cpp @@ -9,7 +9,7 @@ #include "data/StructLayout.hpp" #include "coders/byte_utils.hpp" #include "content/Content.hpp" -#include "files/WorldFiles.hpp" +#include "world/files/WorldFiles.hpp" #include "graphics/core/Mesh.hpp" #include "lighting/Lightmap.hpp" #include "maths/aabb.hpp" diff --git a/src/voxels/GlobalChunks.cpp b/src/voxels/GlobalChunks.cpp index 1e67a374..a4c11151 100644 --- a/src/voxels/GlobalChunks.cpp +++ b/src/voxels/GlobalChunks.cpp @@ -5,7 +5,7 @@ #include "content/Content.hpp" #include "coders/json.hpp" #include "debug/Logger.hpp" -#include "files/WorldFiles.hpp" +#include "world/files/WorldFiles.hpp" #include "items/Inventories.hpp" #include "lighting/Lightmap.hpp" #include "maths/voxmaths.hpp" diff --git a/src/voxels/compressed_chunks.cpp b/src/voxels/compressed_chunks.cpp index 63d3d43f..46ba800d 100644 --- a/src/voxels/compressed_chunks.cpp +++ b/src/voxels/compressed_chunks.cpp @@ -2,8 +2,9 @@ #include "coders/rle.hpp" #include "coders/gzip.hpp" + +#include "world/files/WorldFiles.hpp" #include "content/Content.hpp" -#include "files/WorldFiles.hpp" inline constexpr int HAS_VOXELS = 0x1; inline constexpr int HAS_METADATA = 0x2; diff --git a/src/world/World.cpp b/src/world/World.cpp index 54785862..313f75cd 100644 --- a/src/world/World.cpp +++ b/src/world/World.cpp @@ -7,7 +7,7 @@ #include "content/Content.hpp" #include "content/ContentReport.hpp" #include "debug/Logger.hpp" -#include "files/WorldFiles.hpp" +#include "world/files/WorldFiles.hpp" #include "items/Inventories.hpp" #include "objects/Entities.hpp" #include "objects/Player.hpp" @@ -61,7 +61,7 @@ void World::writeResources(const Content& content) { } } } - files::write_json(wfile->getResourcesFile(), root); + io::write_json(wfile->getResourcesFile(), root); } void World::write(Level* level) { @@ -70,7 +70,7 @@ void World::write(Level* level) { wfile->write(this, &content); auto playerFile = level->players->serialize(); - files::write_json(wfile->getPlayerFile(), playerFile); + io::write_json(wfile->getPlayerFile(), playerFile); writeResources(content); } @@ -78,7 +78,7 @@ void World::write(Level* level) { std::unique_ptr World::create( const std::string& name, const std::string& generator, - const fs::path& directory, + const io::path& directory, uint64_t seed, EngineSettings& settings, const Content& content, @@ -98,7 +98,7 @@ std::unique_ptr World::create( logger.info() << "created nameless world"; } else { logger.info() << "created world '" << name << "' (" - << directory.u8string() << ")"; + << directory.string() << ")"; } logger.info() << "world seed: " << seed << " generator: " << generator; return std::make_unique(std::move(world), content, settings); @@ -116,7 +116,7 @@ std::unique_ptr World::load( throw world_load_error("could not to find world.json"); } logger.info() << "loading world " << info->name << " (" - << worldFilesPtr->getFolder().u8string() << ")"; + << worldFilesPtr->getFolder().string() << ")"; logger.info() << "world version: " << info->major << "." << info->minor << " seed: " << info->seed << " generator: " << info->generator; @@ -129,12 +129,12 @@ std::unique_ptr World::load( auto level = std::make_unique(std::move(world), content, settings); - fs::path file = wfile->getPlayerFile(); - if (!fs::is_regular_file(file)) { + io::path file = wfile->getPlayerFile(); + if (!io::is_regular_file(file)) { logger.warning() << "player.json does not exists"; level->players->create(); } else { - auto playerRoot = files::read_json(file); + auto playerRoot = io::read_json(file); level->players->deserialize(playerRoot); if (!playerRoot["players"].empty()) { @@ -149,8 +149,8 @@ std::unique_ptr World::load( std::shared_ptr World::checkIndices( const std::shared_ptr& worldFiles, const Content* content ) { - fs::path indicesFile = worldFiles->getIndicesFile(); - if (fs::is_regular_file(indicesFile)) { + io::path indicesFile = worldFiles->getIndicesFile(); + if (io::is_regular_file(indicesFile)) { return ContentReport::create(worldFiles, indicesFile, content); } return nullptr; diff --git a/src/world/World.hpp b/src/world/World.hpp index da1ebd81..531e7084 100644 --- a/src/world/World.hpp +++ b/src/world/World.hpp @@ -1,10 +1,10 @@ #pragma once -#include #include #include #include +#include "io/fwd.hpp" #include "content/ContentPack.hpp" #include "interfaces/Serializable.hpp" #include "typedefs.hpp" @@ -16,8 +16,6 @@ class Level; class ContentReport; struct EngineSettings; -namespace fs = std::filesystem; - class world_load_error : public std::runtime_error { public: world_load_error(const std::string& message); @@ -100,7 +98,7 @@ public: static std::unique_ptr create( const std::string& name, const std::string& generator, - const fs::path& directory, + const io::path& directory, uint64_t seed, EngineSettings& settings, const Content& content, diff --git a/src/files/RegionsLayer.cpp b/src/world/files/RegionsLayer.cpp similarity index 93% rename from src/files/RegionsLayer.cpp rename to src/world/files/RegionsLayer.cpp index 027d6bf3..f5c5b696 100644 --- a/src/files/RegionsLayer.cpp +++ b/src/world/files/RegionsLayer.cpp @@ -6,8 +6,8 @@ #define REGION_FORMAT_MAGIC ".VOXREG" -static fs::path get_region_filename(int x, int z) { - return fs::path(std::to_string(x) + "_" + std::to_string(z) + ".bin"); +static io::path get_region_filename(int x, int z) { + return std::to_string(x) + "_" + std::to_string(z) + ".bin"; } /// @brief Read missing chunks data (null pointers) from region file @@ -25,7 +25,7 @@ static void fetch_chunks(WorldRegion* region, int x, int z, regfile* file) { } } -regfile::regfile(fs::path filename) : file(std::move(filename)) { +regfile::regfile(io::path filename) : file(std::move(filename)) { if (file.length() < REGION_HEADER_SIZE) throw std::runtime_error("incomplete region file header"); char header[REGION_HEADER_SIZE]; @@ -98,8 +98,8 @@ regfile_ptr RegionsLayer::getRegFile(glm::ivec2 coord, bool create) { } regfile_ptr RegionsLayer::createRegFile(glm::ivec2 coord) { - auto file = folder / get_region_filename(coord[0], coord[1]); - if (!fs::exists(file)) { + auto file = folder / get_region_filename(coord[0], coord[1]); + if (!io::exists(file)) { return nullptr; } if (openRegFiles.size() == MAX_OPEN_REGION_FILES) { @@ -138,7 +138,7 @@ WorldRegion* RegionsLayer::getRegion(int x, int z) { return found->second.get(); } -fs::path RegionsLayer::getRegionFilePath(int x, int z) const { +io::path RegionsLayer::getRegionFilePath(int x, int z) const { return folder / get_region_filename(x, z); } @@ -179,7 +179,7 @@ ubyte* RegionsLayer::getData(int x, int z, uint32_t& size, uint32_t& srcSize) { } void RegionsLayer::writeRegion(int x, int z, WorldRegion* entry) { - fs::path filename = folder / get_region_filename(x, z); + io::path filename = folder / get_region_filename(x, z); glm::ivec2 regcoord(x, z); if (auto regfile = getRegFile(regcoord, false)) { @@ -193,7 +193,7 @@ void RegionsLayer::writeRegion(int x, int z, WorldRegion* entry) { char header[REGION_HEADER_SIZE] = REGION_FORMAT_MAGIC; header[8] = REGION_FORMAT_VERSION; header[9] = static_cast(compression); // FIXME - std::ofstream file(filename, std::ios::out | std::ios::binary); + std::ofstream file(io::resolve(filename), std::ios::out | std::ios::binary); file.write(header, REGION_HEADER_SIZE); size_t offset = REGION_HEADER_SIZE; diff --git a/src/files/WorldConverter.cpp b/src/world/files/WorldConverter.cpp similarity index 91% rename from src/files/WorldConverter.cpp rename to src/world/files/WorldConverter.cpp index f9484142..007ad08c 100644 --- a/src/files/WorldConverter.cpp +++ b/src/world/files/WorldConverter.cpp @@ -6,9 +6,9 @@ #include #include "content/ContentReport.hpp" -#include "files/compatibility.hpp" +#include "compatibility.hpp" #include "debug/Logger.hpp" -#include "files/files.hpp" +#include "io/io.hpp" #include "objects/Player.hpp" #include "util/ThreadPool.hpp" #include "voxels/Chunk.hpp" @@ -39,17 +39,17 @@ void WorldConverter::addRegionsTasks( ) { const auto& regions = wfile->getRegions(); auto regionsFolder = regions.getRegionsFolder(layerid); - if (!fs::is_directory(regionsFolder)) { + if (!io::is_directory(regionsFolder)) { return; } - for (const auto& file : fs::directory_iterator(regionsFolder)) { + for (const auto& file :io::directory_iterator(regionsFolder)) { int x, z; - std::string name = file.path().stem().string(); + std::string name = file.stem(); if (!WorldRegions::parseRegionFilename(name, x, z)) { logger.error() << "could not parse region name " << name; continue; } - tasks.push(ConvertTask {taskType, file.path(), x, z, layerid}); + tasks.push(ConvertTask {taskType, file, x, z, layerid}); } } @@ -182,15 +182,15 @@ std::shared_ptr WorldConverter::startTask( } void WorldConverter::upgradeRegion( - const fs::path& file, int x, int z, RegionLayerIndex layer + const io::path& file, int x, int z, RegionLayerIndex layer ) const { auto path = wfile->getRegions().getRegionFilePath(layer, x, z); - auto bytes = files::read_bytes_buffer(path); + auto bytes = io::read_bytes_buffer(path); auto buffer = compatibility::convert_region_2to3(bytes, layer); - files::write_bytes(path, buffer.data(), buffer.size()); + io::write_bytes(path, buffer.data(), buffer.size()); } -void WorldConverter::convertVoxels(const fs::path& file, int x, int z) const { +void WorldConverter::convertVoxels(const io::path& file, int x, int z) const { logger.info() << "converting voxels region " << x << "_" << z; wfile->getRegions().processRegion(x, z, REGION_LAYER_VOXELS, [=](std::unique_ptr data, uint32_t*) { @@ -199,18 +199,18 @@ void WorldConverter::convertVoxels(const fs::path& file, int x, int z) const { }); } -void WorldConverter::convertInventories(const fs::path& file, int x, int z) const { +void WorldConverter::convertInventories(const io::path& file, int x, int z) const { logger.info() << "converting inventories region " << x << "_" << z; wfile->getRegions().processInventories(x, z, [=](Inventory* inventory) { inventory->convert(report.get()); }); } -void WorldConverter::convertPlayer(const fs::path& file) const { - logger.info() << "converting player " << file.u8string(); - auto map = files::read_json(file); +void WorldConverter::convertPlayer(const io::path& file) const { + logger.info() << "converting player " << file.string(); + auto map = io::read_json(file); Player::convert(map, report.get()); - files::write_json(file, map); + io::write_json(file, map); } void WorldConverter::convertBlocksData(int x, int z, const ContentReport& report) const { @@ -242,7 +242,7 @@ void WorldConverter::convertBlocksData(int x, int z, const ContentReport& report } void WorldConverter::convert(const ConvertTask& task) const { - if (!fs::is_regular_file(task.file)) return; + if (!io::is_regular_file(task.file)) return; switch (task.type) { case ConvertTaskType::UPGRADE_REGION: diff --git a/src/files/WorldConverter.hpp b/src/world/files/WorldConverter.hpp similarity index 85% rename from src/files/WorldConverter.hpp rename to src/world/files/WorldConverter.hpp index b7852c97..8c065c47 100644 --- a/src/files/WorldConverter.hpp +++ b/src/world/files/WorldConverter.hpp @@ -1,16 +1,14 @@ #pragma once -#include #include #include #include "delegates.hpp" #include "interfaces/Task.hpp" -#include "files/world_regions_fwd.hpp" +#include "io/io.hpp" +#include "world/files/world_regions_fwd.hpp" #include "typedefs.hpp" -namespace fs = std::filesystem; - class Content; class ContentReport; class WorldFiles; @@ -30,7 +28,7 @@ enum class ConvertTaskType { struct ConvertTask { ConvertTaskType type; - fs::path file; + io::path file; /// @brief region coords int x, z; @@ -53,10 +51,10 @@ class WorldConverter : public Task { ConvertMode mode; void upgradeRegion( - const fs::path& file, int x, int z, RegionLayerIndex layer) const; - void convertPlayer(const fs::path& file) const; - void convertVoxels(const fs::path& file, int x, int z) const; - void convertInventories(const fs::path& file, int x, int z) const; + const io::path& file, int x, int z, RegionLayerIndex layer) const; + void convertPlayer(const io::path& file) const; + void convertVoxels(const io::path& file, int x, int z) const; + void convertInventories(const io::path& file, int x, int z) const; void convertBlocksData(int x, int z, const ContentReport& report) const; void addRegionsTasks( diff --git a/src/files/WorldFiles.cpp b/src/world/files/WorldFiles.cpp similarity index 77% rename from src/files/WorldFiles.cpp rename to src/world/files/WorldFiles.cpp index 3759c96b..3617ad7c 100644 --- a/src/files/WorldFiles.cpp +++ b/src/world/files/WorldFiles.cpp @@ -36,11 +36,11 @@ static debug::Logger logger("world-files"); -WorldFiles::WorldFiles(const fs::path& directory) +WorldFiles::WorldFiles(const io::path& directory) : directory(directory), regions(directory) { } -WorldFiles::WorldFiles(const fs::path& directory, const DebugSettings& settings) +WorldFiles::WorldFiles(const io::path& directory, const DebugSettings& settings) : WorldFiles(directory) { generatorTestMode = settings.generatorTestMode.get(); doWriteLights = settings.doWriteLights.get(); @@ -51,28 +51,28 @@ WorldFiles::WorldFiles(const fs::path& directory, const DebugSettings& settings) WorldFiles::~WorldFiles() = default; void WorldFiles::createDirectories() { - fs::create_directories(directory / fs::path("data")); - fs::create_directories(directory / fs::path("content")); + io::create_directories(directory / "data"); + io::create_directories(directory / "content"); } -fs::path WorldFiles::getPlayerFile() const { - return directory / fs::path("player.json"); +io::path WorldFiles::getPlayerFile() const { + return directory / "player.json"; } -fs::path WorldFiles::getResourcesFile() const { - return directory / fs::path("resources.json"); +io::path WorldFiles::getResourcesFile() const { + return directory / "resources.json"; } -fs::path WorldFiles::getWorldFile() const { - return directory / fs::path(WORLD_FILE); +io::path WorldFiles::getWorldFile() const { + return directory / WORLD_FILE; } -fs::path WorldFiles::getIndicesFile() const { - return directory / fs::path("indices.json"); +io::path WorldFiles::getIndicesFile() const { + return directory / "indices.json"; } -fs::path WorldFiles::getPacksFile() const { - return directory / fs::path("packs.list"); +io::path WorldFiles::getPacksFile() const { + return directory / "packs.list"; } void WorldFiles::write( @@ -80,7 +80,7 @@ void WorldFiles::write( ) { if (world) { writeWorldInfo(world->getInfo()); - if (!fs::exists(getPacksFile())) { + if (!io::exists(getPacksFile())) { writePacks(world->getPacks()); } } @@ -100,7 +100,7 @@ void WorldFiles::writePacks(const std::vector& packs) { for (const auto& pack : packs) { ss << pack.id << "\n"; } - files::write_string(packsFile, ss.str()); + io::write_string(packsFile, ss.str()); } template @@ -139,20 +139,20 @@ void WorldFiles::writeIndices(const ContentIndices* indices) { createContentIndicesCache(indices, root); createBlockFieldsIndices(indices, root); - files::write_json(getIndicesFile(), root); + io::write_json(getIndicesFile(), root); } void WorldFiles::writeWorldInfo(const WorldInfo& info) { - files::write_json(getWorldFile(), info.serialize()); + io::write_json(getWorldFile(), info.serialize()); } std::optional WorldFiles::readWorldInfo() { - fs::path file = getWorldFile(); - if (!fs::is_regular_file(file)) { + io::path file = getWorldFile(); + if (!io::is_regular_file(file)) { logger.warning() << "world.json does not exists"; return std::nullopt; } - auto root = files::read_json(file); + auto root = io::read_json(file); WorldInfo info {}; info.deserialize(root); return info; @@ -175,12 +175,12 @@ static void read_resources_data( } bool WorldFiles::readResourcesData(const Content& content) { - fs::path file = getResourcesFile(); - if (!fs::is_regular_file(file)) { + io::path file = getResourcesFile(); + if (!io::is_regular_file(file)) { logger.warning() << "resources.json does not exists"; return false; } - auto root = files::read_json(file); + auto root = io::read_json(file); for (const auto& [key, arr] : root.asObject()) { if (auto resType = ResourceType_from(key)) { read_resources_data(content, arr, *resType); @@ -192,17 +192,17 @@ bool WorldFiles::readResourcesData(const Content& content) { } void WorldFiles::patchIndicesFile(const dv::value& map) { - fs::path file = getIndicesFile(); - if (!fs::is_regular_file(file)) { - logger.error() << file.filename().u8string() << " does not exists"; + io::path file = getIndicesFile(); + if (!io::is_regular_file(file)) { + logger.error() << file.name() << " does not exists"; return; } - auto root = files::read_json(file); + auto root = io::read_json(file); for (const auto& [key, value] : map.asObject()) { logger.info() << "patching indices.json: update " << util::quote(key); root[key] = value; } - files::write_json(file, root, true); + io::write_json(file, root, true); } static void erase_pack_indices(dv::value& root, const std::string& id) { @@ -223,13 +223,13 @@ static void erase_pack_indices(dv::value& root, const std::string& id) { } void WorldFiles::removeIndices(const std::vector& packs) { - auto root = files::read_json(getIndicesFile()); + auto root = io::read_json(getIndicesFile()); for (const auto& id : packs) { erase_pack_indices(root, id); } - files::write_json(getIndicesFile(), root); + io::write_json(getIndicesFile(), root); } -fs::path WorldFiles::getFolder() const { +io::path WorldFiles::getFolder() const { return directory; } diff --git a/src/files/WorldFiles.hpp b/src/world/files/WorldFiles.hpp similarity index 79% rename from src/files/WorldFiles.hpp rename to src/world/files/WorldFiles.hpp index 2d8ff0f0..f14c3c33 100644 --- a/src/files/WorldFiles.hpp +++ b/src/world/files/WorldFiles.hpp @@ -1,6 +1,5 @@ #pragma once -#include #include #include #include @@ -11,7 +10,7 @@ #include "typedefs.hpp" #include "voxels/Chunk.hpp" #include "WorldRegions.hpp" -#include "files.hpp" +#include "io/io.hpp" #define GLM_ENABLE_EXPERIMENTAL #include @@ -24,28 +23,26 @@ class World; struct WorldInfo; struct DebugSettings; -namespace fs = std::filesystem; - class WorldFiles { - fs::path directory; + io::path directory; WorldRegions regions; bool generatorTestMode = false; bool doWriteLights = true; - fs::path getWorldFile() const; - fs::path getPacksFile() const; + io::path getWorldFile() const; + io::path getPacksFile() const; void writeWorldInfo(const WorldInfo& info); void writeIndices(const ContentIndices* indices); public: - WorldFiles(const fs::path& directory); - WorldFiles(const fs::path& directory, const DebugSettings& settings); + WorldFiles(const io::path& directory); + WorldFiles(const io::path& directory, const DebugSettings& settings); ~WorldFiles(); - fs::path getPlayerFile() const; - fs::path getIndicesFile() const; - fs::path getResourcesFile() const; + io::path getPlayerFile() const; + io::path getIndicesFile() const; + io::path getResourcesFile() const; void createDirectories(); std::optional readWorldInfo(); @@ -70,7 +67,7 @@ public: void removeIndices(const std::vector& packs); /// @return world folder - fs::path getFolder() const; + io::path getFolder() const; WorldRegions& getRegions() { return regions; diff --git a/src/files/WorldRegions.cpp b/src/world/files/WorldRegions.cpp similarity index 95% rename from src/files/WorldRegions.cpp rename to src/world/files/WorldRegions.cpp index 54a25ee9..7c1ab08c 100644 --- a/src/files/WorldRegions.cpp +++ b/src/world/files/WorldRegions.cpp @@ -57,24 +57,24 @@ glm::u32vec2 WorldRegion::getChunkDataSize(uint x, uint z) { return sizes[z * REGION_SIZE + x]; } -WorldRegions::WorldRegions(const fs::path& directory) : directory(directory) { +WorldRegions::WorldRegions(const io::path& directory) : directory(directory) { for (size_t i = 0; i < REGION_LAYERS_COUNT; i++) { layers[i].layer = static_cast(i); } auto& voxels = layers[REGION_LAYER_VOXELS]; - voxels.folder = directory / fs::path("regions"); + voxels.folder = directory / "regions"; voxels.compression = compression::Method::EXTRLE16; auto& lights = layers[REGION_LAYER_LIGHTS]; - lights.folder = directory / fs::path("lights"); + lights.folder = directory / "lights"; lights.compression = compression::Method::EXTRLE8; layers[REGION_LAYER_INVENTORIES].folder = - directory / fs::path("inventories"); - layers[REGION_LAYER_ENTITIES].folder = directory / fs::path("entities"); + directory / "inventories"; + layers[REGION_LAYER_ENTITIES].folder = directory / "entities"; auto& blocksData = layers[REGION_LAYER_BLOCKS_DATA]; - blocksData.folder = directory / fs::path("blocksdata"); + blocksData.folder = directory / "blocksdata"; } WorldRegions::~WorldRegions() = default; @@ -388,17 +388,17 @@ void WorldRegions::processRegion( } } -const fs::path& WorldRegions::getRegionsFolder(RegionLayerIndex layerid) const { +const io::path& WorldRegions::getRegionsFolder(RegionLayerIndex layerid) const { return layers[layerid].folder; } -fs::path WorldRegions::getRegionFilePath(RegionLayerIndex layerid, int x, int z) const { +io::path WorldRegions::getRegionFilePath(RegionLayerIndex layerid, int x, int z) const { return layers[layerid].getRegionFilePath(x, z); } void WorldRegions::writeAll() { for (auto& layer : layers) { - fs::create_directories(layer.folder); + io::create_directories(layer.folder); layer.writeAll(); } } @@ -409,9 +409,9 @@ void WorldRegions::deleteRegion(RegionLayerIndex layerid, int x, int z) { throw std::runtime_error("region file is currently in use"); } auto file = layer.getRegionFilePath(x, z); - if (fs::exists(file)) { - logger.info() << "remove region file " << file.u8string(); - fs::remove(file); + if (io::exists(file)) { + logger.info() << "remove region file " << file.string(); + io::remove(file); } } diff --git a/src/files/WorldRegions.hpp b/src/world/files/WorldRegions.hpp similarity index 94% rename from src/files/WorldRegions.hpp rename to src/world/files/WorldRegions.hpp index ad0bc584..a50fc819 100644 --- a/src/files/WorldRegions.hpp +++ b/src/world/files/WorldRegions.hpp @@ -1,7 +1,6 @@ #pragma once #include -#include #include #include #include @@ -13,14 +12,12 @@ #include "voxels/Chunk.hpp" #include "maths/voxmaths.hpp" #include "coders/compression.hpp" -#include "files.hpp" +#include "io/io.hpp" #include "world_regions_fwd.hpp" #define GLM_ENABLE_EXPERIMENTAL #include -namespace fs = std::filesystem; - inline constexpr uint REGION_HEADER_SIZE = 10; inline constexpr uint REGION_SIZE_BIT = 5; @@ -54,11 +51,11 @@ public: }; struct regfile { - files::rafile file; + io::rafile file; int version; bool inUse = false; - regfile(fs::path filename); + regfile(io::path filename); regfile(const regfile&) = delete; std::unique_ptr read(int index, uint32_t& size, uint32_t& srcSize); @@ -121,7 +118,7 @@ struct RegionsLayer { RegionLayerIndex layer; /// @brief Regions layer folder - fs::path folder; + io::path folder; compression::Method compression = compression::Method::NONE; @@ -146,7 +143,7 @@ struct RegionsLayer { WorldRegion* getRegion(int x, int z); WorldRegion* getOrCreateRegion(int x, int z); - fs::path getRegionFilePath(int x, int z) const; + io::path getRegionFilePath(int x, int z) const; /// @brief Get chunk data. Read from file if not loaded yet. /// @param x chunk x coord @@ -178,14 +175,14 @@ struct RegionsLayer { class WorldRegions { /// @brief World directory - fs::path directory; + io::path directory; RegionsLayer layers[REGION_LAYERS_COUNT] {}; public: bool generatorTestMode = false; bool doWriteLights = true; - WorldRegions(const fs::path& directory); + WorldRegions(const io::path& directory); WorldRegions(const WorldRegions&) = delete; ~WorldRegions(); @@ -241,9 +238,9 @@ public: /// @brief Get regions directory by layer index /// @param layerid layer index /// @return directory path - const fs::path& getRegionsFolder(RegionLayerIndex layerid) const; + const io::path& getRegionsFolder(RegionLayerIndex layerid) const; - fs::path getRegionFilePath(RegionLayerIndex layerid, int x, int z) const; + io::path getRegionFilePath(RegionLayerIndex layerid, int x, int z) const; /// @brief Write all region layers void writeAll(); diff --git a/src/files/compatibility.cpp b/src/world/files/compatibility.cpp similarity index 100% rename from src/files/compatibility.cpp rename to src/world/files/compatibility.cpp diff --git a/src/files/compatibility.hpp b/src/world/files/compatibility.hpp similarity index 89% rename from src/files/compatibility.hpp rename to src/world/files/compatibility.hpp index cf78b5bc..736c68a9 100644 --- a/src/files/compatibility.hpp +++ b/src/world/files/compatibility.hpp @@ -2,7 +2,7 @@ #include "typedefs.hpp" #include "util/Buffer.hpp" -#include "files/world_regions_fwd.hpp" +#include "world/files/world_regions_fwd.hpp" namespace compatibility { /// @brief Convert region file from version 2 to 3 diff --git a/src/files/world_regions_fwd.hpp b/src/world/files/world_regions_fwd.hpp similarity index 100% rename from src/files/world_regions_fwd.hpp rename to src/world/files/world_regions_fwd.hpp diff --git a/test/coders/lua_parsing.cpp b/test/coders/lua_parsing.cpp index 9788d1d7..17492d7a 100644 --- a/test/coders/lua_parsing.cpp +++ b/test/coders/lua_parsing.cpp @@ -2,12 +2,16 @@ #include "coders/commons.hpp" #include "coders/lua_parsing.hpp" -#include "files/files.hpp" +#include "io/io.hpp" +#include "io/devices/StdfsDevice.hpp" #include "util/stringutil.hpp" +namespace fs = std::filesystem; + TEST(lua_parsing, Tokenizer) { - auto filename = "../../res/scripts/stdlib.lua"; - auto source = files::read_string(std::filesystem::u8path(filename)); + io::set_device("res", std::make_shared(fs::u8path("../../res"))); + auto filename = "res:scripts/stdlib.lua"; + auto source = io::read_string(filename); try { auto tokens = lua::tokenize(filename, source); for (const auto& token : tokens) { diff --git a/test/coders/vec3.cpp b/test/coders/vec3.cpp index d404e92b..a1dd330b 100644 --- a/test/coders/vec3.cpp +++ b/test/coders/vec3.cpp @@ -1,12 +1,14 @@ #include #include "coders/vec3.hpp" -#include "files/files.hpp" +#include "io/io.hpp" +#include "io/devices/StdfsDevice.hpp" + +namespace fs = std::filesystem; TEST(VEC3, Decode) { - auto file = std::filesystem::u8path( - "res/models/block.vec3" - ); - auto bytes = files::read_bytes_buffer(file); - auto model = vec3::load(file.u8string(), bytes); + io::set_device("res", std::make_shared(fs::u8path("../../res"))); + io::path file = "res:models/block.vec3"; + auto bytes = io::read_bytes_buffer(file); + auto model = vec3::load(file.string(), bytes); } diff --git a/test/io/path.cpp b/test/io/path.cpp new file mode 100644 index 00000000..86d5efe3 --- /dev/null +++ b/test/io/path.cpp @@ -0,0 +1,14 @@ +#include + +#include "io/path.hpp" + +TEST(Path, Path) { + io::path p("entry_point:path/file.ext"); + EXPECT_EQ(p, "entry_point:path/file.ext"); + EXPECT_EQ(p.pathPart(), "path/file.ext"); + EXPECT_EQ(p.name(), "file.ext"); + EXPECT_EQ(p.extension(), ".ext"); + EXPECT_EQ(p.entryPoint(), "entry_point"); + EXPECT_EQ(p / "child", "entry_point:path/file.ext/child"); + EXPECT_EQ(p.parent(), "entry_point:path"); +}