add /coders/compression module

This commit is contained in:
MihailRis
2024-09-01 21:58:33 +03:00
parent 0f53d5b835
commit 7d193941a4
7 changed files with 170 additions and 86 deletions
+87
View File
@@ -0,0 +1,87 @@
#include "compression.hpp"
#include <cstring>
#include <stdexcept>
#include "rle.hpp"
#include "gzip.hpp"
#include "util/BufferPool.hpp"
using namespace compression;
static util::BufferPool<ubyte> buffer_pools[] {
{255},
{UINT16_MAX},
{UINT16_MAX * 8},
};
static std::shared_ptr<ubyte[]> get_buffer(size_t minSize) {
for (auto& pool : buffer_pools) {
if (minSize <= pool.getBufferSize()) {
return pool.get();
}
}
return nullptr;
}
std::unique_ptr<ubyte[]> compression::compress(
const ubyte* src, size_t srclen, size_t& len, Method method
) {
switch (method) {
case Method::NONE:
throw std::invalid_argument("compression method is NONE");
case Method::EXTRLE8: {
// max extrle out size is srcLen * 2
auto buffer = get_buffer(srclen * 2);
auto bytes = buffer.get();
std::unique_ptr<ubyte[]> uptr;
if (bytes == nullptr) {
uptr = std::make_unique<ubyte[]>(srclen * 2);
bytes = uptr.get();
}
len = extrle::encode(src, srclen, bytes);
if (uptr) {
return uptr;
}
auto data = std::make_unique<ubyte[]>(len);
std::memcpy(data.get(), bytes, len);
return data;
}
case Method::GZIP: {
auto buffer = gzip::compress(src, srclen);
auto data = std::make_unique<ubyte[]>(buffer.size());
std::memcpy(data.get(), buffer.data(), buffer.size());
len = buffer.size();
return data;
}
default:
throw std::runtime_error("not implemented");
}
}
std::unique_ptr<ubyte[]> compression::decompress(
const ubyte* src, size_t srclen, size_t dstlen, Method method
) {
switch (method) {
case Method::NONE:
throw std::invalid_argument("compression method is NONE");
case Method::EXTRLE8: {
auto decompressed = std::make_unique<ubyte[]>(dstlen);
extrle::decode(src, srclen, decompressed.get());
return decompressed;
}
case Method::GZIP: {
auto buffer = gzip::decompress(src, srclen);
if (buffer.size() != dstlen) {
throw std::runtime_error(
"expected decompressed size " + std::to_string(dstlen) +
" got " + std::to_string(buffer.size()));
}
auto decompressed = std::make_unique<ubyte[]>(buffer.size());
std::memcpy(decompressed.get(), buffer.data(), buffer.size());
return decompressed;
}
default:
throw std::runtime_error("not implemented");
}
}
+30
View File
@@ -0,0 +1,30 @@
#pragma once
#include <memory>
#include "typedefs.hpp"
namespace compression {
enum class Method {
NONE, EXTRLE8, GZIP
};
/// @brief Compress buffer
/// @param src source buffer
/// @param srclen length of the source buffer
/// @param len (out argument) length of result buffer
/// @param method compression method
/// @return compressed bytes array
/// @throws std::invalid_argument if compression method is NONE
std::unique_ptr<ubyte[]> compress(
const ubyte* src, size_t srclen, size_t& len, Method method
);
/// @brief Decompress buffer
/// @param src compressed buffer
/// @param srclen length of compressed buffer
/// @param dstlen max expected length of source buffer
/// @return decompressed bytes array
std::unique_ptr<ubyte[]> decompress(
const ubyte* src, size_t srclen, size_t dstlen, Method method);
}
+2 -2
View File
@@ -11,7 +11,7 @@ static fs::path get_region_filename(int x, int z) {
} }
/// @brief Read missing chunks data (null pointers) from region file /// @brief Read missing chunks data (null pointers) from region file
static void fetchChunks(WorldRegion* region, int x, int z, regfile* file) { static void fetch_chunks(WorldRegion* region, int x, int z, regfile* file) {
auto* chunks = region->getChunks(); auto* chunks = region->getChunks();
uint32_t* sizes = region->getSizes(); uint32_t* sizes = region->getSizes();
@@ -174,7 +174,7 @@ void RegionsLayer::writeRegion(int x, int z, WorldRegion* entry) {
glm::ivec2 regcoord(x, z); glm::ivec2 regcoord(x, z);
if (auto regfile = getRegFile(regcoord, false)) { if (auto regfile = getRegFile(regcoord, false)) {
fetchChunks(entry, x, z, regfile.get()); fetch_chunks(entry, x, z, regfile.get());
std::lock_guard lock(regFilesMutex); std::lock_guard lock(regFilesMutex);
regfile.reset(); regfile.reset();
+34 -54
View File
@@ -5,7 +5,6 @@
#include <vector> #include <vector>
#include "coders/byte_utils.hpp" #include "coders/byte_utils.hpp"
#include "coders/rle.hpp"
#include "data/dynamic.hpp" #include "data/dynamic.hpp"
#include "items/Inventory.hpp" #include "items/Inventory.hpp"
#include "maths/voxmaths.hpp" #include "maths/voxmaths.hpp"
@@ -55,8 +54,14 @@ WorldRegions::WorldRegions(const fs::path& directory) : directory(directory) {
for (size_t i = 0; i < sizeof(layers) / sizeof(RegionsLayer); i++) { for (size_t i = 0; i < sizeof(layers) / sizeof(RegionsLayer); i++) {
layers[i].layer = i; layers[i].layer = i;
} }
layers[REGION_LAYER_VOXELS].folder = directory / fs::path("regions"); auto& voxels = layers[REGION_LAYER_VOXELS];
layers[REGION_LAYER_LIGHTS].folder = directory / fs::path("lights"); voxels.folder = directory / fs::path("regions");
voxels.compression = compression::Method::EXTRLE8;
auto& lights = layers[REGION_LAYER_LIGHTS];
lights.folder = directory / fs::path("lights");
lights.compression = compression::Method::EXTRLE8;
layers[REGION_LAYER_INVENTORIES].folder = layers[REGION_LAYER_INVENTORIES].folder =
directory / fs::path("inventories"); directory / fs::path("inventories");
layers[REGION_LAYER_ENTITIES].folder = directory / fs::path("entities"); layers[REGION_LAYER_ENTITIES].folder = directory / fs::path("entities");
@@ -64,28 +69,6 @@ WorldRegions::WorldRegions(const fs::path& directory) : directory(directory) {
WorldRegions::~WorldRegions() = default; WorldRegions::~WorldRegions() = default;
std::unique_ptr<ubyte[]> WorldRegions::compress(
const ubyte* src, size_t srclen, size_t& len
) {
auto buffer = bufferPool.get();
auto bytes = buffer.get();
len = extrle::encode(src, srclen, bytes);
auto data = std::make_unique<ubyte[]>(len);
for (size_t i = 0; i < len; i++) {
data[i] = bytes[i];
}
return data;
}
std::unique_ptr<ubyte[]> WorldRegions::decompress(
const ubyte* src, size_t srclen, size_t dstlen
) {
auto decompressed = std::make_unique<ubyte[]>(dstlen);
extrle::decode(src, srclen, decompressed.get());
return decompressed;
}
void RegionsLayer::writeAll() { void RegionsLayer::writeAll() {
for (auto& it : regions) { for (auto& it : regions) {
WorldRegion* region = it.second.get(); WorldRegion* region = it.second.get();
@@ -100,21 +83,19 @@ void RegionsLayer::writeAll() {
void WorldRegions::put( void WorldRegions::put(
int x, int x,
int z, int z,
int layer, int layerid,
std::unique_ptr<ubyte[]> data, std::unique_ptr<ubyte[]> data,
size_t size, size_t size
bool rle
) { ) {
if (rle) { auto& layer = layers[layerid];
size_t compressedSize; if (layer.compression != compression::Method::NONE) {
auto compressed = compress(data.get(), size, compressedSize); data = compression::compress(
put(x, z, layer, std::move(compressed), compressedSize, false); data.get(), size, size, layer.compression);
return;
} }
int regionX, regionZ, localX, localZ; int regionX, regionZ, localX, localZ;
calc_reg_coords(x, z, regionX, regionZ, localX, localZ); calc_reg_coords(x, z, regionX, regionZ, localX, localZ);
WorldRegion* region = layers[layer].getOrCreateRegion(regionX, regionZ); WorldRegion* region = layer.getOrCreateRegion(regionX, regionZ);
region->setUnsaved(true); region->setUnsaved(true);
region->put(localX, localZ, std::move(data), size); region->put(localX, localZ, std::move(data), size);
} }
@@ -139,7 +120,6 @@ static std::unique_ptr<ubyte[]> write_inventories(
return data; return data;
} }
/// @brief Store chunk data (voxels and lights) in region (existing or new)
void WorldRegions::put(Chunk* chunk, std::vector<ubyte> entitiesData) { void WorldRegions::put(Chunk* chunk, std::vector<ubyte> entitiesData) {
assert(chunk != nullptr); assert(chunk != nullptr);
if (!chunk->flags.lighted) { if (!chunk->flags.lighted) {
@@ -157,8 +137,7 @@ void WorldRegions::put(Chunk* chunk, std::vector<ubyte> entitiesData) {
chunk->z, chunk->z,
REGION_LAYER_VOXELS, REGION_LAYER_VOXELS,
chunk->encode(), chunk->encode(),
CHUNK_DATA_LEN, CHUNK_DATA_LEN);
true);
// Writing lights cache // Writing lights cache
if (doWriteLights && chunk->flags.lighted) { if (doWriteLights && chunk->flags.lighted) {
@@ -166,8 +145,7 @@ void WorldRegions::put(Chunk* chunk, std::vector<ubyte> entitiesData) {
chunk->z, chunk->z,
REGION_LAYER_LIGHTS, REGION_LAYER_LIGHTS,
chunk->lightmap.encode(), chunk->lightmap.encode(),
LIGHTMAP_DATA_LEN, LIGHTMAP_DATA_LEN);
true);
} }
// Writing block inventories // Writing block inventories
if (!chunk->inventories.empty()) { if (!chunk->inventories.empty()) {
@@ -177,8 +155,7 @@ void WorldRegions::put(Chunk* chunk, std::vector<ubyte> entitiesData) {
chunk->z, chunk->z,
REGION_LAYER_INVENTORIES, REGION_LAYER_INVENTORIES,
std::move(data), std::move(data),
datasize, datasize);
false);
} }
// Writing entities // Writing entities
if (!entitiesData.empty()) { if (!entitiesData.empty()) {
@@ -190,29 +167,30 @@ void WorldRegions::put(Chunk* chunk, std::vector<ubyte> entitiesData) {
chunk->z, chunk->z,
REGION_LAYER_ENTITIES, REGION_LAYER_ENTITIES,
std::move(data), std::move(data),
entitiesData.size(), entitiesData.size());
false);
} }
} }
std::unique_ptr<ubyte[]> WorldRegions::getVoxels(int x, int z) { std::unique_ptr<ubyte[]> WorldRegions::getVoxels(int x, int z) {
uint32_t size; uint32_t size;
auto* data = layers[REGION_LAYER_VOXELS].getData(x, z, size); auto& layer = layers[REGION_LAYER_VOXELS];
auto* data = layer.getData(x, z, size);
if (data == nullptr) { if (data == nullptr) {
return nullptr; return nullptr;
} }
return decompress(data, size, CHUNK_DATA_LEN); return compression::decompress(data, size, CHUNK_DATA_LEN, layer.compression);
} }
/// @brief Get cached lights for chunk at x,z
/// @return lights data or nullptr
std::unique_ptr<light_t[]> WorldRegions::getLights(int x, int z) { std::unique_ptr<light_t[]> WorldRegions::getLights(int x, int z) {
uint32_t size; uint32_t size;
auto* bytes = layers[REGION_LAYER_LIGHTS].getData(x, z, size); auto& layer = layers[REGION_LAYER_LIGHTS];
auto* bytes = layer.getData(x, z, size);
if (bytes == nullptr) { if (bytes == nullptr) {
return nullptr; return nullptr;
} }
auto data = decompress(bytes, size, LIGHTMAP_DATA_LEN); auto data = compression::decompress(
bytes, size, LIGHTMAP_DATA_LEN, layer.compression
);
return Lightmap::decode(data.get()); return Lightmap::decode(data.get());
} }
@@ -254,10 +232,11 @@ dynamic::Map_sptr WorldRegions::fetchEntities(int x, int z) {
} }
void WorldRegions::processRegionVoxels(int x, int z, const regionproc& func) { void WorldRegions::processRegionVoxels(int x, int z, const regionproc& func) {
if (layers[REGION_LAYER_VOXELS].getRegion(x, z)) { auto& layer = layers[REGION_LAYER_VOXELS];
if (layer.getRegion(x, z)) {
throw std::runtime_error("not implemented for in-memory regions"); throw std::runtime_error("not implemented for in-memory regions");
} }
auto regfile = layers[REGION_LAYER_VOXELS].getRegFile({x, z}); auto regfile = layer.getRegFile({x, z});
if (regfile == nullptr) { if (regfile == nullptr) {
throw std::runtime_error("could not open region file"); throw std::runtime_error("could not open region file");
} }
@@ -271,14 +250,15 @@ void WorldRegions::processRegionVoxels(int x, int z, const regionproc& func) {
if (data == nullptr) { if (data == nullptr) {
continue; continue;
} }
data = decompress(data.get(), length, CHUNK_DATA_LEN); data = compression::decompress(
data.get(), length, CHUNK_DATA_LEN, layer.compression
);
if (func(data.get())) { if (func(data.get())) {
put(gx, put(gx,
gz, gz,
REGION_LAYER_VOXELS, REGION_LAYER_VOXELS,
std::move(data), std::move(data),
CHUNK_DATA_LEN, CHUNK_DATA_LEN);
true);
} }
} }
} }
+11 -25
View File
@@ -8,11 +8,12 @@
#include <mutex> #include <mutex>
#include <unordered_map> #include <unordered_map>
#include "data/dynamic_fwd.hpp"
#include "typedefs.hpp" #include "typedefs.hpp"
#include "data/dynamic_fwd.hpp"
#include "util/BufferPool.hpp" #include "util/BufferPool.hpp"
#include "voxels/Chunk.hpp" #include "voxels/Chunk.hpp"
#include "maths/voxmaths.hpp" #include "maths/voxmaths.hpp"
#include "coders/compression.hpp"
#include "files.hpp" #include "files.hpp"
#define GLM_ENABLE_EXPERIMENTAL #define GLM_ENABLE_EXPERIMENTAL
@@ -126,6 +127,8 @@ struct RegionsLayer {
/// @brief Regions layer folder /// @brief Regions layer folder
fs::path folder; fs::path folder;
compression::Method compression = compression::Method::NONE;
/// @brief In-memory regions data /// @brief In-memory regions data
regionsmap regions; regionsmap regions;
@@ -139,7 +142,7 @@ struct RegionsLayer {
std::mutex regFilesMutex; std::mutex regFilesMutex;
std::condition_variable regFilesCv; std::condition_variable regFilesCv;
regfile_ptr getRegFile(glm::ivec2 coord, bool create = true); [[nodiscard]] regfile_ptr getRegFile(glm::ivec2 coord, bool create = true);
[[nodiscard]] regfile_ptr useRegFile(glm::ivec2 coord); [[nodiscard]] regfile_ptr useRegFile(glm::ivec2 coord);
regfile_ptr createRegFile(glm::ivec2 coord); regfile_ptr createRegFile(glm::ivec2 coord);
void closeRegFile(glm::ivec2 coord); void closeRegFile(glm::ivec2 coord);
@@ -178,26 +181,6 @@ class WorldRegions {
fs::path directory; fs::path directory;
RegionsLayer layers[4] {}; RegionsLayer layers[4] {};
util::BufferPool<ubyte> bufferPool {
std::max(CHUNK_DATA_LEN, LIGHTMAP_DATA_LEN) * 2};
/// @brief Compress buffer with extrle
/// @param src source buffer
/// @param srclen length of the source buffer
/// @param len (out argument) length of result buffer
/// @return compressed bytes array
std::unique_ptr<ubyte[]> compress(
const ubyte* src, size_t srclen, size_t& len
);
/// @brief Decompress buffer with extrle
/// @param src compressed buffer
/// @param srclen length of compressed buffer
/// @param dstlen max expected length of source buffer
/// @return decompressed bytes array
std::unique_ptr<ubyte[]> decompress(
const ubyte* src, size_t srclen, size_t dstlen
);
public: public:
bool generatorTestMode = false; bool generatorTestMode = false;
bool doWriteLights = true; bool doWriteLights = true;
@@ -215,19 +198,22 @@ public:
/// @param layer regions layer /// @param layer regions layer
/// @param data target data /// @param data target data
/// @param size data size /// @param size data size
/// @param rle compress with ext-RLE
void put( void put(
int x, int x,
int z, int z,
int layer, int layer,
std::unique_ptr<ubyte[]> data, std::unique_ptr<ubyte[]> data,
size_t size, size_t size
bool rle
); );
std::unique_ptr<ubyte[]> getVoxels(int x, int z); std::unique_ptr<ubyte[]> getVoxels(int x, int z);
/// @brief Get cached lights for chunk at x,z
/// @return lights data or nullptr
std::unique_ptr<light_t[]> getLights(int x, int z); std::unique_ptr<light_t[]> getLights(int x, int z);
chunk_inventories_map fetchInventories(int x, int z); chunk_inventories_map fetchInventories(int x, int z);
dynamic::Map_sptr fetchEntities(int x, int z); dynamic::Map_sptr fetchEntities(int x, int z);
void processRegionVoxels(int x, int z, const regionproc& func); void processRegionVoxels(int x, int z, const regionproc& func);
+4
View File
@@ -35,5 +35,9 @@ namespace util {
freeBuffers.push(ptr); freeBuffers.push(ptr);
}); });
} }
size_t getBufferSize() const {
return bufferSize;
}
}; };
} }
+2 -5
View File
@@ -59,8 +59,7 @@ std::shared_ptr<Chunk> ChunksStorage::create(int x, int z) {
auto chunk = std::make_shared<Chunk>(x, z); auto chunk = std::make_shared<Chunk>(x, z);
store(chunk); store(chunk);
auto data = regions.getVoxels(chunk->x, chunk->z); if (auto data = regions.getVoxels(chunk->x, chunk->z)) {
if (data) {
chunk->decode(data.get()); chunk->decode(data.get());
auto invs = regions.fetchInventories(chunk->x, chunk->z); auto invs = regions.fetchInventories(chunk->x, chunk->z);
@@ -77,9 +76,7 @@ std::shared_ptr<Chunk> ChunksStorage::create(int x, int z) {
} }
verifyLoadedChunk(level->content->getIndices(), chunk.get()); verifyLoadedChunk(level->content->getIndices(), chunk.get());
} }
if (auto lights = regions.getLights(chunk->x, chunk->z)) {
auto lights = regions.getLights(chunk->x, chunk->z);
if (lights) {
chunk->lightmap.set(lights.get()); chunk->lightmap.set(lights.get());
chunk->flags.loadedLights = true; chunk->flags.loadedLights = true;
} }