MeshUpdateQueue: Add a MapBlock cache that minimizes the amount of MapBlock copying...
authorPerttu Ahola <celeron55@gmail.com>
Sat, 15 Apr 2017 07:55:52 +0000 (10:55 +0300)
committerceleron55 <celeron55@gmail.com>
Mon, 17 Apr 2017 11:58:29 +0000 (14:58 +0300)
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)

12 files changed:
build/android/jni/Android.mk
builtin/settingtypes.txt
minetest.conf.example
src/CMakeLists.txt
src/client.cpp
src/client.h
src/defaultsettings.cpp
src/mapblock.h
src/mapblock_mesh.cpp
src/mapblock_mesh.h
src/mesh_generator_thread.cpp [new file with mode: 0644]
src/mesh_generator_thread.h [new file with mode: 0644]

index fd104db065b9c3377def366f53172c538a01ca7f..2929eaba1ba0f5103094720c5f2202969bb43dda 100644 (file)
@@ -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                 \
index 2c74d6c441dcd1f51da8b41c32456678bf1f08a3..5dc48c00ead98da0300e049fd37a92e844ac9c8a 100644 (file)
@@ -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
 
index d147ebfac37ad8ad3baa39b358cdd3db02f4bc38..9b50a775d2e17699a579aa6d5590d2abf1d4eff9 100644 (file)
 #    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
index edb291545052974ad018995146a97bb49dedc39f..37f72a44d815a1229ab962298fb035eb646e636f 100644 (file)
@@ -522,6 +522,7 @@ set(client_SRCS
        main.cpp
        mapblock_mesh.cpp
        mesh.cpp
+       mesh_generator_thread.cpp
        minimap.cpp
        particles.cpp
        shader.cpp
index 5ca51bd9c90a548dab042c5d61ce81dc91c1516e..7b962cd94acb9d3ffc347f5d3a0a8e64351426bf 100644 (file)
@@ -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<QueuedMeshUpdate*>::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<QueuedMeshUpdate*>::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<QueuedMeshUpdate*>::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)
index e565acd939bba12b5472247b7a9c4a68c30551de..e7fcb597df936c61428f45d87a0ca48f87ce3e06 100644 (file)
@@ -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<QueuedMeshUpdate*> m_queue;
-       std::set<v3s16> 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<MeshUpdateResult> 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<std::string, ModMetadata *> m_mod_storages;
index c6ced593157a718927d11906a131c220f52f37da..573b5e2d823993b836f6220879d890600e3905f9 100644 (file)
@@ -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");
index 7ff613fe86086010eee748185cf358ad9ebd5fa8..8816dc817f7ef7f7f18b3ca44c4972dc50028a3f 100644 (file)
@@ -154,6 +154,11 @@ public:
                raiseModified(MOD_STATE_WRITE_NEEDED, MOD_REASON_REALLOCATE);
        }
 
+       MapNode* getData()
+       {
+               return data;
+       }
+
        ////
        //// Modification tracking methods
        ////
index eddb061b45b582353c9d83a6bc29b1e5339d2acb..933dfc32aa8e5ce4eb9246c06e56123a9e499088 100644 (file)
@@ -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());
        }
 }
 
index 916703f3ea97ec49326e96714d966e24f81cf6cd..25c699e1cab24d2ba3520e13d0f698795d24ba5b 100644 (file)
@@ -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 (file)
index 0000000..126bf63
--- /dev/null
@@ -0,0 +1,329 @@
+/*
+Minetest
+Copyright (C) 2013, 2017 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+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<QueuedMeshUpdate*>::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<CachedMapBlockData*> 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<QueuedMeshUpdate*>::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; i<cached_blocks.size(); i++) {
+               cached_blocks[i]->refcount_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<QueuedMeshUpdate*>::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<v3s16, CachedMapBlockData*>::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<v3s16, CachedMapBlockData*>::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<v3s16, CachedMapBlockData*>::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 (file)
index 0000000..e748618
--- /dev/null
@@ -0,0 +1,135 @@
+/*
+Minetest
+Copyright (C) 2013, 2017 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+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<QueuedMeshUpdate*> m_queue;
+       std::set<v3s16> m_urgents;
+       std::map<v3s16, CachedMapBlockData*> 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<MeshUpdateResult> 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