From: Perttu Ahola Date: Sat, 15 Apr 2017 07:55:52 +0000 (+0300) Subject: MeshUpdateQueue: Add a MapBlock cache that minimizes the amount of MapBlock copying... X-Git-Tag: 0.4.16~236 X-Git-Url: https://git.librecmc.org/?a=commitdiff_plain;h=04cc9de8f2fbcb11f133c88f02fc11504b3ea6f3;p=oweals%2Fminetest.git MeshUpdateQueue: Add a MapBlock cache that minimizes the amount of MapBlock copying done in the main thread Cache size is configurable by the meshgen_block_cache_size (default 20 MB). New profiler stats: - MeshUpdateQueue MapBlock cache hit % - MeshUpdateQueue MapBlock cache size kB Removes one type of stutter that was seen on the client when received MapBlocks were being handled. (the "MeshMakeData::fill" stutter) Kind of related to at least #5239 Originally preceded by these commits, now includes them: - Move the mesh generator thread into src/mesh_generator_thread.{cpp,h} - mesh_generator_thread.cpp: Update code style - MeshUpdateThread: Modify interface to house a different implementation: Actual functionality will be changed by next commits. - MeshMakeData: Add fillBlockData() interface (so that caller can fill in stuff from eg. a MapBlock cache) --- diff --git a/build/android/jni/Android.mk b/build/android/jni/Android.mk index fd104db06..2929eaba1 100644 --- a/build/android/jni/Android.mk +++ b/build/android/jni/Android.mk @@ -185,6 +185,7 @@ LOCAL_SRC_FILES := \ jni/src/mapnode.cpp \ jni/src/mapsector.cpp \ jni/src/mesh.cpp \ + jni/src/mesh_generator_thread.cpp \ jni/src/metadata.cpp \ jni/src/mg_biome.cpp \ jni/src/mg_decoration.cpp \ diff --git a/builtin/settingtypes.txt b/builtin/settingtypes.txt index 2c74d6c44..5dc48c00e 100644 --- a/builtin/settingtypes.txt +++ b/builtin/settingtypes.txt @@ -551,6 +551,11 @@ enable_mesh_cache (Mesh cache) bool false # down the rate of mesh updates, thus reducing jitter on slower clients. mesh_generation_interval (Mapblock mesh generation delay) int 0 0 50 +# Size of the MapBlock cache of the mesh generator. Increasing this will +# increase the cache hit %, reducing the data being copied from the main +# thread, thus reducing jitter. +meshgen_block_cache_size (Mapblock mesh generator's MapBlock cache size MB) int 20 0 1000 + # Enables minimap. enable_minimap (Minimap) bool true diff --git a/minetest.conf.example b/minetest.conf.example index d147ebfac..9b50a775d 100644 --- a/minetest.conf.example +++ b/minetest.conf.example @@ -644,6 +644,12 @@ # type: int min: 0 max: 50 # mesh_generation_interval = 0 +# Size of the MapBlock cache of the mesh generator. Increasing this will +# increase the cache hit %, reducing the data being copied from the main +# thread, thus reducing jitter. +# type: int min: 0 max: 1000 +# meshgen_block_cache_size = 20 + # Enables minimap. # type: bool # enable_minimap = true diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index edb291545..37f72a44d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -522,6 +522,7 @@ set(client_SRCS main.cpp mapblock_mesh.cpp mesh.cpp + mesh_generator_thread.cpp minimap.cpp particles.cpp shader.cpp diff --git a/src/client.cpp b/src/client.cpp index 5ca51bd9c..7b962cd94 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -50,147 +50,6 @@ with this program; if not, write to the Free Software Foundation, Inc., extern gui::IGUIEnvironment* guienv; -/* - QueuedMeshUpdate -*/ - -QueuedMeshUpdate::QueuedMeshUpdate(): - p(-1337,-1337,-1337), - data(NULL), - ack_block_to_server(false) -{ -} - -QueuedMeshUpdate::~QueuedMeshUpdate() -{ - if(data) - delete data; -} - -/* - MeshUpdateQueue -*/ - -MeshUpdateQueue::MeshUpdateQueue() -{ -} - -MeshUpdateQueue::~MeshUpdateQueue() -{ - MutexAutoLock lock(m_mutex); - - for(std::vector::iterator - i = m_queue.begin(); - i != m_queue.end(); ++i) - { - QueuedMeshUpdate *q = *i; - delete q; - } -} - -/* - peer_id=0 adds with nobody to send to -*/ -void MeshUpdateQueue::addBlock(v3s16 p, MeshMakeData *data, bool ack_block_to_server, bool urgent) -{ - DSTACK(FUNCTION_NAME); - - assert(data); // pre-condition - - MutexAutoLock lock(m_mutex); - - if(urgent) - m_urgents.insert(p); - - /* - Find if block is already in queue. - If it is, update the data and quit. - */ - for(std::vector::iterator - i = m_queue.begin(); - i != m_queue.end(); ++i) - { - QueuedMeshUpdate *q = *i; - if(q->p == p) - { - if(q->data) - delete q->data; - q->data = data; - if(ack_block_to_server) - q->ack_block_to_server = true; - return; - } - } - - /* - Add the block - */ - QueuedMeshUpdate *q = new QueuedMeshUpdate; - q->p = p; - q->data = data; - q->ack_block_to_server = ack_block_to_server; - m_queue.push_back(q); -} - -// Returned pointer must be deleted -// Returns NULL if queue is empty -QueuedMeshUpdate *MeshUpdateQueue::pop() -{ - 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; - m_queue.erase(i); - m_urgents.erase(q->p); - return q; - } - return NULL; -} - -/* - MeshUpdateThread -*/ - -MeshUpdateThread::MeshUpdateThread() : UpdateThread("Mesh") -{ - m_generation_interval = g_settings->getU16("mesh_generation_interval"); - m_generation_interval = rangelim(m_generation_interval, 0, 50); -} - -void MeshUpdateThread::enqueueUpdate(v3s16 p, MeshMakeData *data, - bool ack_block_to_server, bool urgent) -{ - m_queue_in.addBlock(p, data, ack_block_to_server, urgent); - deferUpdate(); -} - -void MeshUpdateThread::doUpdate() -{ - QueuedMeshUpdate *q; - while ((q = m_queue_in.pop())) { - if (m_generation_interval) - sleep_ms(m_generation_interval); - ScopeProfiler sp(g_profiler, "Client: Mesh making"); - - MapBlockMesh *mesh_new = new MapBlockMesh(q->data, m_camera_offset); - - MeshUpdateResult r; - r.p = q->p; - r.mesh = mesh_new; - r.ack_block_to_server = q->ack_block_to_server; - - m_queue_out.push_back(r); - - delete q; - } -} - /* Client */ @@ -220,7 +79,7 @@ Client::Client( m_nodedef(nodedef), m_sound(sound), m_event(event), - m_mesh_update_thread(), + m_mesh_update_thread(this), m_env( new ClientMap(this, control, device->getSceneManager()->getRootSceneNode(), @@ -269,12 +128,6 @@ Client::Client( m_minimap = new Minimap(device, this); m_cache_save_interval = g_settings->getU16("server_map_save_interval"); - m_cache_smooth_lighting = g_settings->getBool("smooth_lighting"); - m_cache_enable_shaders = g_settings->getBool("enable_shaders"); - m_cache_use_tangent_vertices = m_cache_enable_shaders && ( - g_settings->getBool("enable_bumpmapping") || - g_settings->getBool("enable_parallax_occlusion")); - m_modding_enabled = g_settings->getBool("enable_client_modding"); m_script = new ClientScripting(this); m_env.setScript(m_script); @@ -1605,6 +1458,11 @@ int Client::getCrackLevel() return m_crack_level; } +v3s16 Client::getCrackPos() +{ + return m_crack_pos; +} + void Client::setCrack(int level, v3s16 pos) { int old_crack_level = m_crack_level; @@ -1670,28 +1528,14 @@ void Client::typeChatMessage(const std::wstring &message) void Client::addUpdateMeshTask(v3s16 p, bool ack_to_server, bool urgent) { + // Check if the block exists to begin with. In the case when a non-existing + // neighbor is automatically added, it may not. In that case we don't want + // to tell the mesh update thread about it. MapBlock *b = m_env.getMap().getBlockNoCreateNoEx(p); - if(b == NULL) + if (b == NULL) return; - /* - Create a task to update the mesh of the block - */ - - MeshMakeData *data = new MeshMakeData(this, m_cache_enable_shaders, - m_cache_use_tangent_vertices); - - { - //TimeTaker timer("data fill"); - // Release: ~0ms - // Debug: 1-6ms, avg=2ms - data->fill(b); - data->setCrack(m_crack_level, m_crack_pos); - data->setSmoothLighting(m_cache_smooth_lighting); - } - - // Add task to queue - m_mesh_update_thread.enqueueUpdate(p, data, ack_to_server, urgent); + m_mesh_update_thread.updateBlock(&m_env.getMap(), p, ack_to_server, urgent); } void Client::addUpdateMeshTaskWithEdge(v3s16 blockpos, bool ack_to_server, bool urgent) diff --git a/src/client.h b/src/client.h index e565acd93..e7fcb597d 100644 --- a/src/client.h +++ b/src/client.h @@ -36,6 +36,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "particles.h" #include "mapnode.h" #include "tileanimation.h" +#include "mesh_generator_thread.h" struct MeshMakeData; class MapBlockMesh; @@ -54,88 +55,12 @@ struct MinimapMapblock; class Camera; class NetworkPacket; -struct QueuedMeshUpdate -{ - v3s16 p; - MeshMakeData *data; - bool ack_block_to_server; - - QueuedMeshUpdate(); - ~QueuedMeshUpdate(); -}; - enum LocalClientState { LC_Created, LC_Init, LC_Ready }; -/* - A thread-safe queue of mesh update tasks -*/ -class MeshUpdateQueue -{ -public: - MeshUpdateQueue(); - - ~MeshUpdateQueue(); - - /* - peer_id=0 adds with nobody to send to - */ - void addBlock(v3s16 p, MeshMakeData *data, - bool ack_block_to_server, bool urgent); - - // Returned pointer must be deleted - // Returns NULL if queue is empty - QueuedMeshUpdate * pop(); - - u32 size() - { - MutexAutoLock lock(m_mutex); - return m_queue.size(); - } - -private: - std::vector m_queue; - std::set m_urgents; - Mutex m_mutex; -}; - -struct MeshUpdateResult -{ - v3s16 p; - MapBlockMesh *mesh; - bool ack_block_to_server; - - MeshUpdateResult(): - p(-1338,-1338,-1338), - mesh(NULL), - ack_block_to_server(false) - { - } -}; - -class MeshUpdateThread : public UpdateThread -{ -private: - MeshUpdateQueue m_queue_in; - int m_generation_interval; - -protected: - virtual void doUpdate(); - -public: - - MeshUpdateThread(); - - void enqueueUpdate(v3s16 p, MeshMakeData *data, - bool ack_block_to_server, bool urgent); - MutexedQueue m_queue_out; - - v3s16 m_camera_offset; -}; - enum ClientEventType { CE_NONE, @@ -471,6 +396,7 @@ public: float getAnimationTime(); int getCrackLevel(); + v3s16 getCrackPos(); void setCrack(int level, v3s16 pos); u16 getHP(); @@ -726,11 +652,6 @@ private: IntervalLimiter m_localdb_save_interval; u16 m_cache_save_interval; - // TODO: Add callback to update these when g_settings changes - bool m_cache_smooth_lighting; - bool m_cache_enable_shaders; - bool m_cache_use_tangent_vertices; - ClientScripting *m_script; bool m_modding_enabled; UNORDERED_MAP m_mod_storages; diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index c6ced5931..573b5e2d8 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -39,6 +39,7 @@ void set_default_settings(Settings *settings) settings->setDefault("sound_volume", "0.8"); settings->setDefault("enable_mesh_cache", "false"); settings->setDefault("mesh_generation_interval", "0"); + settings->setDefault("meshgen_block_cache_size", "20"); settings->setDefault("enable_vbo", "true"); settings->setDefault("free_move", "false"); settings->setDefault("fast_move", "false"); diff --git a/src/mapblock.h b/src/mapblock.h index 7ff613fe8..8816dc817 100644 --- a/src/mapblock.h +++ b/src/mapblock.h @@ -154,6 +154,11 @@ public: raiseModified(MOD_STATE_WRITE_NEEDED, MOD_REASON_REALLOCATE); } + MapNode* getData() + { + return data; + } + //// //// Modification tracking methods //// diff --git a/src/mapblock_mesh.cpp b/src/mapblock_mesh.cpp index eddb061b4..933dfc32a 100644 --- a/src/mapblock_mesh.cpp +++ b/src/mapblock_mesh.cpp @@ -48,49 +48,43 @@ MeshMakeData::MeshMakeData(Client *client, bool use_shaders, m_use_tangent_vertices(use_tangent_vertices) {} -void MeshMakeData::fill(MapBlock *block) +void MeshMakeData::fillBlockDataBegin(const v3s16 &blockpos) { - m_blockpos = block->getPos(); + m_blockpos = blockpos; v3s16 blockpos_nodes = m_blockpos*MAP_BLOCKSIZE; - /* - Copy data - */ - - // Allocate this block + neighbors m_vmanip.clear(); VoxelArea voxel_area(blockpos_nodes - v3s16(1,1,1) * MAP_BLOCKSIZE, blockpos_nodes + v3s16(1,1,1) * MAP_BLOCKSIZE*2-v3s16(1,1,1)); m_vmanip.addArea(voxel_area); +} - { - //TimeTaker timer("copy central block data"); - // 0ms +void MeshMakeData::fillBlockData(const v3s16 &block_offset, MapNode *data) +{ + v3s16 data_size(MAP_BLOCKSIZE, MAP_BLOCKSIZE, MAP_BLOCKSIZE); + VoxelArea data_area(v3s16(0,0,0), data_size - v3s16(1,1,1)); - // Copy our data - block->copyTo(m_vmanip); - } - { - //TimeTaker timer("copy neighbor block data"); - // 0ms + v3s16 bp = m_blockpos + block_offset; + v3s16 blockpos_nodes = bp * MAP_BLOCKSIZE; + m_vmanip.copyFrom(data, data_area, v3s16(0,0,0), blockpos_nodes, data_size); +} + +void MeshMakeData::fill(MapBlock *block) +{ + fillBlockDataBegin(block->getPos()); - /* - Copy neighbors. This is lightning fast. - Copying only the borders would be *very* slow. - */ + fillBlockData(v3s16(0,0,0), block->getData()); - // Get map - Map *map = block->getParent(); + // Get map for reading neigbhor blocks + Map *map = block->getParent(); - for(u16 i=0; i<26; i++) - { - const v3s16 &dir = g_26dirs[i]; - v3s16 bp = m_blockpos + dir; - MapBlock *b = map->getBlockNoCreateNoEx(bp); - if(b) - b->copyTo(m_vmanip); - } + for (u16 i=0; i<26; i++) { + const v3s16 &dir = g_26dirs[i]; + v3s16 bp = m_blockpos + dir; + MapBlock *b = map->getBlockNoCreateNoEx(bp); + if(b) + fillBlockData(dir, b->getData()); } } diff --git a/src/mapblock_mesh.h b/src/mapblock_mesh.h index 916703f3e..25c699e1c 100644 --- a/src/mapblock_mesh.h +++ b/src/mapblock_mesh.h @@ -52,6 +52,12 @@ struct MeshMakeData MeshMakeData(Client *client, bool use_shaders, bool use_tangent_vertices = false); + /* + Copy block data manually (to allow optimizations by the caller) + */ + void fillBlockDataBegin(const v3s16 &blockpos); + void fillBlockData(const v3s16 &block_offset, MapNode *data); + /* Copy central data directly from block, and other data from parent of block. diff --git a/src/mesh_generator_thread.cpp b/src/mesh_generator_thread.cpp new file mode 100644 index 000000000..126bf6327 --- /dev/null +++ b/src/mesh_generator_thread.cpp @@ -0,0 +1,329 @@ +/* +Minetest +Copyright (C) 2013, 2017 celeron55, Perttu Ahola + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "mesh_generator_thread.h" +#include "settings.h" +#include "profiler.h" +#include "client.h" +#include "mapblock.h" +#include "map.h" + +/* + CachedMapBlockData +*/ + +CachedMapBlockData::CachedMapBlockData(): + p(-1337,-1337,-1337), + data(NULL), + refcount_from_queue(0), + last_used_timestamp(time(0)) +{ +} + +CachedMapBlockData::~CachedMapBlockData() +{ + assert(refcount_from_queue == 0); + + if (data) + delete[] data; +} + +/* + QueuedMeshUpdate +*/ + +QueuedMeshUpdate::QueuedMeshUpdate(): + p(-1337,-1337,-1337), + ack_block_to_server(false), + urgent(false), + crack_level(-1), + crack_pos(0,0,0), + data(NULL) +{ +} + +QueuedMeshUpdate::~QueuedMeshUpdate() +{ + if (data) + delete data; +} + +/* + MeshUpdateQueue +*/ + +MeshUpdateQueue::MeshUpdateQueue(Client *client): + m_client(client) +{ + m_cache_enable_shaders = g_settings->getBool("enable_shaders"); + m_cache_use_tangent_vertices = m_cache_enable_shaders && ( + g_settings->getBool("enable_bumpmapping") || + g_settings->getBool("enable_parallax_occlusion")); + m_cache_smooth_lighting = g_settings->getBool("smooth_lighting"); + m_meshgen_block_cache_size = g_settings->getS32("meshgen_block_cache_size"); +} + +MeshUpdateQueue::~MeshUpdateQueue() +{ + MutexAutoLock lock(m_mutex); + + for (std::vector::iterator i = m_queue.begin(); + i != m_queue.end(); ++i) { + QueuedMeshUpdate *q = *i; + delete q; + } +} + +void MeshUpdateQueue::addBlock(Map *map, v3s16 p, bool ack_block_to_server, bool urgent) +{ + DSTACK(FUNCTION_NAME); + + 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; + cached_blocks.reserve(3*3*3); + v3s16 dp; + for (dp.X = -1; dp.X <= 1; dp.X++) + for (dp.Y = -1; dp.Y <= 1; dp.Y++) + for (dp.Z = -1; dp.Z <= 1; dp.Z++) { + v3s16 p1 = p + dp; + CachedMapBlockData *cached_block; + if (dp == v3s16(0, 0, 0)) + cached_block = cacheBlock(map, p1, FORCE_UPDATE); + else + cached_block = cacheBlock(map, p1, SKIP_UPDATE_IF_ALREADY_CACHED, + &cache_hit_counter); + cached_blocks.push_back(cached_block); + } + g_profiler->avg("MeshUpdateQueue MapBlock cache hit %", + 100.0f * cache_hit_counter / cached_blocks.size()); + + /* + Mark the block as urgent if requested + */ + if (urgent) + m_urgents.insert(p); + + /* + Find if block is already in queue. + If it is, update the data and quit. + */ + for (std::vector::iterator i = m_queue.begin(); + i != m_queue.end(); ++i) { + QueuedMeshUpdate *q = *i; + if (q->p == p) { + // NOTE: We are not adding a new position to the queue, thus + // refcount_from_queue stays the same. + if(ack_block_to_server) + q->ack_block_to_server = true; + q->crack_level = m_client->getCrackLevel(); + q->crack_pos = m_client->getCrackPos(); + return; + } + } + + /* + Add the block + */ + QueuedMeshUpdate *q = new QueuedMeshUpdate; + q->p = p; + q->ack_block_to_server = ack_block_to_server; + q->crack_level = m_client->getCrackLevel(); + q->crack_pos = m_client->getCrackPos(); + m_queue.push_back(q); + + // This queue entry is a new reference to the cached blocks + for (size_t i=0; irefcount_from_queue++; + } +} + +// Returned pointer must be deleted +// Returns NULL if queue is empty +QueuedMeshUpdate *MeshUpdateQueue::pop() +{ + 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; + m_queue.erase(i); + m_urgents.erase(q->p); + fillDataFromMapBlockCache(q); + return q; + } + return NULL; +} + +CachedMapBlockData* MeshUpdateQueue::cacheBlock(Map *map, v3s16 p, UpdateMode mode, + size_t *cache_hit_counter) +{ + std::map::iterator it = + m_cache.find(p); + if (it != m_cache.end()) { + // Already in cache + CachedMapBlockData *cached_block = it->second; + if (mode == SKIP_UPDATE_IF_ALREADY_CACHED) { + if (cache_hit_counter) + (*cache_hit_counter)++; + return cached_block; + } + MapBlock *b = map->getBlockNoCreateNoEx(p); + if (b) { + if (cached_block->data == NULL) + 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 = NULL; + } + return cached_block; + } else { + // Not yet in cache + CachedMapBlockData *cached_block = new CachedMapBlockData(); + m_cache[p] = cached_block; + MapBlock *b = map->getBlockNoCreateNoEx(p); + if (b) { + 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)); + } + return cached_block; + } +} + +CachedMapBlockData* MeshUpdateQueue::getCachedBlock(const v3s16 &p) +{ + std::map::iterator it = m_cache.find(p); + if (it != m_cache.end()) { + return it->second; + } + return NULL; +} + +void MeshUpdateQueue::fillDataFromMapBlockCache(QueuedMeshUpdate *q) +{ + MeshMakeData *data = new MeshMakeData(m_client, m_cache_enable_shaders, + m_cache_use_tangent_vertices); + q->data = data; + + data->fillBlockDataBegin(q->p); + + int t_now = time(0); + + // Collect data for 3*3*3 blocks from cache + v3s16 dp; + for (dp.X = -1; dp.X <= 1; dp.X++) + for (dp.Y = -1; dp.Y <= 1; dp.Y++) + for (dp.Z = -1; dp.Z <= 1; dp.Z++) { + 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); + } + } + + 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()); + + // 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 (std::map::iterator 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) { + m_cache.erase(it++); + } else { + ++it; + } + } +} + +/* + MeshUpdateThread +*/ + +MeshUpdateThread::MeshUpdateThread(Client *client): + UpdateThread("Mesh"), + m_queue_in(client) +{ + m_generation_interval = g_settings->getU16("mesh_generation_interval"); + m_generation_interval = rangelim(m_generation_interval, 0, 50); +} + +void MeshUpdateThread::updateBlock(Map *map, v3s16 p, bool ack_block_to_server, + bool urgent) +{ + // Allow the MeshUpdateQueue to do whatever it wants + m_queue_in.addBlock(map, p, ack_block_to_server, urgent); + deferUpdate(); +} + +void MeshUpdateThread::doUpdate() +{ + QueuedMeshUpdate *q; + while ((q = m_queue_in.pop())) { + if (m_generation_interval) + sleep_ms(m_generation_interval); + ScopeProfiler sp(g_profiler, "Client: Mesh making"); + + MapBlockMesh *mesh_new = new MapBlockMesh(q->data, m_camera_offset); + + MeshUpdateResult r; + r.p = q->p; + r.mesh = mesh_new; + r.ack_block_to_server = q->ack_block_to_server; + + m_queue_out.push_back(r); + + delete q; + } +} diff --git a/src/mesh_generator_thread.h b/src/mesh_generator_thread.h new file mode 100644 index 000000000..e74861862 --- /dev/null +++ b/src/mesh_generator_thread.h @@ -0,0 +1,135 @@ +/* +Minetest +Copyright (C) 2013, 2017 celeron55, Perttu Ahola + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifndef MESH_GENERATOR_THREAD_HEADER +#define MESH_GENERATOR_THREAD_HEADER + +#include "mapblock_mesh.h" +#include "threading/mutex_auto_lock.h" +#include "util/thread.h" + +struct CachedMapBlockData +{ + v3s16 p; + MapNode *data; // A copy of the MapBlock's data member + int refcount_from_queue; + int last_used_timestamp; + + CachedMapBlockData(); + ~CachedMapBlockData(); +}; + +struct QueuedMeshUpdate +{ + v3s16 p; + bool ack_block_to_server; + bool urgent; + int crack_level; + v3s16 crack_pos; + MeshMakeData *data; // This is generated in MeshUpdateQueue::pop() + + QueuedMeshUpdate(); + ~QueuedMeshUpdate(); +}; + +/* + A thread-safe queue of mesh update tasks and a cache of MapBlock data +*/ +class MeshUpdateQueue +{ + enum UpdateMode { + FORCE_UPDATE, + SKIP_UPDATE_IF_ALREADY_CACHED, + }; +public: + MeshUpdateQueue(Client *client); + + ~MeshUpdateQueue(); + + // Caches the block at p and its neighbors (if needed) and queues a mesh + // update for the block at p + void addBlock(Map *map, v3s16 p, bool ack_block_to_server, bool urgent); + + // Returned pointer must be deleted + // Returns NULL if queue is empty + QueuedMeshUpdate * pop(); + + u32 size() + { + MutexAutoLock lock(m_mutex); + return m_queue.size(); + } + +private: + Client *m_client; + std::vector m_queue; + std::set m_urgents; + std::map m_cache; + Mutex m_mutex; + + // TODO: Add callback to update these when g_settings changes + bool m_cache_enable_shaders; + bool m_cache_use_tangent_vertices; + 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 cleanupCache(); +}; + +struct MeshUpdateResult +{ + v3s16 p; + MapBlockMesh *mesh; + bool ack_block_to_server; + + MeshUpdateResult(): + p(-1338,-1338,-1338), + mesh(NULL), + ack_block_to_server(false) + { + } +}; + +class MeshUpdateThread : public UpdateThread +{ +public: + MeshUpdateThread(Client *client); + + // Caches the block at p and its neighbors (if needed) and queues a mesh + // update for the block at p + void updateBlock(Map *map, v3s16 p, bool ack_block_to_server, bool urgent); + + v3s16 m_camera_offset; + MutexedQueue m_queue_out; + +private: + MeshUpdateQueue m_queue_in; + + // TODO: Add callback to update these when g_settings changes + int m_generation_interval; + +protected: + virtual void doUpdate(); +}; + +#endif