mirror of
https://github.com/luanti-org/luanti.git
synced 2025-09-30 19:22:14 +00:00
Monoblocks: optimize blocks that contain a single type of node (#16293)
Reduces memory usage on the server, especially with many user and/or large viewing distances. Currently disabled on the client due to known data races on a block's data.
This commit is contained in:
parent
afd681d013
commit
08b7870c79
11 changed files with 245 additions and 47 deletions
|
@ -371,6 +371,7 @@ public:
|
||||||
scene::ISceneManager *getSceneManager();
|
scene::ISceneManager *getSceneManager();
|
||||||
|
|
||||||
// IGameDef interface
|
// IGameDef interface
|
||||||
|
bool isClient() override { return true; }
|
||||||
IItemDefManager* getItemDefManager() override;
|
IItemDefManager* getItemDefManager() override;
|
||||||
const NodeDefManager* getNodeDefManager() override;
|
const NodeDefManager* getNodeDefManager() override;
|
||||||
ICraftDefManager* getCraftDefManager() override;
|
ICraftDefManager* getCraftDefManager() override;
|
||||||
|
|
|
@ -30,6 +30,7 @@ public:
|
||||||
delete m_itemdef;
|
delete m_itemdef;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool isClient() override { return false; }
|
||||||
IItemDefManager *getItemDefManager() override { return m_itemdef; }
|
IItemDefManager *getItemDefManager() override { return m_itemdef; }
|
||||||
const NodeDefManager *getNodeDefManager() override { return m_nodedef; }
|
const NodeDefManager *getNodeDefManager() override { return m_nodedef; }
|
||||||
NodeDefManager* getWritableNodeDefManager() { return m_nodedef; }
|
NodeDefManager* getWritableNodeDefManager() { return m_nodedef; }
|
||||||
|
|
|
@ -61,4 +61,5 @@ public:
|
||||||
virtual bool sendModChannelMessage(const std::string &channel,
|
virtual bool sendModChannelMessage(const std::string &channel,
|
||||||
const std::string &message) = 0;
|
const std::string &message) = 0;
|
||||||
virtual ModChannel *getModChannel(const std::string &channel) = 0;
|
virtual ModChannel *getModChannel(const std::string &channel) = 0;
|
||||||
|
virtual bool isClient() = 0;
|
||||||
};
|
};
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
#include "mapblock.h"
|
#include "mapblock.h"
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include "map.h"
|
#include "map.h"
|
||||||
#include "light.h"
|
#include "light.h"
|
||||||
|
@ -101,11 +102,13 @@ static const char *modified_reason_strings[] = {
|
||||||
MapBlock::MapBlock(v3s16 pos, IGameDef *gamedef):
|
MapBlock::MapBlock(v3s16 pos, IGameDef *gamedef):
|
||||||
m_pos(pos),
|
m_pos(pos),
|
||||||
m_pos_relative(pos * MAP_BLOCKSIZE),
|
m_pos_relative(pos * MAP_BLOCKSIZE),
|
||||||
data(new MapNode[nodecount]),
|
m_gamedef(gamedef),
|
||||||
m_gamedef(gamedef)
|
m_is_mono_block(false)
|
||||||
{
|
{
|
||||||
reallocate();
|
// We start with nodecount nodes, because in the vast
|
||||||
assert(m_modified > MOD_STATE_CLEAN);
|
// majority of the cases a block is created just before
|
||||||
|
// it is de-serialized or generated.
|
||||||
|
reallocate(nodecount, MapNode(CONTENT_IGNORE));
|
||||||
}
|
}
|
||||||
|
|
||||||
MapBlock::~MapBlock()
|
MapBlock::~MapBlock()
|
||||||
|
@ -118,6 +121,7 @@ MapBlock::~MapBlock()
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
delete[] data;
|
delete[] data;
|
||||||
|
if (!m_is_mono_block)
|
||||||
porting::TrackFreedMemory(sizeof(MapNode) * nodecount);
|
porting::TrackFreedMemory(sizeof(MapNode) * nodecount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -214,7 +218,7 @@ void MapBlock::copyTo(VoxelManipulator &dst)
|
||||||
VoxelArea data_area(v3s16(0,0,0), data_size - v3s16(1,1,1));
|
VoxelArea data_area(v3s16(0,0,0), data_size - v3s16(1,1,1));
|
||||||
|
|
||||||
// Copy from data to VoxelManipulator
|
// Copy from data to VoxelManipulator
|
||||||
dst.copyFrom(data, data_area, v3s16(0,0,0),
|
dst.copyFrom(data, m_is_mono_block, data_area, v3s16(0,0,0),
|
||||||
getPosRelative(), data_size);
|
getPosRelative(), data_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -223,9 +227,61 @@ void MapBlock::copyFrom(const VoxelManipulator &src)
|
||||||
v3s16 data_size(MAP_BLOCKSIZE, MAP_BLOCKSIZE, MAP_BLOCKSIZE);
|
v3s16 data_size(MAP_BLOCKSIZE, MAP_BLOCKSIZE, MAP_BLOCKSIZE);
|
||||||
VoxelArea data_area(v3s16(0,0,0), data_size - v3s16(1,1,1));
|
VoxelArea data_area(v3s16(0,0,0), data_size - v3s16(1,1,1));
|
||||||
|
|
||||||
|
expandNodesIfNeeded();
|
||||||
// Copy from VoxelManipulator to data
|
// Copy from VoxelManipulator to data
|
||||||
src.copyTo(data, data_area, v3s16(0,0,0),
|
src.copyTo(data, data_area, v3s16(0,0,0),
|
||||||
getPosRelative(), data_size);
|
getPosRelative(), data_size);
|
||||||
|
tryShrinkNodes();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MapBlock::reallocate(u32 count, MapNode n)
|
||||||
|
{
|
||||||
|
assert(count == 1 || count == nodecount);
|
||||||
|
// For now monoblocks are disabled on the client.
|
||||||
|
// The client has known data races on the block's data (FIXME).
|
||||||
|
assert(!m_gamedef->isClient() || count == nodecount);
|
||||||
|
|
||||||
|
delete[] data;
|
||||||
|
if (data && !m_is_mono_block && count == 1)
|
||||||
|
porting::TrackFreedMemory(sizeof(MapNode) * nodecount);
|
||||||
|
|
||||||
|
data = new MapNode[count];
|
||||||
|
std::fill_n(data, count, n);
|
||||||
|
|
||||||
|
m_is_mono_block = (count == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MapBlock::tryShrinkNodes()
|
||||||
|
{
|
||||||
|
// For now monoblocks are disabled on the client.
|
||||||
|
// The client has known data races on the block's data (FIXME).
|
||||||
|
if (m_gamedef->isClient())
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (m_is_mono_block)
|
||||||
|
return;
|
||||||
|
|
||||||
|
MapNode n = data[0];
|
||||||
|
bool is_mono_block = true;
|
||||||
|
for (u32 i=1; i<nodecount; i++) {
|
||||||
|
if (n != data[i]) {
|
||||||
|
is_mono_block = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_mono_block) {
|
||||||
|
reallocate(1, n);
|
||||||
|
m_is_air = n.getContent() == CONTENT_AIR;
|
||||||
|
m_is_air_expired = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MapBlock::expandNodesIfNeeded()
|
||||||
|
{
|
||||||
|
if (m_is_mono_block) {
|
||||||
|
reallocate(nodecount, data[0]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MapBlock::actuallyUpdateIsAir()
|
void MapBlock::actuallyUpdateIsAir()
|
||||||
|
@ -233,6 +289,10 @@ void MapBlock::actuallyUpdateIsAir()
|
||||||
// Running this function un-expires m_is_air
|
// Running this function un-expires m_is_air
|
||||||
m_is_air_expired = false;
|
m_is_air_expired = false;
|
||||||
|
|
||||||
|
if (m_is_mono_block) {
|
||||||
|
m_is_air = data[0].getContent() == CONTENT_AIR;
|
||||||
|
return;
|
||||||
|
}
|
||||||
bool only_air = true;
|
bool only_air = true;
|
||||||
for (u32 i = 0; i < nodecount; i++) {
|
for (u32 i = 0; i < nodecount; i++) {
|
||||||
MapNode &n = data[i];
|
MapNode &n = data[i];
|
||||||
|
@ -260,12 +320,12 @@ void MapBlock::expireIsAirCache()
|
||||||
// Note that there's no technical reason why we *have to* renumber the IDs,
|
// Note that there's no technical reason why we *have to* renumber the IDs,
|
||||||
// but we do it anyway as it also helps compressability.
|
// but we do it anyway as it also helps compressability.
|
||||||
void MapBlock::getBlockNodeIdMapping(NameIdMapping *nimap, MapNode *nodes,
|
void MapBlock::getBlockNodeIdMapping(NameIdMapping *nimap, MapNode *nodes,
|
||||||
const NodeDefManager *nodedef)
|
u32 count, const NodeDefManager *nodedef)
|
||||||
{
|
{
|
||||||
IdIdMapping &mapping = IdIdMapping::giveClearedThreadLocalInstance();
|
IdIdMapping &mapping = IdIdMapping::giveClearedThreadLocalInstance();
|
||||||
|
|
||||||
content_t id_counter = 0;
|
content_t id_counter = 0;
|
||||||
for (u32 i = 0; i < MapBlock::nodecount; i++) {
|
for (u32 i = 0; i < count; i++) {
|
||||||
content_t global_id = nodes[i].getContent();
|
content_t global_id = nodes[i].getContent();
|
||||||
content_t id = CONTENT_IGNORE;
|
content_t id = CONTENT_IGNORE;
|
||||||
|
|
||||||
|
@ -378,13 +438,13 @@ void MapBlock::serialize(std::ostream &os_compressed, u8 version, bool disk, int
|
||||||
const u8 params_width = 2;
|
const u8 params_width = 2;
|
||||||
if(disk)
|
if(disk)
|
||||||
{
|
{
|
||||||
MapNode *tmp_nodes = new MapNode[nodecount];
|
const size_t size = m_is_mono_block ? 1 : nodecount;
|
||||||
memcpy(tmp_nodes, data, nodecount * sizeof(MapNode));
|
std::unique_ptr<MapNode[]> tmp_nodes(new MapNode[size]);
|
||||||
getBlockNodeIdMapping(&nimap, tmp_nodes, m_gamedef->ndef());
|
std::copy_n(data, size, tmp_nodes.get());
|
||||||
|
getBlockNodeIdMapping(&nimap, tmp_nodes.get(), size, m_gamedef->ndef());
|
||||||
|
|
||||||
buf = MapNode::serializeBulk(version, tmp_nodes, nodecount,
|
buf = MapNode::serializeBulk(version, tmp_nodes.get(), nodecount,
|
||||||
content_width, params_width);
|
content_width, params_width, m_is_mono_block);
|
||||||
delete[] tmp_nodes;
|
|
||||||
|
|
||||||
// write timestamp and node/id mapping first
|
// write timestamp and node/id mapping first
|
||||||
if (version >= 29) {
|
if (version >= 29) {
|
||||||
|
@ -396,7 +456,7 @@ void MapBlock::serialize(std::ostream &os_compressed, u8 version, bool disk, int
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
buf = MapNode::serializeBulk(version, data, nodecount,
|
buf = MapNode::serializeBulk(version, data, nodecount,
|
||||||
content_width, params_width);
|
content_width, params_width, m_is_mono_block);
|
||||||
}
|
}
|
||||||
|
|
||||||
writeU8(os, content_width);
|
writeU8(os, content_width);
|
||||||
|
@ -465,6 +525,7 @@ void MapBlock::deSerialize(std::istream &in_compressed, u8 version, bool disk)
|
||||||
TRACESTREAM(<<"MapBlock::deSerialize "<<getPos()<<std::endl);
|
TRACESTREAM(<<"MapBlock::deSerialize "<<getPos()<<std::endl);
|
||||||
|
|
||||||
m_is_air_expired = true;
|
m_is_air_expired = true;
|
||||||
|
expandNodesIfNeeded();
|
||||||
|
|
||||||
if(version <= 21)
|
if(version <= 21)
|
||||||
{
|
{
|
||||||
|
@ -593,10 +654,14 @@ void MapBlock::deSerialize(std::istream &in_compressed, u8 version, bool disk)
|
||||||
m_node_timers.deSerialize(is, version);
|
m_node_timers.deSerialize(is, version);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (nimap.size() == 1) {
|
||||||
|
tryShrinkNodes();
|
||||||
|
|
||||||
u16 dummy;
|
u16 dummy;
|
||||||
m_is_air = nimap.size() == 1 && nimap.getId("air", dummy);
|
m_is_air = nimap.getId("air", dummy);
|
||||||
m_is_air_expired = false;
|
m_is_air_expired = false;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
TRACESTREAM(<<"MapBlock::deSerialize "<<getPos()
|
TRACESTREAM(<<"MapBlock::deSerialize "<<getPos()
|
||||||
<<": Done."<<std::endl);
|
<<": Done."<<std::endl);
|
||||||
|
|
|
@ -22,6 +22,7 @@ class IGameDef;
|
||||||
class MapBlockMesh;
|
class MapBlockMesh;
|
||||||
class VoxelManipulator;
|
class VoxelManipulator;
|
||||||
class NameIdMapping;
|
class NameIdMapping;
|
||||||
|
class TestMapBlock;
|
||||||
|
|
||||||
#define BLOCK_TIMESTAMP_UNDEFINED 0xffffffff
|
#define BLOCK_TIMESTAMP_UNDEFINED 0xffffffff
|
||||||
|
|
||||||
|
@ -30,7 +31,7 @@ class NameIdMapping;
|
||||||
////
|
////
|
||||||
|
|
||||||
enum ModReason : u32 {
|
enum ModReason : u32 {
|
||||||
MOD_REASON_REALLOCATE = 1 << 0,
|
// UNUSED = 1 << 0,
|
||||||
MOD_REASON_SET_IS_UNDERGROUND = 1 << 1,
|
MOD_REASON_SET_IS_UNDERGROUND = 1 << 1,
|
||||||
MOD_REASON_SET_LIGHTING_COMPLETE = 1 << 2,
|
MOD_REASON_SET_LIGHTING_COMPLETE = 1 << 2,
|
||||||
MOD_REASON_SET_GENERATED = 1 << 3,
|
MOD_REASON_SET_GENERATED = 1 << 3,
|
||||||
|
@ -74,13 +75,6 @@ public:
|
||||||
m_orphan = true;
|
m_orphan = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void reallocate()
|
|
||||||
{
|
|
||||||
for (u32 i = 0; i < nodecount; i++)
|
|
||||||
data[i] = MapNode(CONTENT_IGNORE);
|
|
||||||
raiseModified(MOD_STATE_WRITE_NEEDED, MOD_REASON_REALLOCATE);
|
|
||||||
}
|
|
||||||
|
|
||||||
////
|
////
|
||||||
//// Modification tracking methods
|
//// Modification tracking methods
|
||||||
////
|
////
|
||||||
|
@ -234,7 +228,7 @@ public:
|
||||||
if (!*valid_position)
|
if (!*valid_position)
|
||||||
return {CONTENT_IGNORE};
|
return {CONTENT_IGNORE};
|
||||||
|
|
||||||
return data[z * zstride + y * ystride + x];
|
return data[m_is_mono_block ? 0 : z * zstride + y * ystride + x];
|
||||||
}
|
}
|
||||||
|
|
||||||
inline MapNode getNode(v3s16 p, bool *valid_position)
|
inline MapNode getNode(v3s16 p, bool *valid_position)
|
||||||
|
@ -253,6 +247,7 @@ public:
|
||||||
if (!isValidPosition(x, y, z))
|
if (!isValidPosition(x, y, z))
|
||||||
throw InvalidPositionException();
|
throw InvalidPositionException();
|
||||||
|
|
||||||
|
expandNodesIfNeeded();
|
||||||
data[z * zstride + y * ystride + x] = n;
|
data[z * zstride + y * ystride + x] = n;
|
||||||
raiseModified(MOD_STATE_WRITE_NEEDED, MOD_REASON_SET_NODE);
|
raiseModified(MOD_STATE_WRITE_NEEDED, MOD_REASON_SET_NODE);
|
||||||
}
|
}
|
||||||
|
@ -268,7 +263,7 @@ public:
|
||||||
|
|
||||||
inline MapNode getNodeNoCheck(s16 x, s16 y, s16 z)
|
inline MapNode getNodeNoCheck(s16 x, s16 y, s16 z)
|
||||||
{
|
{
|
||||||
return data[z * zstride + y * ystride + x];
|
return data[m_is_mono_block ? 0 : z * zstride + y * ystride + x];
|
||||||
}
|
}
|
||||||
|
|
||||||
inline MapNode getNodeNoCheck(v3s16 p)
|
inline MapNode getNodeNoCheck(v3s16 p)
|
||||||
|
@ -278,6 +273,7 @@ public:
|
||||||
|
|
||||||
inline void setNodeNoCheck(s16 x, s16 y, s16 z, MapNode n)
|
inline void setNodeNoCheck(s16 x, s16 y, s16 z, MapNode n)
|
||||||
{
|
{
|
||||||
|
expandNodesIfNeeded();
|
||||||
data[z * zstride + y * ystride + x] = n;
|
data[z * zstride + y * ystride + x] = n;
|
||||||
raiseModified(MOD_STATE_WRITE_NEEDED, MOD_REASON_SET_NODE);
|
raiseModified(MOD_STATE_WRITE_NEEDED, MOD_REASON_SET_NODE);
|
||||||
}
|
}
|
||||||
|
@ -431,14 +427,25 @@ private:
|
||||||
|
|
||||||
static const u32 nodecount = MAP_BLOCKSIZE * MAP_BLOCKSIZE * MAP_BLOCKSIZE;
|
static const u32 nodecount = MAP_BLOCKSIZE * MAP_BLOCKSIZE * MAP_BLOCKSIZE;
|
||||||
|
|
||||||
|
private:
|
||||||
|
#if BUILD_UNITTESTS
|
||||||
|
// access to data, tryConvertToMonoBlock, deconvertMonoblock
|
||||||
|
friend class TestMapBlock;
|
||||||
|
#endif
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Private methods
|
Private methods
|
||||||
*/
|
*/
|
||||||
|
|
||||||
void deSerialize_pre22(std::istream &is, u8 version, bool disk);
|
void deSerialize_pre22(std::istream &is, u8 version, bool disk);
|
||||||
|
// check if all nodes are identical, if so convert to monoblock
|
||||||
|
void tryShrinkNodes();
|
||||||
|
// if a monoblock, expand storage back to the full array
|
||||||
|
void expandNodesIfNeeded();
|
||||||
|
void reallocate(u32 count, MapNode n);
|
||||||
|
|
||||||
static void getBlockNodeIdMapping(NameIdMapping *nimap, MapNode *nodes,
|
static void getBlockNodeIdMapping(NameIdMapping *nimap, MapNode *nodes,
|
||||||
const NodeDefManager *nodedef);
|
u32 count, const NodeDefManager *nodedef);
|
||||||
static void correctBlockNodeIds(const NameIdMapping *nimap, MapNode *nodes,
|
static void correctBlockNodeIds(const NameIdMapping *nimap, MapNode *nodes,
|
||||||
IGameDef *gamedef);
|
IGameDef *gamedef);
|
||||||
|
|
||||||
|
@ -475,11 +482,11 @@ private:
|
||||||
short m_refcount = 0;
|
short m_refcount = 0;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Note that this is not an inline array because that has implications for
|
* Note that this is not an inline array because that has implications for heap
|
||||||
* heap fragmentation (the array is exactly 16K), CPU caches and/or
|
* fragmentation (the array is exactly 16K, or exactly 4 bytes for a "monoblock"),
|
||||||
* optimizability of algorithms working on this array.
|
* CPU caches and/or optimizability of algorithms working on this array.
|
||||||
*/
|
*/
|
||||||
MapNode *const data; // of `nodecount` elements
|
MapNode *data = nullptr;
|
||||||
|
|
||||||
// provides the item and node definitions
|
// provides the item and node definitions
|
||||||
IGameDef *m_gamedef;
|
IGameDef *m_gamedef;
|
||||||
|
@ -490,6 +497,11 @@ private:
|
||||||
*/
|
*/
|
||||||
float m_usage_timer = 0;
|
float m_usage_timer = 0;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* For "monoblocks", the whole block is filled with the same node, only this node is stored.
|
||||||
|
* (For reduced memory usage)
|
||||||
|
*/
|
||||||
|
bool m_is_mono_block;
|
||||||
public:
|
public:
|
||||||
//// ABM optimizations ////
|
//// ABM optimizations ////
|
||||||
// True if we never want to cache content types for this block
|
// True if we never want to cache content types for this block
|
||||||
|
|
|
@ -589,7 +589,7 @@ void MapNode::deSerialize(const u8 *source, u8 version)
|
||||||
|
|
||||||
Buffer<u8> MapNode::serializeBulk(int version,
|
Buffer<u8> MapNode::serializeBulk(int version,
|
||||||
const MapNode *nodes, u32 nodecount,
|
const MapNode *nodes, u32 nodecount,
|
||||||
u8 content_width, u8 params_width)
|
u8 content_width, u8 params_width, bool is_mono_block)
|
||||||
{
|
{
|
||||||
if (!ser_ver_supported_write(version))
|
if (!ser_ver_supported_write(version))
|
||||||
throw VersionMismatchException("ERROR: MapNode format not supported");
|
throw VersionMismatchException("ERROR: MapNode format not supported");
|
||||||
|
@ -601,14 +601,22 @@ Buffer<u8> MapNode::serializeBulk(int version,
|
||||||
|
|
||||||
// Writing to the buffer linearly is faster
|
// Writing to the buffer linearly is faster
|
||||||
u8 *p = &databuf[0];
|
u8 *p = &databuf[0];
|
||||||
|
if (is_mono_block) {
|
||||||
|
MapNode n = nodes[0];
|
||||||
|
for (u32 i = 0; i < nodecount; i++, p += 2)
|
||||||
|
writeU16(p, n.param0);
|
||||||
|
for (u32 i = 0; i < nodecount; i++, p++)
|
||||||
|
writeU8(p, n.param1);
|
||||||
|
for (u32 i = 0; i < nodecount; i++, p++)
|
||||||
|
writeU8(p, n.param2);
|
||||||
|
} else {
|
||||||
for (u32 i = 0; i < nodecount; i++, p += 2)
|
for (u32 i = 0; i < nodecount; i++, p += 2)
|
||||||
writeU16(p, nodes[i].param0);
|
writeU16(p, nodes[i].param0);
|
||||||
|
|
||||||
for (u32 i = 0; i < nodecount; i++, p++)
|
for (u32 i = 0; i < nodecount; i++, p++)
|
||||||
writeU8(p, nodes[i].param1);
|
writeU8(p, nodes[i].param1);
|
||||||
|
|
||||||
for (u32 i = 0; i < nodecount; i++, p++)
|
for (u32 i = 0; i < nodecount; i++, p++)
|
||||||
writeU8(p, nodes[i].param2);
|
writeU8(p, nodes[i].param2);
|
||||||
|
}
|
||||||
|
|
||||||
return databuf;
|
return databuf;
|
||||||
}
|
}
|
||||||
|
|
|
@ -305,9 +305,10 @@ struct alignas(u32) MapNode
|
||||||
// content_width = the number of bytes of content per node
|
// content_width = the number of bytes of content per node
|
||||||
// params_width = the number of bytes of params per node
|
// params_width = the number of bytes of params per node
|
||||||
// compressed = true to zlib-compress output
|
// compressed = true to zlib-compress output
|
||||||
|
// is_mono_block = if true, nodes is array of size 1
|
||||||
static Buffer<u8> serializeBulk(int version,
|
static Buffer<u8> serializeBulk(int version,
|
||||||
const MapNode *nodes, u32 nodecount,
|
const MapNode *nodes, u32 nodecount,
|
||||||
u8 content_width, u8 params_width);
|
u8 content_width, u8 params_width, bool is_mono_block = false);
|
||||||
static void deSerializeBulk(std::istream &is, int version,
|
static void deSerializeBulk(std::istream &is, int version,
|
||||||
MapNode *nodes, u32 nodecount,
|
MapNode *nodes, u32 nodecount,
|
||||||
u8 content_width, u8 params_width);
|
u8 content_width, u8 params_width);
|
||||||
|
|
|
@ -319,6 +319,7 @@ public:
|
||||||
std::list<std::string> *log);
|
std::list<std::string> *log);
|
||||||
|
|
||||||
// IGameDef interface
|
// IGameDef interface
|
||||||
|
bool isClient() override { return false; }
|
||||||
// Under envlock
|
// Under envlock
|
||||||
virtual IItemDefManager* getItemDefManager();
|
virtual IItemDefManager* getItemDefManager();
|
||||||
virtual const NodeDefManager* getNodeDefManager();
|
virtual const NodeDefManager* getNodeDefManager();
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
#include "serialization.h"
|
#include "serialization.h"
|
||||||
#include "noise.h"
|
#include "noise.h"
|
||||||
#include "inventory.h"
|
#include "inventory.h"
|
||||||
|
#include "voxel.h"
|
||||||
|
|
||||||
class TestMapBlock : public TestBase
|
class TestMapBlock : public TestBase
|
||||||
{
|
{
|
||||||
|
@ -33,6 +34,9 @@ public:
|
||||||
|
|
||||||
// Tests loading a non-standard MapBlock
|
// Tests loading a non-standard MapBlock
|
||||||
void testLoadNonStd(IGameDef *gamedef);
|
void testLoadNonStd(IGameDef *gamedef);
|
||||||
|
|
||||||
|
// Tests blocks with a single recurring node
|
||||||
|
void testMonoblock(IGameDef *gamedef);
|
||||||
};
|
};
|
||||||
|
|
||||||
static TestMapBlock g_test_instance;
|
static TestMapBlock g_test_instance;
|
||||||
|
@ -45,10 +49,110 @@ void TestMapBlock::runTests(IGameDef *gamedef)
|
||||||
TEST(testLoad29, gamedef);
|
TEST(testLoad29, gamedef);
|
||||||
TEST(testLoad20, gamedef);
|
TEST(testLoad20, gamedef);
|
||||||
TEST(testLoadNonStd, gamedef);
|
TEST(testLoadNonStd, gamedef);
|
||||||
|
TEST(testMonoblock, gamedef);
|
||||||
}
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
void TestMapBlock::testMonoblock(IGameDef *gamedef)
|
||||||
|
{
|
||||||
|
MapBlock block({}, gamedef);
|
||||||
|
UASSERT(!block.m_is_mono_block);
|
||||||
|
|
||||||
|
// make the array is expanded
|
||||||
|
block.expandNodesIfNeeded();
|
||||||
|
UASSERT(std::all_of(block.data, block.data + MapBlock::nodecount, [](MapNode &n) { return n == MapNode(CONTENT_IGNORE); }));
|
||||||
|
|
||||||
|
// covert to monoblock
|
||||||
|
block.tryShrinkNodes();
|
||||||
|
UASSERT(block.m_is_mono_block);
|
||||||
|
UASSERT(block.data[0].param0 == CONTENT_IGNORE);
|
||||||
|
|
||||||
|
block.data[0] = MapNode(CONTENT_AIR);
|
||||||
|
UASSERT(block.m_is_mono_block);
|
||||||
|
UASSERT(block.data[0].param0 == CONTENT_AIR);
|
||||||
|
|
||||||
|
block.expandNodesIfNeeded();
|
||||||
|
UASSERT(!block.m_is_mono_block);
|
||||||
|
UASSERT(std::all_of(block.data, block.data + MapBlock::nodecount, [](MapNode &n) { return n == MapNode(CONTENT_AIR); }));
|
||||||
|
|
||||||
|
// covert back to mono block
|
||||||
|
block.tryShrinkNodes();
|
||||||
|
UASSERT(block.m_is_mono_block);
|
||||||
|
|
||||||
|
// deconvert explicitly
|
||||||
|
block.expandNodesIfNeeded();
|
||||||
|
UASSERT(!block.m_is_mono_block);
|
||||||
|
|
||||||
|
// covert back to mono block
|
||||||
|
block.tryShrinkNodes();
|
||||||
|
UASSERT(block.m_is_mono_block);
|
||||||
|
|
||||||
|
static_assert(CONTENT_AIR != 42);
|
||||||
|
// set a node, should deconvert the block
|
||||||
|
block.setNode(5,5,5, MapNode(42));
|
||||||
|
UASSERT(!block.m_is_mono_block);
|
||||||
|
|
||||||
|
// cannot covert to mono block
|
||||||
|
block.tryShrinkNodes();
|
||||||
|
UASSERT(!block.m_is_mono_block);
|
||||||
|
|
||||||
|
// set all nodes to 42
|
||||||
|
for (size_t i = 0; i < MapBlock::nodecount; ++i) {
|
||||||
|
block.data[i] = MapNode(42);
|
||||||
|
}
|
||||||
|
|
||||||
|
// can covert to mono block
|
||||||
|
block.tryShrinkNodes();
|
||||||
|
UASSERT(block.m_is_mono_block);
|
||||||
|
UASSERT(block.data[0].param0 == 42);
|
||||||
|
|
||||||
|
VoxelManipulator vmm;
|
||||||
|
v3s16 data_size(MAP_BLOCKSIZE, MAP_BLOCKSIZE, MAP_BLOCKSIZE);
|
||||||
|
vmm.addArea(VoxelArea(block.getPosRelative(), block.getPosRelative() + data_size + v3s16(1,1,1)));
|
||||||
|
block.copyTo(vmm);
|
||||||
|
UASSERT(block.m_is_mono_block);
|
||||||
|
UASSERT(vmm.getNode({5,5,5}).param0 == 42);
|
||||||
|
|
||||||
|
block.setNode(5,5,5,MapNode(23));
|
||||||
|
|
||||||
|
block.copyFrom(vmm);
|
||||||
|
UASSERT(block.m_is_mono_block);
|
||||||
|
UASSERT(block.data[0].param0 == 42);
|
||||||
|
|
||||||
|
vmm.setNode({5,5,5}, MapNode(23));
|
||||||
|
block.copyFrom(vmm);
|
||||||
|
UASSERT(!block.m_is_mono_block);
|
||||||
|
|
||||||
|
vmm.setNode({5,5,5}, MapNode(42,1,0));
|
||||||
|
block.copyFrom(vmm);
|
||||||
|
UASSERT(!block.m_is_mono_block);
|
||||||
|
|
||||||
|
vmm.setNode({5,5,5}, MapNode(42,0,1));
|
||||||
|
block.copyFrom(vmm);
|
||||||
|
UASSERT(!block.m_is_mono_block);
|
||||||
|
|
||||||
|
vmm.setNode({5,5,5}, MapNode(42));
|
||||||
|
block.copyFrom(vmm);
|
||||||
|
UASSERT(block.m_is_mono_block);
|
||||||
|
|
||||||
|
block.setNode(5,5,5,MapNode(23));
|
||||||
|
block.tryShrinkNodes();
|
||||||
|
UASSERT(!block.m_is_mono_block);
|
||||||
|
|
||||||
|
block.setNode(5,5,5,MapNode(42, 1, 0));
|
||||||
|
block.tryShrinkNodes();
|
||||||
|
UASSERT(!block.m_is_mono_block);
|
||||||
|
|
||||||
|
block.setNode(5,5,5,MapNode(42, 0, 1));
|
||||||
|
block.tryShrinkNodes();
|
||||||
|
UASSERT(!block.m_is_mono_block);
|
||||||
|
|
||||||
|
block.setNode(5,5,5,MapNode(42));
|
||||||
|
block.tryShrinkNodes();
|
||||||
|
UASSERT(block.m_is_mono_block);
|
||||||
|
}
|
||||||
|
|
||||||
void TestMapBlock::testSaveLoad(IGameDef *gamedef, const u8 version)
|
void TestMapBlock::testSaveLoad(IGameDef *gamedef, const u8 version)
|
||||||
{
|
{
|
||||||
// Use the bottom node ids for this test
|
// Use the bottom node ids for this test
|
||||||
|
|
|
@ -177,7 +177,7 @@ void VoxelManipulator::addArea(const VoxelArea &area)
|
||||||
delete[] old_flags;
|
delete[] old_flags;
|
||||||
}
|
}
|
||||||
|
|
||||||
void VoxelManipulator::copyFrom(MapNode *src, const VoxelArea& src_area,
|
void VoxelManipulator::copyFrom(MapNode *src, bool is_mono_block, const VoxelArea& src_area,
|
||||||
v3s16 from_pos, v3s16 to_pos, const v3s16 &size)
|
v3s16 from_pos, v3s16 to_pos, const v3s16 &size)
|
||||||
{
|
{
|
||||||
/* The reason for this optimised code is that we're a member function
|
/* The reason for this optimised code is that we're a member function
|
||||||
|
@ -216,8 +216,12 @@ void VoxelManipulator::copyFrom(MapNode *src, const VoxelArea& src_area,
|
||||||
|
|
||||||
for (s16 z = 0; z < size.Z; z++) {
|
for (s16 z = 0; z < size.Z; z++) {
|
||||||
for (s16 y = 0; y < size.Y; y++) {
|
for (s16 y = 0; y < size.Y; y++) {
|
||||||
memcpy(&m_data[i_local], &src[i_src], size.X * sizeof(*m_data));
|
if (is_mono_block) {
|
||||||
memset(&m_flags[i_local], 0, size.X);
|
std::fill_n(m_data + i_local, size.X, src[0]);
|
||||||
|
} else {
|
||||||
|
std::copy_n(src + i_src, size.X, m_data + i_local);
|
||||||
|
}
|
||||||
|
std::fill_n(m_flags + i_local, size.X, 0);
|
||||||
i_src += src_step;
|
i_src += src_step;
|
||||||
i_local += dest_step;
|
i_local += dest_step;
|
||||||
}
|
}
|
||||||
|
|
|
@ -482,7 +482,7 @@ public:
|
||||||
Copy data and set flags to 0
|
Copy data and set flags to 0
|
||||||
dst_area.getExtent() <= src_area.getExtent()
|
dst_area.getExtent() <= src_area.getExtent()
|
||||||
*/
|
*/
|
||||||
void copyFrom(MapNode *src, const VoxelArea& src_area,
|
void copyFrom(MapNode *src, bool is_mono_block, const VoxelArea& src_area,
|
||||||
v3s16 from_pos, v3s16 to_pos, const v3s16 &size);
|
v3s16 from_pos, v3s16 to_pos, const v3s16 &size);
|
||||||
|
|
||||||
// Copy data
|
// Copy data
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue