From 139db66901a76dc95747335145606839aae746bb Mon Sep 17 00:00:00 2001 From: x2048 Date: Mon, 9 Jan 2023 21:40:08 +0100 Subject: [PATCH] Remove mapblock cache for mesh generation. (#13124) Reduces memory consumption and improves performance --- src/client/client.cpp | 10 +- src/client/mesh_generator_thread.cpp | 213 ++++++++------------------- src/client/mesh_generator_thread.h | 20 +-- src/network/clientpackethandler.cpp | 3 +- 4 files changed, 74 insertions(+), 172 deletions(-) diff --git a/src/client/client.cpp b/src/client/client.cpp index 0f96904b7..9a7e1498f 100644 --- a/src/client/client.cpp +++ b/src/client/client.cpp @@ -339,8 +339,12 @@ Client::~Client() m_mesh_update_manager.wait(); MeshUpdateResult r; - while (m_mesh_update_manager.getNextResult(r)) + while (m_mesh_update_manager.getNextResult(r)) { + for (auto block : r.map_blocks) + if (block) + block->refDrop(); delete r.mesh; + } delete m_inventory_from_server; @@ -595,6 +599,10 @@ void Client::step(float dtime) blocks_to_ack.emplace_back(r.p); } + + for (auto block : r.map_blocks) + if (block) + block->refDrop(); } if (blocks_to_ack.size() > 0) { // Acknowledge block(s) diff --git a/src/client/mesh_generator_thread.cpp b/src/client/mesh_generator_thread.cpp index 345f44eb9..adea057d6 100644 --- a/src/client/mesh_generator_thread.cpp +++ b/src/client/mesh_generator_thread.cpp @@ -25,17 +25,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "map.h" #include "util/directiontables.h" -/* - CachedMapBlockData -*/ - -CachedMapBlockData::~CachedMapBlockData() -{ - assert(refcount_from_queue == 0); - - delete[] data; -} - /* QueuedMeshUpdate */ @@ -50,8 +39,7 @@ QueuedMeshUpdate::~QueuedMeshUpdate() */ MeshUpdateQueue::MeshUpdateQueue(Client *client): - m_client(client), - m_next_cache_cleanup(0) + m_client(client) { m_cache_enable_shaders = g_settings->getBool("enable_shaders"); m_cache_smooth_lighting = g_settings->getBool("smooth_lighting"); @@ -62,39 +50,22 @@ MeshUpdateQueue::~MeshUpdateQueue() { MutexAutoLock lock(m_mutex); - for (auto &i : m_cache) { - delete i.second; - } - for (QueuedMeshUpdate *q : m_queue) { + for (auto block : q->map_blocks) + if (block) + block->refDrop(); delete q; } } bool MeshUpdateQueue::addBlock(Map *map, v3s16 p, bool ack_block_to_server, bool urgent) { + MapBlock *main_block = map->getBlockNoCreateNoEx(p); + if (!main_block) + return false; + MutexAutoLock lock(m_mutex); - cleanupCache(); - - /* - Cache the block data (force-update the center block, don't update the - neighbors but get them if they aren't already cached) - */ - std::vector cached_blocks; - size_t cache_hit_counter = 0; - CachedMapBlockData *cached_block = cacheBlock(map, p, FORCE_UPDATE); - if (!cached_block->data) - return false; // nothing to update - cached_blocks.reserve(3*3*3); - cached_blocks.push_back(cached_block); - for (v3s16 dp : g_26dirs) - cached_blocks.push_back(cacheBlock(map, p + dp, - SKIP_UPDATE_IF_ALREADY_CACHED, - &cache_hit_counter)); - g_profiler->avg("MeshUpdateQueue: MapBlocks from cache [%]", - 100.0f * cache_hit_counter / cached_blocks.size()); - /* Mark the block as urgent if requested */ @@ -114,10 +85,34 @@ bool MeshUpdateQueue::addBlock(Map *map, v3s16 p, bool ack_block_to_server, bool q->crack_level = m_client->getCrackLevel(); q->crack_pos = m_client->getCrackPos(); q->urgent |= urgent; + for (std::size_t i = 1; i < q->map_blocks.size(); i++) { + if (!q->map_blocks[i]) { + MapBlock *block = map->getBlockNoCreateNoEx(q->p + g_26dirs[i - 1]); + if (block) { + block->refGrab(); + q->map_blocks[i] = block; + } + } + } return true; } } + /* + Cache the block data (force-update the center block, don't update the + neighbors but get them if they aren't already cached) + */ + std::vector cached_blocks; + cached_blocks.reserve(3*3*3); + cached_blocks.push_back(main_block); + main_block->refGrab(); + for (v3s16 dp : g_26dirs) { + MapBlock *block = map->getBlockNoCreateNoEx(p + dp); + cached_blocks.push_back(block); + if (block) + block->refGrab(); + } + /* Add the block */ @@ -127,12 +122,9 @@ bool MeshUpdateQueue::addBlock(Map *map, v3s16 p, bool ack_block_to_server, bool q->crack_level = m_client->getCrackLevel(); q->crack_pos = m_client->getCrackPos(); q->urgent = urgent; + q->map_blocks = std::move(cached_blocks); m_queue.push_back(q); - // This queue entry is a new reference to the cached blocks - for (CachedMapBlockData *cached_block : cached_blocks) { - cached_block->refcount_from_queue++; - } return true; } @@ -140,24 +132,31 @@ bool MeshUpdateQueue::addBlock(Map *map, v3s16 p, bool ack_block_to_server, bool // Returns NULL if queue is empty QueuedMeshUpdate *MeshUpdateQueue::pop() { - MutexAutoLock lock(m_mutex); + QueuedMeshUpdate *result = NULL; + { + MutexAutoLock lock(m_mutex); - bool must_be_urgent = !m_urgents.empty(); - for (std::vector::iterator i = m_queue.begin(); - i != m_queue.end(); ++i) { - QueuedMeshUpdate *q = *i; - if (must_be_urgent && m_urgents.count(q->p) == 0) - continue; - // Make sure no two threads are processing the same mapblock, as that causes racing conditions - if (m_inflight_blocks.find(q->p) != m_inflight_blocks.end()) - continue; - m_queue.erase(i); - m_urgents.erase(q->p); - m_inflight_blocks.insert(q->p); - fillDataFromMapBlockCache(q); - return q; + bool must_be_urgent = !m_urgents.empty(); + for (std::vector::iterator i = m_queue.begin(); + i != m_queue.end(); ++i) { + QueuedMeshUpdate *q = *i; + if (must_be_urgent && m_urgents.count(q->p) == 0) + continue; + // Make sure no two threads are processing the same mapblock, as that causes racing conditions + if (m_inflight_blocks.find(q->p) != m_inflight_blocks.end()) + continue; + m_queue.erase(i); + m_urgents.erase(q->p); + m_inflight_blocks.insert(q->p); + result = q; + break; + } } - return NULL; + + if (result) + fillDataFromMapBlocks(result); + + return result; } void MeshUpdateQueue::done(v3s16 pos) @@ -166,113 +165,22 @@ void MeshUpdateQueue::done(v3s16 pos) m_inflight_blocks.erase(pos); } -CachedMapBlockData* MeshUpdateQueue::cacheBlock(Map *map, v3s16 p, UpdateMode mode, - size_t *cache_hit_counter) -{ - CachedMapBlockData *cached_block = nullptr; - auto it = m_cache.find(p); - if (it != m_cache.end()) { - cached_block = it->second; - - if (mode == SKIP_UPDATE_IF_ALREADY_CACHED) { - if (cache_hit_counter) - (*cache_hit_counter)++; - return cached_block; - } - } - - if (!cached_block) { - // Not yet in cache - cached_block = new CachedMapBlockData(); - m_cache[p] = cached_block; - } - - MapBlock *b = map->getBlockNoCreateNoEx(p); - if (b) { - if (!cached_block->data) - cached_block->data = - new MapNode[MAP_BLOCKSIZE * MAP_BLOCKSIZE * MAP_BLOCKSIZE]; - memcpy(cached_block->data, b->getData(), - MAP_BLOCKSIZE * MAP_BLOCKSIZE * MAP_BLOCKSIZE * sizeof(MapNode)); - } else { - delete[] cached_block->data; - cached_block->data = nullptr; - } - return cached_block; -} - -CachedMapBlockData* MeshUpdateQueue::getCachedBlock(const v3s16 &p) -{ - auto it = m_cache.find(p); - if (it != m_cache.end()) { - return it->second; - } - return NULL; -} - -void MeshUpdateQueue::fillDataFromMapBlockCache(QueuedMeshUpdate *q) +void MeshUpdateQueue::fillDataFromMapBlocks(QueuedMeshUpdate *q) { MeshMakeData *data = new MeshMakeData(m_client, m_cache_enable_shaders); q->data = data; data->fillBlockDataBegin(q->p); - std::time_t t_now = std::time(0); - - // Collect data for 3*3*3 blocks from cache - for (v3s16 dp : g_27dirs) { - v3s16 p = q->p + dp; - CachedMapBlockData *cached_block = getCachedBlock(p); - if (cached_block) { - cached_block->refcount_from_queue--; - cached_block->last_used_timestamp = t_now; - if (cached_block->data) - data->fillBlockData(dp, cached_block->data); - } - } + for (MapBlock *block : q->map_blocks) + if (block) + data->fillBlockData(block->getPos() - q->p, block->getData()); data->setCrack(q->crack_level, q->crack_pos); data->setSmoothLighting(m_cache_smooth_lighting); } -void MeshUpdateQueue::cleanupCache() -{ - const int mapblock_kB = MAP_BLOCKSIZE * MAP_BLOCKSIZE * MAP_BLOCKSIZE * - sizeof(MapNode) / 1000; - g_profiler->avg("MeshUpdateQueue MapBlock cache size kB", - mapblock_kB * m_cache.size()); - - // Iterating the entire cache can get pretty expensive so don't do it too often - { - constexpr int cleanup_interval = 250; - const u64 now = porting::getTimeMs(); - if (m_next_cache_cleanup > now) - return; - m_next_cache_cleanup = now + cleanup_interval; - } - - // The cache size is kept roughly below cache_soft_max_size, not letting - // anything get older than cache_seconds_max or deleted before 2 seconds. - const int cache_seconds_max = 10; - const int cache_soft_max_size = m_meshgen_block_cache_size * 1000 / mapblock_kB; - int cache_seconds = MYMAX(2, cache_seconds_max - - m_cache.size() / (cache_soft_max_size / cache_seconds_max)); - - int t_now = time(0); - - for (auto it = m_cache.begin(); it != m_cache.end(); ) { - CachedMapBlockData *cached_block = it->second; - if (cached_block->refcount_from_queue == 0 && - cached_block->last_used_timestamp < t_now - cache_seconds) { - it = m_cache.erase(it); - delete cached_block; - } else { - ++it; - } - } -} - /* MeshUpdateWorkerThread */ @@ -302,6 +210,7 @@ void MeshUpdateWorkerThread::doUpdate() r.solid_sides = get_solid_sides(q->data); r.ack_block_to_server = q->ack_block_to_server; r.urgent = q->urgent; + r.map_blocks = q->map_blocks; m_manager->putResult(r); m_queue_in->done(q->p); diff --git a/src/client/mesh_generator_thread.h b/src/client/mesh_generator_thread.h index e37640a7a..cfcb1df09 100644 --- a/src/client/mesh_generator_thread.h +++ b/src/client/mesh_generator_thread.h @@ -29,17 +29,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include -struct CachedMapBlockData -{ - v3s16 p = v3s16(-1337, -1337, -1337); - MapNode *data = nullptr; // A copy of the MapBlock's data member - int refcount_from_queue = 0; - std::time_t last_used_timestamp = std::time(0); - - CachedMapBlockData() = default; - ~CachedMapBlockData(); -}; - struct QueuedMeshUpdate { v3s16 p = v3s16(-1337, -1337, -1337); @@ -47,6 +36,7 @@ struct QueuedMeshUpdate int crack_level = -1; v3s16 crack_pos; MeshMakeData *data = nullptr; // This is generated in MeshUpdateQueue::pop() + std::vector map_blocks; bool urgent = false; QueuedMeshUpdate() = default; @@ -90,9 +80,7 @@ private: Client *m_client; std::vector m_queue; std::unordered_set m_urgents; - std::unordered_map m_cache; std::unordered_set m_inflight_blocks; - u64 m_next_cache_cleanup; // milliseconds std::mutex m_mutex; // TODO: Add callback to update these when g_settings changes @@ -100,10 +88,7 @@ private: bool m_cache_smooth_lighting; int m_meshgen_block_cache_size; - CachedMapBlockData *cacheBlock(Map *map, v3s16 p, UpdateMode mode, - size_t *cache_hit_counter = NULL); - CachedMapBlockData *getCachedBlock(const v3s16 &p); - void fillDataFromMapBlockCache(QueuedMeshUpdate *q); + void fillDataFromMapBlocks(QueuedMeshUpdate *q); void cleanupCache(); }; @@ -114,6 +99,7 @@ struct MeshUpdateResult u8 solid_sides = 0; bool ack_block_to_server = false; bool urgent = false; + std::vector map_blocks; MeshUpdateResult() = default; }; diff --git a/src/network/clientpackethandler.cpp b/src/network/clientpackethandler.cpp index 0e6256356..8a323872a 100644 --- a/src/network/clientpackethandler.cpp +++ b/src/network/clientpackethandler.cpp @@ -322,10 +322,9 @@ void Client::handleCommand_BlockData(NetworkPacket* pkt) /* Create a new block */ - block = new MapBlock(&m_env.getMap(), p, this); + block = sector->createBlankBlock(p.Y); block->deSerialize(istr, m_server_ser_ver, false); block->deSerializeNetworkSpecific(istr); - sector->insertBlock(block); } if (m_localdb) {