diff --git a/src/files/WorldRegions.cpp b/src/files/WorldRegions.cpp index d42aa072..35d15ac7 100644 --- a/src/files/WorldRegions.cpp +++ b/src/files/WorldRegions.cpp @@ -68,6 +68,9 @@ WorldRegions::WorldRegions(const fs::path& directory) : directory(directory) { layers[REGION_LAYER_INVENTORIES].folder = directory / fs::path("inventories"); layers[REGION_LAYER_ENTITIES].folder = directory / fs::path("entities"); + + auto& blocksData = layers[REGION_LAYER_BLOCKS_DATA]; + blocksData.folder = directory / fs::path("blocksdata"); } WorldRegions::~WorldRegions() = default; @@ -188,6 +191,15 @@ void WorldRegions::put(Chunk* chunk, std::vector entitiesData) { std::move(data), entitiesData.size()); } + // Writing blocks data + if (chunk->flags.blocksData) { + auto bytes = chunk->blocksMetadata.serialize(); + put(chunk->x, + chunk->z, + REGION_LAYER_BLOCKS_DATA, + bytes.release(), + bytes.size()); + } } std::unique_ptr WorldRegions::getVoxels(int x, int z) { @@ -227,6 +239,18 @@ chunk_inventories_map WorldRegions::fetchInventories(int x, int z) { return load_inventories(bytes, bytesSize); } +BlocksMetadata WorldRegions::getBlocksData(int x, int z) { + uint32_t bytesSize; + uint32_t srcSize; + auto bytes = layers[REGION_LAYER_BLOCKS_DATA].getData(x, z, bytesSize, srcSize); + if (bytes == nullptr) { + return {}; + } + BlocksMetadata heap; + heap.deserialize(bytes, bytesSize); + return heap; +} + void WorldRegions::processInventories( int x, int z, const inventoryproc& func ) { diff --git a/src/files/WorldRegions.hpp b/src/files/WorldRegions.hpp index ff548b6f..4fe37ab7 100644 --- a/src/files/WorldRegions.hpp +++ b/src/files/WorldRegions.hpp @@ -216,6 +216,8 @@ public: std::unique_ptr getLights(int x, int z); chunk_inventories_map fetchInventories(int x, int z); + + BlocksMetadata getBlocksData(int x, int z); /// @brief Load saved entities data for chunk /// @param x chunk.x diff --git a/src/files/compatibility.cpp b/src/files/compatibility.cpp index 8cdd437d..0ffc97fe 100644 --- a/src/files/compatibility.cpp +++ b/src/files/compatibility.cpp @@ -12,7 +12,6 @@ static inline size_t VOXELS_DATA_SIZE_V1 = CHUNK_VOL * 4; static inline size_t VOXELS_DATA_SIZE_V2 = CHUNK_VOL * 4; -#include static util::Buffer convert_voxels_1to2(const ubyte* buffer, uint32_t size) { auto data = compression::decompress( buffer, size, VOXELS_DATA_SIZE_V1, compression::Method::EXTRLE8); @@ -40,11 +39,9 @@ static util::Buffer convert_voxels_1to2(const ubyte* buffer, uint32_t siz return util::Buffer(std::move(compressed), outLen); } -#include "util/timeutil.hpp" util::Buffer compatibility::convert_region_2to3( const util::Buffer& src, RegionLayerIndex layer ) { - timeutil::ScopeLogTimer log(555); const size_t REGION_CHUNKS = 1024; const size_t HEADER_SIZE = 10; const size_t OFFSET_TABLE_SIZE = REGION_CHUNKS * sizeof(uint32_t); @@ -95,7 +92,8 @@ util::Buffer compatibility::convert_region_2to3( builder.put(data, size); break; case REGION_LAYER_ENTITIES: - case REGION_LAYER_INVENTORIES: { + case REGION_LAYER_INVENTORIES: + case REGION_LAYER_BLOCKS_DATA: { builder.putInt32(size); builder.putInt32(size); builder.put(data, size); diff --git a/src/files/world_regions_fwd.hpp b/src/files/world_regions_fwd.hpp index e0398641..9938239d 100644 --- a/src/files/world_regions_fwd.hpp +++ b/src/files/world_regions_fwd.hpp @@ -7,6 +7,7 @@ enum RegionLayerIndex : uint { REGION_LAYER_LIGHTS, REGION_LAYER_INVENTORIES, REGION_LAYER_ENTITIES, + REGION_LAYER_BLOCKS_DATA, REGION_LAYERS_COUNT }; diff --git a/src/logic/scripting/lua/libblock.cpp b/src/logic/scripting/lua/libblock.cpp index ea31e446..f186fdae 100644 --- a/src/logic/scripting/lua/libblock.cpp +++ b/src/logic/scripting/lua/libblock.cpp @@ -557,6 +557,8 @@ static int l_set_field(lua::State* L) { if (dst == nullptr) { dst = chunk->blocksMetadata.allocate(voxelIndex, dataStruct.size()); } + chunk->flags.unsaved = true; + chunk->flags.blocksData = true; return set_field(L, dst, *field, index, dataStruct, value); } diff --git a/src/util/Buffer.hpp b/src/util/Buffer.hpp index 01769f0d..fbe1ade7 100644 --- a/src/util/Buffer.hpp +++ b/src/util/Buffer.hpp @@ -4,6 +4,8 @@ #include namespace util { + /// @brief Template similar to std::unique_ptr stores a buffer with its size + /// @tparam T buffer elements type template class Buffer { std::unique_ptr ptr; @@ -12,7 +14,9 @@ namespace util { Buffer(size_t length) : ptr(std::make_unique(length)), length(length) { } - Buffer(const Buffer& o) : Buffer(o.data(), o.size()) {} + explicit Buffer(const Buffer& o) : Buffer(o.data(), o.size()) {} + + Buffer(Buffer&& o) : ptr(std::move(o.ptr)), length(o.length) {} Buffer(std::unique_ptr ptr, size_t length) : ptr(std::move(ptr)), length(length) {} @@ -42,14 +46,18 @@ namespace util { return length; } + /// @brief Take ownership over the buffer unique_ptr std::unique_ptr release() { return std::move(ptr); } + /// @brief Create a buffer copy Buffer clone() const { return Buffer(ptr.get(), length); } + /// @brief Update buffer size without releasing used memory + /// @param size new size (must be less or equal to current) void resizeFast(size_t size) { length = size; } diff --git a/src/util/SmallHeap.hpp b/src/util/SmallHeap.hpp index 0279f465..189d54c2 100644 --- a/src/util/SmallHeap.hpp +++ b/src/util/SmallHeap.hpp @@ -6,7 +6,15 @@ #include #include -namespace util { +#include "Buffer.hpp" +#include "data_io.hpp" + +namespace util { + template + inline T read_int_le(const uint8_t* src, size_t offset=0) { + return dataio::le2h(*(reinterpret_cast(src) + offset)); + } + // TODO: make it safer (minimize raw temporary pointers use) /// @brief Simple heap implementation for memory-optimal sparse array of /// small different structures @@ -28,9 +36,9 @@ namespace util { uint8_t* find(Tindex index) { auto data = buffer.data(); for (size_t i = 0; i < entriesCount; i++) { - auto nextIndex = *reinterpret_cast(data); + auto nextIndex = read_int_le(data); data += sizeof(Tindex); - auto nextSize = *reinterpret_cast(data); + auto nextSize = read_int_le(data); data += sizeof(Tsize); if (nextIndex == index) { return data; @@ -85,9 +93,9 @@ namespace util { } for (size_t i = 0; i < entriesCount; i++) { auto data = buffer.data() + offset; - auto nextIndex = *reinterpret_cast(data); + auto nextIndex = read_int_le(data); data += sizeof(Tindex); - auto nextSize = *reinterpret_cast(data); + auto nextSize = read_int_le(data); data += sizeof(Tsize); if (nextIndex > index) { break; @@ -103,9 +111,9 @@ namespace util { entriesCount++; auto data = buffer.data() + offset; - *reinterpret_cast(data) = index; + *reinterpret_cast(data) = dataio::h2le(index); data += sizeof(Tindex); - *reinterpret_cast(data) = size; + *reinterpret_cast(data) = dataio::h2le(size); return data + sizeof(Tsize); } @@ -115,7 +123,7 @@ namespace util { if (ptr == nullptr) { return 0; } - return *(reinterpret_cast(ptr)-1); + return read_int_le(ptr, -1); } /// @return number of entries @@ -127,5 +135,30 @@ namespace util { size_t size() const { return buffer.size(); } + + inline bool operator==(const SmallHeap& o) const { + if (o.entriesCount != entriesCount) { + return false; + } + return buffer == o.buffer; + } + + util::Buffer serialize() const { + util::Buffer out(sizeof(Tindex) + buffer.size()); + ubyte* dst = out.data(); + const ubyte* src = buffer.data(); + + *reinterpret_cast(dst) = dataio::h2le(entriesCount); + dst += sizeof(Tindex); + + std::memcpy(dst, src, buffer.size()); + return out; + } + + void deserialize(const ubyte* src, size_t size) { + entriesCount = read_int_le(src); + buffer.resize(size - sizeof(Tindex)); + std::memcpy(buffer.data(), src + sizeof(Tindex), buffer.size()); + } }; } diff --git a/src/voxels/Chunk.hpp b/src/voxels/Chunk.hpp index 184fb2d9..d7a6979a 100644 --- a/src/voxels/Chunk.hpp +++ b/src/voxels/Chunk.hpp @@ -18,6 +18,8 @@ class Inventory; using chunk_inventories_map = std::unordered_map>; +using BlocksMetadata = util::SmallHeap; + class Chunk { public: int x, z; @@ -32,12 +34,13 @@ public: bool unsaved : 1; bool loadedLights : 1; bool entities : 1; + bool blocksData : 1; } flags {}; /// @brief Block inventories map where key is index of block in voxels array chunk_inventories_map inventories; /// @brief Blocks metadata heap - util::SmallHeap blocksMetadata; + BlocksMetadata blocksMetadata; Chunk(int x, int z); diff --git a/src/voxels/Chunks.cpp b/src/voxels/Chunks.cpp index 408ab828..7196670f 100644 --- a/src/voxels/Chunks.cpp +++ b/src/voxels/Chunks.cpp @@ -383,7 +383,11 @@ void Chunks::set( eraseSegments(prevdef, vox.state, gx, y, gz); } if (prevdef.dataStruct) { - chunk->blocksMetadata.free(chunk->blocksMetadata.find(index)); + if (auto found = chunk->blocksMetadata.find(index)) { + chunk->blocksMetadata.free(found); + chunk->flags.unsaved = true; + chunk->flags.blocksData = true; + } } // block initialization diff --git a/src/voxels/ChunksStorage.cpp b/src/voxels/ChunksStorage.cpp index af8127f4..36a98ea7 100644 --- a/src/voxels/ChunksStorage.cpp +++ b/src/voxels/ChunksStorage.cpp @@ -81,6 +81,7 @@ std::shared_ptr ChunksStorage::create(int x, int z) { chunk->lightmap.set(lights.get()); chunk->flags.loadedLights = true; } + chunk->blocksMetadata = regions.getBlocksData(chunk->x, chunk->z); return chunk; } diff --git a/test/util/SmallHeap.cpp b/test/util/SmallHeap.cpp index d8b08a4c..63af3592 100644 --- a/test/util/SmallHeap.cpp +++ b/test/util/SmallHeap.cpp @@ -53,3 +53,19 @@ TEST(SmallHeap, RandomFill) { } EXPECT_EQ(map.sizeOf(map.find(n)), 123); } + +TEST(SmallHeap, EncodeDecode) { + SmallHeap map; + int n = 3'000; + map.allocate(n, 123); + for (int i = 0; i < n; i++) { + int index = rand() % n; + int size = rand() % 254 + 1; + map.allocate(index, size); + } + auto bytes = map.serialize(); + + SmallHeap out; + out.deserialize(bytes.data(), bytes.size()); + EXPECT_EQ(map, out); +}