#pragma once #include #include #include #include #include #include #include #include "typedefs.hpp" #include "util/BufferPool.hpp" #include "voxels/Chunk.hpp" #include "maths/voxmaths.hpp" #include "coders/compression.hpp" #include "files.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; inline constexpr uint REGION_SIZE = (1 << (REGION_SIZE_BIT)); inline constexpr uint REGION_CHUNKS_COUNT = ((REGION_SIZE) * (REGION_SIZE)); class illegal_region_format : public std::runtime_error { public: illegal_region_format(const std::string& message) : std::runtime_error(message) { } }; class WorldRegion { std::unique_ptr[]> chunksData; std::unique_ptr sizes; bool unsaved = false; public: WorldRegion(); ~WorldRegion(); void put(uint x, uint z, std::unique_ptr data, uint32_t size, uint32_t srcSize); ubyte* getChunkData(uint x, uint z); glm::u32vec2 getChunkDataSize(uint x, uint z); void setUnsaved(bool unsaved); bool isUnsaved() const; std::unique_ptr* getChunks() const; glm::u32vec2* getSizes() const; }; struct regfile { files::rafile file; int version; bool inUse = false; regfile(fs::path filename); regfile(const regfile&) = delete; std::unique_ptr read(int index, uint32_t& size, uint32_t& srcSize); }; using RegionsMap = std::unordered_map>; using RegionProc = std::function(std::unique_ptr,uint32_t*)>; using InventoryProc = std::function; using BlockDataProc = std::function)>; /// @brief Region file pointer keeping inUse flag on until destroyed class regfile_ptr { regfile* file; std::condition_variable* cv; public: regfile_ptr(regfile* file, std::condition_variable* cv) : file(file), cv(cv) { } regfile_ptr(const regfile_ptr&) = delete; regfile_ptr(std::nullptr_t) : file(nullptr), cv(nullptr) { } bool operator==(std::nullptr_t) const { return file == nullptr; } bool operator!=(std::nullptr_t) const { return file != nullptr; } operator bool() const { return file != nullptr; } ~regfile_ptr() { reset(); } regfile* get() { return file; } void reset() { if (file) { file->inUse = false; cv->notify_one(); file = nullptr; } } }; inline void calc_reg_coords( int x, int z, int& regionX, int& regionZ, int& localX, int& localZ ) { regionX = floordiv(x, REGION_SIZE); regionZ = floordiv(z, REGION_SIZE); localX = x - (regionX * REGION_SIZE); localZ = z - (regionZ * REGION_SIZE); } struct RegionsLayer { /// @brief Layer index RegionLayerIndex layer; /// @brief Regions layer folder fs::path folder; compression::Method compression = compression::Method::NONE; /// @brief In-memory regions data RegionsMap regions; /// @brief In-memory regions map mutex std::mutex mapMutex; /// @brief Open region files map std::unordered_map> openRegFiles; /// @brief Open region files map mutex std::mutex regFilesMutex; std::condition_variable regFilesCv; [[nodiscard]] regfile_ptr getRegFile(glm::ivec2 coord, bool create = true); [[nodiscard]] regfile_ptr useRegFile(glm::ivec2 coord); regfile_ptr createRegFile(glm::ivec2 coord); void closeRegFile(glm::ivec2 coord); WorldRegion* getRegion(int x, int z); WorldRegion* getOrCreateRegion(int x, int z); fs::path getRegionFilePath(int x, int z) const; /// @brief Get chunk data. Read from file if not loaded yet. /// @param x chunk x coord /// @param z chunk z coord /// @param size [out] compressed chunk data length /// @param size [out] source chunk data length /// @return nullptr if no saved chunk data found [[nodiscard]] ubyte* getData(int x, int z, uint32_t& size, uint32_t& srcSize); /// @brief Write or rewrite region file /// @param x region X /// @param z region Z void writeRegion(int x, int y, WorldRegion* entry); /// @brief Write all unsaved regions to files void writeAll(); /// @brief Read chunk data from region file /// @param x chunk x coord /// @param z chunk z coord /// @param size [out] compressed chunk data length /// @param srcSize [out] source chunk data length /// @param rfile region file /// @return nullptr if chunk is not present in region file [[nodiscard]] static std::unique_ptr readChunkData( int x, int z, uint32_t& size, uint32_t& srcSize, regfile* rfile ); }; class WorldRegions { /// @brief World directory fs::path directory; RegionsLayer layers[REGION_LAYERS_COUNT] {}; public: bool generatorTestMode = false; bool doWriteLights = true; WorldRegions(const fs::path& directory); WorldRegions(const WorldRegions&) = delete; ~WorldRegions(); /// @brief Put all chunk data to regions void put(Chunk* chunk, std::vector entitiesData); /// @brief Store data in specified region /// @param x chunk.x /// @param z chunk.z /// @param layer regions layer /// @param data target data /// @param size data size void put( int x, int z, RegionLayerIndex layer, std::unique_ptr data, size_t size ); /// @brief Get chunk voxels data /// @param x chunk.x /// @param z chunk.z /// @return voxels data buffer or nullptr std::unique_ptr getVoxels(int x, int z); /// @brief Get cached lights for chunk at x,z /// @return lights data or nullptr std::unique_ptr getLights(int x, int z); ChunkInventoriesMap fetchInventories(int x, int z); BlocksMetadata getBlocksData(int x, int z); /// @brief Load saved entities data for chunk /// @param x chunk.x /// @param z chunk.z /// @return map with entities list as "data" dv::value fetchEntities(int x, int z); /// @brief Load, process and save processed region chunks data /// @param x region X /// @param z region Z /// @param layerid regions layer index /// @param func processing callback void processRegion( int x, int z, RegionLayerIndex layerid, const RegionProc& func); void processInventories(int x, int z, const InventoryProc& func); void processBlocksData(int x, int z, const BlockDataProc& func); /// @brief Get regions directory by layer index /// @param layerid layer index /// @return directory path const fs::path& getRegionsFolder(RegionLayerIndex layerid) const; fs::path getRegionFilePath(RegionLayerIndex layerid, int x, int z) const; /// @brief Write all region layers void writeAll(); void deleteRegion(RegionLayerIndex layerid, int x, int z); /// @brief Extract X and Z from 'X_Z.bin' region file name. /// @param name source region file name /// @param x parsed X destination /// @param z parsed Z destination /// @return false if std::invalid_argument or std::out_of_range occurred static bool parseRegionFilename(const std::string& name, int& x, int& y); };