1
0
Fork 0
mirror of https://github.com/luanti-org/luanti.git synced 2025-06-27 16:36:03 +00:00

Refactor MMVManip to get rid of m_loaded_blocks

This commit is contained in:
sfan5 2025-05-25 14:50:13 +02:00
parent 41651c7317
commit 6274a8dec4
5 changed files with 123 additions and 38 deletions

View file

@ -772,6 +772,11 @@ void MMVManip::initialEmerge(v3s16 p_min, v3s16 p_max, bool load_if_inexistent)
infostream<<std::endl; infostream<<std::endl;
} }
std::map<v3s16, bool> had_blocks;
// we can skip this calculation if the areas are disjoint
if (!m_area.intersect(block_area_nodes).hasEmptyExtent())
had_blocks = getCoveredBlocks();
const bool all_new = m_area.hasEmptyExtent(); const bool all_new = m_area.hasEmptyExtent();
addArea(block_area_nodes); addArea(block_area_nodes);
@ -779,12 +784,12 @@ void MMVManip::initialEmerge(v3s16 p_min, v3s16 p_max, bool load_if_inexistent)
for(s32 y=p_min.Y; y<=p_max.Y; y++) for(s32 y=p_min.Y; y<=p_max.Y; y++)
for(s32 x=p_min.X; x<=p_max.X; x++) for(s32 x=p_min.X; x<=p_max.X; x++)
{ {
u8 flags = 0;
MapBlock *block;
v3s16 p(x,y,z); v3s16 p(x,y,z);
if (m_loaded_blocks.count(p) > 0) // if this block was already in the vmanip and it has data, skip
if (auto it = had_blocks.find(p); it != had_blocks.end() && it->second)
continue; continue;
MapBlock *block;
bool block_data_inexistent = false; bool block_data_inexistent = false;
{ {
TimeTaker timer2("emerge load", &emerge_load_time); TimeTaker timer2("emerge load", &emerge_load_time);
@ -803,21 +808,53 @@ void MMVManip::initialEmerge(v3s16 p_min, v3s16 p_max, bool load_if_inexistent)
assert(block); assert(block);
block->copyTo(*this); block->copyTo(*this);
} else { } else {
flags |= VMANIP_BLOCK_DATA_INEXIST;
// Mark area inexistent // Mark area inexistent
VoxelArea a(p*MAP_BLOCKSIZE, (p+1)*MAP_BLOCKSIZE-v3s16(1,1,1)); VoxelArea a(p*MAP_BLOCKSIZE, (p+1)*MAP_BLOCKSIZE-v3s16(1,1,1));
setFlags(a, VOXELFLAG_NO_DATA); setFlags(a, VOXELFLAG_NO_DATA);
} }
} }
m_loaded_blocks[p] = flags;
} }
if (all_new) if (all_new)
m_is_dirty = false; m_is_dirty = false;
} }
std::map<v3s16, bool> MMVManip::getCoveredBlocks() const
{
std::map<v3s16, bool> ret;
if (m_area.hasEmptyExtent())
return ret;
// Figure out if *any* node in this block has data according to m_flags
const auto &check_block = [this] (v3s16 bp) -> bool {
v3s16 pmin = bp * MAP_BLOCKSIZE;
v3s16 pmax = pmin + v3s16(MAP_BLOCKSIZE-1);
for(s16 z=pmin.Z; z<=pmax.Z; z++)
for(s16 y=pmin.Y; y<=pmax.Y; y++)
for(s16 x=pmin.X; x<=pmax.X; x++) {
if (!(m_flags[m_area.index(x,y,z)] & VOXELFLAG_NO_DATA))
return true;
}
return false;
};
v3s16 bpmin = getNodeBlockPos(m_area.MinEdge);
v3s16 bpmax = getNodeBlockPos(m_area.MaxEdge);
if (bpmin * MAP_BLOCKSIZE != m_area.MinEdge)
throw BaseException("MMVManip not block-aligned");
if ((bpmax+1) * MAP_BLOCKSIZE - v3s16(1) != m_area.MaxEdge)
throw BaseException("MMVManip not block-aligned");
for(s16 z=bpmin.Z; z<=bpmax.Z; z++)
for(s16 y=bpmin.Y; y<=bpmax.Y; y++)
for(s16 x=bpmin.X; x<=bpmax.X; x++) {
v3s16 bp(x,y,z);
ret[bp] = check_block(bp);
}
return ret;
}
void MMVManip::blitBackAll(std::map<v3s16, MapBlock*> *modified_blocks, void MMVManip::blitBackAll(std::map<v3s16, MapBlock*> *modified_blocks,
bool overwrite_generated) const bool overwrite_generated) const
{ {
@ -825,16 +862,14 @@ void MMVManip::blitBackAll(std::map<v3s16, MapBlock*> *modified_blocks,
return; return;
assert(m_map); assert(m_map);
/* // Copy all the blocks with data back to the map
Copy data of all blocks const auto loaded_blocks = getCoveredBlocks();
*/ for (auto &it : loaded_blocks) {
assert(!m_loaded_blocks.empty()); if (!it.second)
for (auto &loaded_block : m_loaded_blocks) { continue;
v3s16 p = loaded_block.first; v3s16 p = it.first;
MapBlock *block = m_map->getBlockNoCreateNoEx(p); MapBlock *block = m_map->getBlockNoCreateNoEx(p);
bool existed = !(loaded_block.second & VMANIP_BLOCK_DATA_INEXIST); if (!block || (!overwrite_generated && block->isGenerated()))
if (!existed || (block == NULL) ||
(!overwrite_generated && block->isGenerated()))
continue; continue;
block->copyFrom(*this); block->copyFrom(*this);
@ -860,11 +895,7 @@ MMVManip *MMVManip::clone() const
ret->m_flags = new u8[size]; ret->m_flags = new u8[size];
memcpy(ret->m_flags, m_flags, size * sizeof(u8)); memcpy(ret->m_flags, m_flags, size * sizeof(u8));
} }
ret->m_is_dirty = m_is_dirty; ret->m_is_dirty = m_is_dirty;
// Even if the copy is disconnected from a map object keep the information
// needed to write it back to one
ret->m_loaded_blocks = m_loaded_blocks;
return ret; return ret;
} }

View file

@ -307,16 +307,29 @@ public:
MMVManip(Map *map); MMVManip(Map *map);
virtual ~MMVManip() = default; virtual ~MMVManip() = default;
virtual void clear() /*
{ Loads specified area from map and *adds* it to the area already
VoxelManipulator::clear(); contained in the VManip.
m_loaded_blocks.clear(); */
}
void initialEmerge(v3s16 blockpos_min, v3s16 blockpos_max, void initialEmerge(v3s16 blockpos_min, v3s16 blockpos_max,
bool load_if_inexistent = true); bool load_if_inexistent = true);
// This is much faster with big chunks of generated data /**
Uses the flags array to determine which blocks the VManip covers,
and for which of them we have any data.
@warning requires VManip area to be block-aligned
@return map of blockpos -> any data?
*/
std::map<v3s16, bool> getCoveredBlocks() const;
/**
Writes data in VManip back to the map. Blocks without any data in the VManip
are skipped.
@note VOXELFLAG_NO_DATA is checked per-block, not per-node. So you need
to ensure that the relevant parts of m_data are initialized.
@param modified_blocks output array of touched blocks (optional)
@param overwrite_generated if false, blocks marked as generate in the map are not changed
*/
void blitBackAll(std::map<v3s16, MapBlock*> * modified_blocks, void blitBackAll(std::map<v3s16, MapBlock*> * modified_blocks,
bool overwrite_generated = true) const; bool overwrite_generated = true) const;
@ -339,13 +352,4 @@ protected:
// may be null // may be null
Map *m_map = nullptr; Map *m_map = nullptr;
/*
key = blockpos
value = flags describing the block
*/
std::map<v3s16, u8> m_loaded_blocks;
enum : u8 {
VMANIP_BLOCK_DATA_INEXIST = 1 << 0,
};
}; };

View file

@ -264,6 +264,10 @@ void TestVoxelArea::test_intersect()
UASSERT(v3.intersect(v1) == v1.intersect(v3)); UASSERT(v3.intersect(v1) == v1.intersect(v3));
UASSERT(v1.intersect(v4) == UASSERT(v1.intersect(v4) ==
VoxelArea({-10, -2, -10}, {10, 2, 10})); VoxelArea({-10, -2, -10}, {10, 2, 10}));
// edge cases
UASSERT(VoxelArea().intersect(v1).hasEmptyExtent());
UASSERT(v1.intersect(VoxelArea()).hasEmptyExtent());
} }
void TestVoxelArea::test_index_xyz_all_pos() void TestVoxelArea::test_index_xyz_all_pos()

View file

@ -22,6 +22,7 @@ public:
void testBasic(const NodeDefManager *nodedef); void testBasic(const NodeDefManager *nodedef);
void testEmerge(IGameDef *gamedef); void testEmerge(IGameDef *gamedef);
void testBlitBack(IGameDef *gamedef); void testBlitBack(IGameDef *gamedef);
void testBlitBack2(IGameDef *gamedef);
}; };
static TestVoxelManipulator g_test_instance; static TestVoxelManipulator g_test_instance;
@ -31,6 +32,7 @@ void TestVoxelManipulator::runTests(IGameDef *gamedef)
TEST(testBasic, gamedef->ndef()); TEST(testBasic, gamedef->ndef());
TEST(testEmerge, gamedef); TEST(testEmerge, gamedef);
TEST(testBlitBack, gamedef); TEST(testBlitBack, gamedef);
TEST(testBlitBack2, gamedef);
} }
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
@ -90,7 +92,7 @@ void TestVoxelManipulator::testEmerge(IGameDef *gamedef)
UASSERTEQ(auto, vm.getNodeNoExNoEmerge({0,bs+1,0}).getContent(), t_CONTENT_BRICK); UASSERTEQ(auto, vm.getNodeNoExNoEmerge({0,bs+1,0}).getContent(), t_CONTENT_BRICK);
// emerge out of bounds: should produce empty data // emerge out of bounds: should produce empty data
vm.initialEmerge({0,0,0}, {0,2,0}, false); vm.initialEmerge({0,2,0}, {0,2,0}, false);
UASSERTEQ(auto, vm.m_area.getExtent(), v3s32(bs,3*bs,bs)); UASSERTEQ(auto, vm.m_area.getExtent(), v3s32(bs,3*bs,bs));
UASSERTEQ(auto, vm.getNodeNoExNoEmerge({0,2*bs,0}).getContent(), CONTENT_IGNORE); UASSERTEQ(auto, vm.getNodeNoExNoEmerge({0,2*bs,0}).getContent(), CONTENT_IGNORE);
@ -133,3 +135,47 @@ void TestVoxelManipulator::testBlitBack(IGameDef *gamedef)
// ignore nodes are not written (is this an intentional feature?) // ignore nodes are not written (is this an intentional feature?)
UASSERTEQ(auto, map.getNode({2,2,2}).getContent(), CONTENT_AIR); UASSERTEQ(auto, map.getNode({2,2,2}).getContent(), CONTENT_AIR);
} }
void TestVoxelManipulator::testBlitBack2(IGameDef *gamedef)
{
constexpr int bs = MAP_BLOCKSIZE;
DummyMap map(gamedef, {0,0,0}, {1,1,1});
map.fill({0,0,0}, {1,1,1}, CONTENT_AIR);
// Create a vmanip "manually" without using initialEmerge
MMVManip vm(&map);
vm.addArea(VoxelArea({0,0,0}, v3s16(1,2,1) * bs - v3s16(1)));
// Lower block is initialized with ignore, upper with lava
for(s16 z=0; z<bs; z++)
for(s16 y=0; y<2*bs; y++)
for(s16 x=0; x<bs; x++) {
auto c = y >= bs ? t_CONTENT_LAVA : CONTENT_IGNORE;
vm.setNodeNoEmerge({x,y,z}, c);
}
// But pretend the upper block was not actually initialized
vm.setFlags(VoxelArea({0,bs,0}, v3s16(1,2,1) * bs - v3s16(1)), VOXELFLAG_NO_DATA);
// Add a node to the lower one
vm.setNodeNoEmerge({0,1,0}, t_CONTENT_TORCH);
// Verify covered blocks
{
auto cov = vm.getCoveredBlocks();
UASSERTEQ(size_t, cov.size(), 2);
auto it = cov.find({0,0,0});
UASSERT(it != cov.end() && it->second);
it = cov.find({0,1,0});
UASSERT(it != cov.end() && !it->second);
}
// Now blit it back
std::map<v3s16, MapBlock*> modified;
vm.blitBackAll(&modified);
// The lower block data should have been written
UASSERTEQ(size_t, modified.size(), 1);
UASSERTEQ(auto, modified.begin()->first, v3s16(0,0,0));
UASSERTEQ(auto, map.getNode({0,1,0}).getContent(), t_CONTENT_TORCH);
// The upper one should not!
UASSERTEQ(auto, map.getNode({0,bs,0}).getContent(), CONTENT_AIR);
}

View file

@ -469,7 +469,7 @@ public:
Control Control
*/ */
virtual void clear(); void clear();
void print(std::ostream &o, const NodeDefManager *nodemgr, void print(std::ostream &o, const NodeDefManager *nodemgr,
VoxelPrintMode mode=VOXELPRINT_MATERIAL) const; VoxelPrintMode mode=VOXELPRINT_MATERIAL) const;