BlockChunks now use Run Index Encoding, 80% reduction in mem usage.

master
Nicole Collings 2020-02-14 13:04:07 -08:00
parent c209b2a372
commit ee94e691ae
8 changed files with 529 additions and 137 deletions

View File

@ -56,7 +56,6 @@ include_directories(
lib/header/json/include
)
add_subdirectory(src)
add_executable(${MAIN_EXEC_NAME} src/Main.cpp)
target_link_libraries(${MAIN_EXEC_NAME} Zepha_Core)
@ -76,6 +75,11 @@ target_compile_definitions(${MAIN_EXEC_NAME} PUBLIC SOL_ALL_SAFETIES_ON)
# Test Build
add_subdirectory(test)
add_executable(${TEST_EXEC_NAME} test/Main.cpp)
target_link_libraries(${TEST_EXEC_NAME} Zepha_Core)
target_link_libraries(${TEST_EXEC_NAME} Zepha_Test)
target_include_directories(${TEST_EXEC_NAME} PRIVATE ${GLFW_HEADERS})
target_compile_definitions(${TEST_EXEC_NAME} PUBLIC SOL_ALL_SAFETIES_ON)
target_link_libraries(${TEST_EXEC_NAME} ${LUA_LIB})
target_link_libraries (${TEST_EXEC_NAME} z)
target_link_libraries(${TEST_EXEC_NAME} ${ENET_LIB})

View File

@ -158,30 +158,54 @@ void MapGen::buildElevationMap(chunk_partials_map& chunks, chunk_partial& chunk)
void MapGen::generateBlocks(chunk_partial& chunk) {
glm::ivec3 lp {};
for (unsigned short i = 0; i < 256; i++) {
unsigned short x = i / 16;
unsigned short z = i % 16;
auto dupe = std::make_unique<BlockChunk>(*chunk.second);
lp = {x, 0, z};
auto& biome = biomes.getBiomeAt(chunk.first->temperature.get(lp), chunk.first->humidity.get(lp), chunk.first->roughness.get(lp));
chunk.second->blocks = {};
chunk.second->biomes = {};
for (unsigned short y = 0; y < 16; y++) {
lp.y = y;
unsigned short ind = Space::Block::index(lp);
std::array<std::array<unsigned short, 16>, 16> biomeArray {};
for (unsigned short x = 0; x < 16; x++) {
biomeArray[x] = {};
for (unsigned short z = 0; z < 16; z++) {
lp = {x, 0, z};
biomeArray[x][z] = biomes.getBiomeAt(
chunk.first->temperature.get(lp),
chunk.first->humidity.get(lp),
chunk.first->roughness.get(lp)).index;
}
}
chunk.second->biomes[ind] = biome.index;
for (unsigned short m = 0; m < 4096; m++) {
Vec::indAssignVec(m, lp);
auto& biome = biomes.biomeFromId(biomeArray[lp.x][lp.z]);
if (chunk.second->blocks[ind] != DefinitionAtlas::INVALID) continue;
unsigned int storedBlock = (chunk.second->blocks.size() <= 0 ? -1 : chunk.second->blocks[chunk.second->blocks.size() - 1]);
unsigned int storedBiome = (chunk.second->biomes.size() <= 0 ? -1 : chunk.second->biomes[chunk.second->biomes.size() - 1]);
int d = std::floor(chunk.first->depth[ind]);
chunk.second->blocks[ind]
if (biome.index != storedBiome) {
chunk.second->biomes.emplace_back(m);
chunk.second->biomes.emplace_back(biome.index);
}
// if (chunk.second->blocks[ind] != DefinitionAtlas::INVALID) continue;
int d = std::floor(chunk.first->depth[m]);
unsigned int targetBlock
= d <= 1 ? DefinitionAtlas::AIR
: d <= 2 ? biome.topBlock
: d <= 4 ? biome.soilBlock
: biome.rockBlock;
if (targetBlock != storedBlock) {
chunk.second->blocks.emplace_back(m);
chunk.second->blocks.emplace_back(targetBlock);
}
}
if (dupe->partial) for (unsigned short i = 0; i < 4096; i++) {
unsigned int b = dupe->getBlock(i);
if (b != DefinitionAtlas::INVALID) chunk.second->setBlock(i, b);
}
}
void MapGen::generateStructures(chunk_partials_map& chunks, chunk_partial& chunk) {
@ -235,7 +259,9 @@ void MapGen::setBlock(glm::ivec3 worldPos, unsigned int block, MapGen::chunk_par
if (chunks.count(chunkPos)) chunk = chunks.at(chunkPos).second;
else {
chunk = new BlockChunk();
chunk->initializeEmpty();
chunk->pos = chunkPos;
chunk->partial = true;
chunks.insert(std::pair<glm::ivec3, chunk_partial>{chunkPos, {new MapGenJob(), chunk}});
}
@ -261,5 +287,6 @@ std::shared_ptr<BlockChunk> MapGen::combinePartials(std::shared_ptr<BlockChunk>
}
res->generated = src->generated || res->generated;
res->partial = !res->generated;
return res;
}

View File

@ -28,10 +28,26 @@ MeshGenerator::MeshGenerator(MeshDetails* meshDetails, LocalDefs& defs, std::sha
glm::vec3 vis;
glm::vec3 check;
for (unsigned short i = 0; i < 4096; i++) {
BlockModel& model = hi ? atlas.blockFromId(chunk->getBlock(i)).model : atlas.blockFromId(chunk->getBlock(i)).farModel;
const auto& blocks = chunk->cGetBlocks();
const auto& biomes = chunk->cGetBiomes();
glm::vec3 biomeTint = defs.biomes.biomeFromId(chunk->getBiome(i)).biomeTint;
unsigned short blockArrayPos = 0;
unsigned int cBlock = blocks[blockArrayPos + 1];
unsigned short biomeArrayPos = 0;
unsigned short cBiome = biomes[biomeArrayPos + 1];
for (unsigned short i = 0; i < 4096; i++) {
if (blockArrayPos + 2 < blocks.size() && i >= blocks[blockArrayPos + 2]) {
blockArrayPos += 2;
cBlock = blocks[blockArrayPos + 1];
}
if (biomeArrayPos + 2 < biomes.size() && i >= biomes[biomeArrayPos + 2]) {
biomeArrayPos += 2;
cBiome = biomes[biomeArrayPos + 1];
}
BlockModel& model = hi ? atlas.blockFromId(cBlock).model : atlas.blockFromId(cBlock).farModel;
glm::vec3 biomeTint = defs.biomes.biomeFromId(cBiome).biomeTint;
if (model.visible) {
Vec::indAssignVec(i, off);
@ -82,7 +98,6 @@ MeshGenerator::MeshGenerator(MeshDetails* meshDetails, LocalDefs& defs, std::sha
BlockDef& MeshGenerator::getBlockAt(const glm::ivec3 &pos) {
if (pos.x < 0 || pos.x >= 16 || pos.y < 0 || pos.y >= 16 || pos.z < 0 || pos.z >= 16) {
if (pos.x == 16) return atlas.blockFromId(adjacent[0]->getBlock(pos - glm::ivec3 {16, 0, 0}));
if (pos.x == -1) return atlas.blockFromId(adjacent[1]->getBlock(pos + glm::ivec3 {16, 0, 0}));

View File

@ -8,10 +8,10 @@
#include <gzip/decompress.hpp>
#include <gzip/utils.hpp>
BlockChunk::BlockChunk(const std::array<unsigned int, 4096>& blocks, const std::array<unsigned short, 4096>& biomes) :
BlockChunk::BlockChunk(const std::vector<unsigned int>& blocks, const std::vector<unsigned short>& biomes) :
BlockChunk(blocks, biomes, {0, 0, 0}) {}
BlockChunk::BlockChunk(const std::array<unsigned int, 4096>& blocks, const std::array<unsigned short, 4096>& biomes, glm::ivec3 pos) :
BlockChunk::BlockChunk(const std::vector<unsigned int>& blocks, const std::vector<unsigned short>& biomes, glm::ivec3 pos) :
blocks(std::move(blocks)),
biomes(std::move(biomes)),
pos(pos),
@ -19,55 +19,134 @@ BlockChunk::BlockChunk(const std::array<unsigned int, 4096>& blocks, const std::
calcNonAirBlocks();
}
bool BlockChunk::setBlock(unsigned int ind, unsigned int blk) {
// Exit early if no manipulation is needed.
if (ind >= 4096) return false;
const unsigned int existing = getBlock(ind);
if (existing == blk) return false;
// Deal with shouldHaveMesh
if (blk == DefinitionAtlas::AIR) {
if ((nonAirBlocks = fmax(nonAirBlocks - 1, 0)) == 0) {
empty = true;
shouldHaveMesh = false;
}
}
else if (existing == DefinitionAtlas::AIR) {
if (nonAirBlocks == 0) shouldHaveMesh = true;
empty = false;
nonAirBlocks++;
}
if (ind == 0) {
if (blocks[2] == ind + 1) {
blocks[1] = blk;
return true;
}
else {
blocks.insert(blocks.begin() + 2, 2, ind);
blocks[2] = 1;
blocks[3] = blocks[1];
blocks[1] = blk;
return true;
}
}
for (unsigned int i = 0; i < blocks.size(); i += 2) {
if (blocks[i] == ind) {
// We found an index equating to the block we are going to be setting.
if (blocks[i - 1] == blk) {
// The last block strip is the same block ID as what we are setting,
// So we should extend that strip.
if (blocks.size() > i + 2 && blocks[i + 2] == ind + 1) {
// The next block is one later, meaning we can simply remove this found block
// To cause the next strip to cascade over its position.
blocks.erase(blocks.begin() + i, blocks.begin() + i + 2);
return true;
}
else {
// The next strip is multiple blocks over, so just add one to our found block index.
blocks[i] += 1;
return true;
}
}
else {
// The last strip is not the same block.
if (blocks.size() > i + 2 && blocks[i + 2] == ind + 1) {
// There is only one of our block, so we can just update its id.
blocks[i + 1] = blk;
return true;
}
else {
// The next strip is multiple blocks over, so we need to copy the previous block to the right
// and then set our block into its place
blocks.insert(blocks.begin() + i, 2, ind);
blocks[i + 1] = blk;
blocks[i + 2] = ind + 1;
return true;
}
}
}
else if (blocks[i] > ind) {
// We found a strip with an index *larger* than our ind.
// We can assume the last strip is not our block, because the getBlock() catch would have caught that.
if (blocks[i] == ind + 1) {
if (blocks[i + 1] == blk) {
// The next block over is the same, so we can just decrement our index by one.
blocks[i] --;
return true;
}
else {
// There is only one of our block to be placed, directly before our current strip
blocks.insert(blocks.begin() + i, 2, ind);
blocks[i + 1] = blk;
return true;
}
}
else {
// The next strip is multiple blocks over, so we need to insert both our block
// *and* the previous strip's block after
blocks.insert(blocks.begin() + i, 4, ind);
blocks[i + 1] = blk;
blocks[i + 2] = ind + 1;
blocks[i + 3] = blocks[i - 1];
return true;
}
}
}
// Escaped the for loop, meaning there's no index greater than ours.
// We will insert our index at the end of the array, and insert the previous block after
// if we're not at the end of the chunk.
blocks.push_back(ind);
blocks.push_back(blk);
if (ind >= 4095) return true; // Don't add the reset if at the end of the chunk.
blocks.push_back(ind + 1);
blocks.push_back(blocks[blocks.size() - 4]);
return true;
}
const std::vector<unsigned int> &BlockChunk::cGetBlocks() const {
return blocks;
}
const std::vector<unsigned short> &BlockChunk::cGetBiomes() const {
return biomes;
}
Packet BlockChunk::serialize() {
Serializer s;
s.append(pos);
{
unsigned int curr = blocks[0];
unsigned int length = 1;
std::string temp = Serializer().append(blocks).data;
s.append<std::string>(gzip::compress(temp.data(), temp.size()));
std::vector<unsigned int> blocksRle;
temp = Serializer().append(biomes).data;
s.append<std::string>(gzip::compress(temp.data(), temp.size()));
for (int i = 1; i < 4096; i++) {
if (blocks[i] == curr) length++;
else {
blocksRle.push_back(curr);
blocksRle.push_back(length);
length = 1;
curr = blocks[i];
}
}
blocksRle.push_back(curr);
blocksRle.push_back(length);
std::string temp = Serializer().append(blocksRle).data;
s.append<std::string>(gzip::compress(temp.data(), temp.size()));
} {
unsigned int curr = biomes[0];
unsigned short length = 1;
std::vector<unsigned short> biomesRle;
for (int i = 1; i < 4096; i++) {
if (biomes[i] == curr) length++;
else {
biomesRle.push_back(curr);
biomesRle.push_back(length);
length = 1;
curr = biomes[i];
}
}
biomesRle.push_back(curr);
biomesRle.push_back(length);
std::string temp = Serializer().append(biomesRle).data;
s.append<std::string>(gzip::compress(temp.data(), temp.size()));
}
Packet p(PacketType::CHUNK);
p.data = s.data;
return p;
return s.packet(PacketType::CHUNK);
}
void BlockChunk::deserialize(Packet& packet) {
@ -75,60 +154,39 @@ void BlockChunk::deserialize(Packet& packet) {
pos = d.read<glm::ivec3>();
{
auto gzip = d.read<std::string>();
if (!gzip::is_compressed(gzip.data(), gzip.length())) throw "Invalid Blocks GZip Data.";
gzip = gzip::decompress(gzip.data(), gzip.length());
auto gzip = d.read<std::string>();
if (!gzip::is_compressed(gzip.data(), gzip.length())) throw "Invalid Blocks GZip Data.";
gzip = gzip::decompress(gzip.data(), gzip.length());
std::vector<unsigned int> rle = Deserializer(gzip).read<std::vector<unsigned int>>();
this->empty = true;
int ind = 0;
for (int i = 0; i < rle.size() / 2; i++) {
unsigned int block = rle[i * 2];
unsigned int count = rle[i * 2 + 1];
for (int j = 0; j < count; j++) {
blocks[ind++] = block;
if (block != 0) this->empty = false;
if (ind >= 4096) goto biomes;
}
}
}
biomes: {}
{
auto gzip = d.read<std::string>();
if (!gzip::is_compressed(gzip.data(), gzip.length())) throw "Invalid Biomes GZip Data.";
gzip = gzip::decompress(gzip.data(), gzip.length());
std::vector<unsigned short> rle = Deserializer(gzip).read<std::vector<unsigned short>>();
int ind = 0;
for (int i = 0; i < rle.size() / 2; i++) {
unsigned int biome = rle[i * 2];
unsigned int count = rle[i * 2 + 1];
for (int j = 0; j < count; j++) {
biomes[ind++] = biome;
if (ind >= 4096) goto end;
}
}
}
end: {}
blocks = Deserializer(gzip).read<std::vector<unsigned int>>();
calcNonAirBlocks();
gzip = d.read<std::string>();
if (!gzip::is_compressed(gzip.data(), gzip.length())) throw "Invalid Biomes GZip Data.";
gzip = gzip::decompress(gzip.data(), gzip.length());
biomes = Deserializer(gzip).read<std::vector<unsigned short>>();
}
void BlockChunk::calcNonAirBlocks() {
nonAirBlocks = 0;
empty = true;
for (unsigned int block : blocks) {
if (block > DefinitionAtlas::AIR) {
for (unsigned int i = 0; i < blocks.size(); i += 2) {
unsigned int cInd = blocks[i];
unsigned int lInd = (i == 0 ? 0 : blocks[i - 2]);
unsigned int blk = blocks[i + 1];
if (blk > DefinitionAtlas::AIR) {
empty = false;
nonAirBlocks++;
nonAirBlocks += cInd - lInd;
}
}
shouldHaveMesh = !empty;
}
}
void BlockChunk::initializeEmpty() {
blocks = {0, 0};
biomes = {0, 0};
}

View File

@ -19,10 +19,12 @@
class BlockChunk {
public:
BlockChunk() = default;
explicit BlockChunk(const std::array<unsigned int, 4096>& blocks, const std::array<unsigned short, 4096>& biomes);
BlockChunk(const std::array<unsigned int, 4096>& blocks, const std::array<unsigned short, 4096>& biomes, glm::ivec3 pos);
explicit BlockChunk(const std::vector<unsigned int>& blocks, const std::vector<unsigned short>& biomes);
BlockChunk(const std::vector<unsigned int>& blocks, const std::vector<unsigned short>& biomes, glm::ivec3 pos);
inline bool setBlock(unsigned int ind, unsigned int blk);
void initializeEmpty();
bool setBlock(unsigned int ind, unsigned int blk);
inline bool setBlock(const glm::ivec3& pos, unsigned int blk);
inline unsigned int getBlock(unsigned int ind) const;
@ -39,6 +41,9 @@ public:
inline int getSunlight(unsigned int ind);
inline int getBlocklight(unsigned int ind);
const std::vector<unsigned int>& cGetBlocks() const;
const std::vector<unsigned short>& cGetBiomes() const;
Packet serialize();
void deserialize(Packet& packet);
@ -49,37 +54,18 @@ public:
glm::ivec3 pos;
private:
std::array<unsigned int, 4096> blocks {};
std::array<unsigned short, 4096> biomes {};
std::vector<unsigned int> blocks {};
std::vector<unsigned short> biomes {};
std::array<unsigned char, 4096> lighting {};
bool empty = true;
bool partial = false;
unsigned short nonAirBlocks = 0;
friend class MapGen;
void calcNonAirBlocks();
};
inline bool BlockChunk::setBlock(unsigned int ind, unsigned int blk) {
if (ind >= 4096) return false;
if (blocks[ind] != blk) {
if (blk == DefinitionAtlas::AIR) {
if ((nonAirBlocks = fmax(nonAirBlocks - 1, 0)) == 0) {
empty = true;
shouldHaveMesh = false;
}
}
else if (blocks[ind] == DefinitionAtlas::AIR) {
if (nonAirBlocks == 0) shouldHaveMesh = true;
empty = false;
nonAirBlocks++;
}
blocks[ind] = blk;
return true;
}
return false;
}
inline bool BlockChunk::setBlock(const glm::ivec3& pos, unsigned int blk) {
if (pos.x > 15 || pos.x < 0 || pos.y > 15 || pos.y < 0 || pos.z > 15 || pos.z < 0) return false;
return setBlock(Space::Block::index(pos), blk);
@ -87,7 +73,10 @@ inline bool BlockChunk::setBlock(const glm::ivec3& pos, unsigned int blk) {
inline unsigned int BlockChunk::getBlock(unsigned int ind) const {
if (ind >= 4096) return DefinitionAtlas::INVALID;
return blocks[ind];
for (unsigned int i = 0; i < blocks.size(); i += 2) {
if (blocks[i] > ind) return blocks[i - 1];
}
return blocks[blocks.size() - 1];
}
inline unsigned int BlockChunk::getBlock(const glm::ivec3& pos) const {
@ -97,7 +86,10 @@ inline unsigned int BlockChunk::getBlock(const glm::ivec3& pos) const {
inline unsigned short BlockChunk::getBiome(unsigned int ind) const {
if (ind >= 4096) return BiomeAtlas::INVALID;
return biomes[ind];
for (unsigned int i = 0; i < biomes.size(); i += 2) {
if (biomes[i] > ind) return biomes[i - 1];
}
return biomes[biomes.size() - 1];
}
inline unsigned short BlockChunk::getBiome(const glm::ivec3& pos) const {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 205 B

After

Width:  |  Height:  |  Size: 205 B

View File

@ -5,6 +5,7 @@
#pragma clang diagnostic pop
#include <catch2/catch.hpp>
#include <sol2/sol.hpp>
TEST_CASE("Catch2 Library", "[core]") {
REQUIRE(true);

View File

@ -6,10 +6,13 @@
#include <iostream>
#include "../../src/world/chunk/BlockChunk.h"
#pragma clang diagnostic push
#pragma ide diagnostic ignored "OCUnusedGlobalDeclarationInspection"
TEST_CASE("BlockChunk", "[world]") {
BlockChunk b;
SECTION("Lighting") {
BlockChunk b;
b.setSunlight(1, 4);
b.setSunlight(2, 1);
b.setSunlight(3, 11);
@ -42,4 +45,296 @@ TEST_CASE("BlockChunk", "[world]") {
REQUIRE(b.getBlocklight(3000) == 0);
}
}
}
SECTION("Blocks") {
SECTION("Exact index = 0, strip one after") {
BlockChunk b {{0, 1, 1, 0}, {}};
b.setBlock(0, 2);
REQUIRE(b.cGetBlocks().size() == 4);
REQUIRE(b.cGetBlocks()[0] == 0);
REQUIRE(b.cGetBlocks()[1] == 2);
REQUIRE(b.cGetBlocks()[2] == 1);
REQUIRE(b.cGetBlocks()[3] == 0);
}
SECTION("Exact index, last block same, strip one after") {
BlockChunk b {{0, 1, 1, 0, 2, 5}, {}};
b.setBlock(1, 1);
REQUIRE(b.cGetBlocks().size() == 4);
REQUIRE(b.cGetBlocks()[0] == 0);
REQUIRE(b.cGetBlocks()[1] == 1);
REQUIRE(b.cGetBlocks()[2] == 2);
REQUIRE(b.cGetBlocks()[3] == 5);
}
SECTION("Exact index, last block same, strip two after") {
BlockChunk b {{0, 1, 2, 0, 4, 5}, {}};
b.setBlock(2, 1);
REQUIRE(b.cGetBlocks().size() == 6);
REQUIRE(b.cGetBlocks()[0] == 0);
REQUIRE(b.cGetBlocks()[1] == 1);
REQUIRE(b.cGetBlocks()[2] == 3);
REQUIRE(b.cGetBlocks()[3] == 0);
REQUIRE(b.cGetBlocks()[4] == 4);
REQUIRE(b.cGetBlocks()[5] == 5);
SECTION("Exact index, last block same, strip one after AFTER two after") {
b.setBlock(3, 1);
REQUIRE(b.cGetBlocks().size() == 4);
REQUIRE(b.cGetBlocks()[0] == 0);
REQUIRE(b.cGetBlocks()[1] == 1);
REQUIRE(b.cGetBlocks()[4] == 4);
REQUIRE(b.cGetBlocks()[5] == 5);
}
}
SECTION("Exact index, last block same, no strip after") {
BlockChunk b {{0, 1, 1, 0}, {}};
b.setBlock(1, 1);
REQUIRE(b.cGetBlocks().size() == 4);
REQUIRE(b.cGetBlocks()[0] == 0);
REQUIRE(b.cGetBlocks()[1] == 1);
REQUIRE(b.cGetBlocks()[2] == 2);
REQUIRE(b.cGetBlocks()[3] == 0);
}
SECTION("Exact index, last block different, strip one after") {
BlockChunk b {{0, 1, 1, 0, 2, 5}, {}};
b.setBlock(1, 2);
REQUIRE(b.cGetBlocks().size() == 6);
REQUIRE(b.cGetBlocks()[0] == 0);
REQUIRE(b.cGetBlocks()[1] == 1);
REQUIRE(b.cGetBlocks()[2] == 1);
REQUIRE(b.cGetBlocks()[3] == 2);
REQUIRE(b.cGetBlocks()[4] == 2);
REQUIRE(b.cGetBlocks()[5] == 5);
}
SECTION("Exact index, last block different, strip two after") {
BlockChunk b {{0, 1, 2, 0, 4, 5}, {}};
b.setBlock(2, 2);
REQUIRE(b.cGetBlocks().size() == 8);
REQUIRE(b.cGetBlocks()[0] == 0);
REQUIRE(b.cGetBlocks()[1] == 1);
REQUIRE(b.cGetBlocks()[2] == 2);
REQUIRE(b.cGetBlocks()[3] == 2);
REQUIRE(b.cGetBlocks()[4] == 3);
REQUIRE(b.cGetBlocks()[5] == 0);
REQUIRE(b.cGetBlocks()[6] == 4);
REQUIRE(b.cGetBlocks()[7] == 5);
SECTION("Exact index, last block different, strip one after AFTER two after") {
b.setBlock(3, 1);
REQUIRE(b.cGetBlocks().size() == 8);
REQUIRE(b.cGetBlocks()[0] == 0);
REQUIRE(b.cGetBlocks()[1] == 1);
REQUIRE(b.cGetBlocks()[2] == 2);
REQUIRE(b.cGetBlocks()[3] == 2);
REQUIRE(b.cGetBlocks()[4] == 3);
REQUIRE(b.cGetBlocks()[5] == 1);
REQUIRE(b.cGetBlocks()[6] == 4);
REQUIRE(b.cGetBlocks()[7] == 5);
}
SECTION("Exact index, last block same, strip one after AFTER two after") {
b.setBlock(3, 2);
REQUIRE(b.cGetBlocks().size() == 6);
REQUIRE(b.cGetBlocks()[0] == 0);
REQUIRE(b.cGetBlocks()[1] == 1);
REQUIRE(b.cGetBlocks()[2] == 2);
REQUIRE(b.cGetBlocks()[3] == 2);
REQUIRE(b.cGetBlocks()[4] == 4);
REQUIRE(b.cGetBlocks()[5] == 5);
}
}
SECTION("Exact index, last block different, no strip after") {
BlockChunk b {{0, 1, 1, 0}, {}};
b.setBlock(1, 2);
REQUIRE(b.cGetBlocks().size() == 6);
REQUIRE(b.cGetBlocks()[0] == 0);
REQUIRE(b.cGetBlocks()[1] == 1);
REQUIRE(b.cGetBlocks()[2] == 1);
REQUIRE(b.cGetBlocks()[3] == 2);
REQUIRE(b.cGetBlocks()[4] == 2);
REQUIRE(b.cGetBlocks()[5] == 0);
}
SECTION("Greater index, last block same, strip one after") {
BlockChunk b {{0, 1, 1, 0, 3, 5}, {}};
b.setBlock(2, 0);
// The function should exit early because of the getBlock check
REQUIRE(b.cGetBlocks().size() == 6);
REQUIRE(b.cGetBlocks()[0] == 0);
REQUIRE(b.cGetBlocks()[1] == 1);
REQUIRE(b.cGetBlocks()[2] == 1);
REQUIRE(b.cGetBlocks()[3] == 0);
REQUIRE(b.cGetBlocks()[4] == 3);
REQUIRE(b.cGetBlocks()[5] == 5);
}
SECTION("Greater index, last block same, strip two after") {
BlockChunk b {{0, 1, 2, 0, 5, 5}, {}};
b.setBlock(3, 0);
// The function should exit early because of the getBlock check
REQUIRE(b.cGetBlocks().size() == 6);
REQUIRE(b.cGetBlocks()[0] == 0);
REQUIRE(b.cGetBlocks()[1] == 1);
REQUIRE(b.cGetBlocks()[2] == 2);
REQUIRE(b.cGetBlocks()[3] == 0);
REQUIRE(b.cGetBlocks()[4] == 5);
REQUIRE(b.cGetBlocks()[5] == 5);
}
SECTION("Greater index, last block same, no strip after") {
BlockChunk b {{0, 1, 3, 0}, {}};
b.setBlock(1, 1);
// The function should exit early because of the getBlock check
REQUIRE(b.cGetBlocks().size() == 4);
REQUIRE(b.cGetBlocks()[0] == 0);
REQUIRE(b.cGetBlocks()[1] == 1);
REQUIRE(b.cGetBlocks()[2] == 3);
REQUIRE(b.cGetBlocks()[3] == 0);
}
SECTION("Greater index, last block different, strip one after") {
SECTION("Indexed block different") {
BlockChunk b {{0, 1, 1, 0, 3, 5}, {}};
b.setBlock(2, 2);
REQUIRE(b.cGetBlocks().size() == 8);
REQUIRE(b.cGetBlocks()[0] == 0);
REQUIRE(b.cGetBlocks()[1] == 1);
REQUIRE(b.cGetBlocks()[2] == 1);
REQUIRE(b.cGetBlocks()[3] == 0);
REQUIRE(b.cGetBlocks()[4] == 2);
REQUIRE(b.cGetBlocks()[5] == 2);
REQUIRE(b.cGetBlocks()[6] == 3);
REQUIRE(b.cGetBlocks()[7] == 5);
}
SECTION("Indexed block same") {
BlockChunk b {{0, 1, 1, 0, 3, 5}, {}};
b.setBlock(2, 5);
REQUIRE(b.cGetBlocks().size() == 6);
REQUIRE(b.cGetBlocks()[0] == 0);
REQUIRE(b.cGetBlocks()[1] == 1);
REQUIRE(b.cGetBlocks()[2] == 1);
REQUIRE(b.cGetBlocks()[3] == 0);
REQUIRE(b.cGetBlocks()[4] == 2);
REQUIRE(b.cGetBlocks()[5] == 5);
}
}
SECTION("Greater index, last block different, strip three after") {
BlockChunk b {{0, 1, 2, 0, 6, 6}, {}};
b.setBlock(3, 5);
REQUIRE(b.cGetBlocks().size() == 10);
REQUIRE(b.cGetBlocks()[0] == 0);
REQUIRE(b.cGetBlocks()[1] == 1);
REQUIRE(b.cGetBlocks()[2] == 2);
REQUIRE(b.cGetBlocks()[3] == 0);
REQUIRE(b.cGetBlocks()[4] == 3);
REQUIRE(b.cGetBlocks()[5] == 5);
REQUIRE(b.cGetBlocks()[6] == 4);
REQUIRE(b.cGetBlocks()[7] == 0);
REQUIRE(b.cGetBlocks()[8] == 6);
REQUIRE(b.cGetBlocks()[9] == 6);
SECTION("Greater index, last block same, strip one after AFTER three after") {
b.setBlock(4, 5);
REQUIRE(b.cGetBlocks().size() == 10);
REQUIRE(b.cGetBlocks()[0] == 0);
REQUIRE(b.cGetBlocks()[1] == 1);
REQUIRE(b.cGetBlocks()[2] == 2);
REQUIRE(b.cGetBlocks()[3] == 0);
REQUIRE(b.cGetBlocks()[4] == 3);
REQUIRE(b.cGetBlocks()[5] == 5);
REQUIRE(b.cGetBlocks()[6] == 5);
REQUIRE(b.cGetBlocks()[7] == 0);
REQUIRE(b.cGetBlocks()[8] == 6);
REQUIRE(b.cGetBlocks()[9] == 6);
}
SECTION("Greater index, last block different, strip one after AFTER three after") {
b.setBlock(4, 2);
REQUIRE(b.cGetBlocks().size() == 12);
REQUIRE(b.cGetBlocks()[0] == 0);
REQUIRE(b.cGetBlocks()[1] == 1);
REQUIRE(b.cGetBlocks()[2] == 2);
REQUIRE(b.cGetBlocks()[3] == 0);
REQUIRE(b.cGetBlocks()[4] == 3);
REQUIRE(b.cGetBlocks()[5] == 5);
REQUIRE(b.cGetBlocks()[6] == 4);
REQUIRE(b.cGetBlocks()[7] == 2);
REQUIRE(b.cGetBlocks()[8] == 5);
REQUIRE(b.cGetBlocks()[9] == 0);
REQUIRE(b.cGetBlocks()[10] == 6);
REQUIRE(b.cGetBlocks()[11] == 6);
}
}
SECTION("No index found in loop, last block different, not end of chunk") {
BlockChunk b {{0, 1, 2, 0}, {}};
b.setBlock(6, 6);
REQUIRE(b.cGetBlocks().size() == 8);
REQUIRE(b.cGetBlocks()[0] == 0);
REQUIRE(b.cGetBlocks()[1] == 1);
REQUIRE(b.cGetBlocks()[2] == 2);
REQUIRE(b.cGetBlocks()[3] == 0);
REQUIRE(b.cGetBlocks()[4] == 6);
REQUIRE(b.cGetBlocks()[5] == 6);
REQUIRE(b.cGetBlocks()[6] == 7);
REQUIRE(b.cGetBlocks()[7] == 0);
}
SECTION("No index found in loop, last block different, end of chunk") {
BlockChunk b {{0, 1, 2, 0}, {}};
b.setBlock(4095, 6);
REQUIRE(b.cGetBlocks().size() == 6);
REQUIRE(b.cGetBlocks()[0] == 0);
REQUIRE(b.cGetBlocks()[1] == 1);
REQUIRE(b.cGetBlocks()[2] == 2);
REQUIRE(b.cGetBlocks()[3] == 0);
REQUIRE(b.cGetBlocks()[4] == 4095);
REQUIRE(b.cGetBlocks()[5] == 6);
}
}
}
#pragma clang diagnostic pop