1
0
Fork 0
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:
lhofhansl 2025-09-21 13:19:30 -07:00 committed by GitHub
parent afd681d013
commit 08b7870c79
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 245 additions and 47 deletions

View file

@ -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;

View file

@ -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; }

View file

@ -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;
}; };

View file

@ -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);

View file

@ -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

View file

@ -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;
} }

View file

@ -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);

View file

@ -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();

View file

@ -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

View file

@ -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;
} }

View file

@ -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