]> git.lizzy.rs Git - minetest.git/commitdiff
Use multiple threads for mesh generation (#13062)
authorx2048 <codeforsmile@gmail.com>
Tue, 27 Dec 2022 17:44:18 +0000 (18:44 +0100)
committerGitHub <noreply@github.com>
Tue, 27 Dec 2022 17:44:18 +0000 (18:44 +0100)
Co-authored-by: sfan5 <sfan5@live.de>
builtin/settingtypes.txt
src/client/client.cpp
src/client/client.h
src/client/mesh_generator_thread.cpp
src/client/mesh_generator_thread.h
src/client/tile.cpp
src/defaultsettings.cpp
src/network/clientpackethandler.cpp

index b3398c831501adfc50710f6b866724a26c3354e1..a97d2c0f72ea4b6f0ae3c37262a89688f7410514 100644 (file)
@@ -1673,6 +1673,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
 
+#    Number of threads to use for mesh generation.
+#    Value of 0 (default) will let Minetest autodetect the number of available threads.
+mesh_generation_threads (Mapblock mesh generation threads) int 0 0 8
+
+
 #    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.
index 04686c43cba29984e5e7f3951044f8ecdbcf2734..27b0ca852d2defca0bcfb18706c8493b5f7409e8 100644 (file)
@@ -111,7 +111,7 @@ Client::Client(
        m_sound(sound),
        m_event(event),
        m_rendering_engine(rendering_engine),
-       m_mesh_update_thread(this),
+       m_mesh_update_manager(this),
        m_env(
                new ClientMap(this, rendering_engine, control, 666),
                tsrc, this
@@ -312,7 +312,7 @@ void Client::Stop()
        if (m_mods_loaded)
                m_script->on_shutdown();
        //request all client managed threads to stop
-       m_mesh_update_thread.stop();
+       m_mesh_update_manager.stop();
        // Save local server map
        if (m_localdb) {
                infostream << "Local map saving ended." << std::endl;
@@ -325,7 +325,7 @@ void Client::Stop()
 
 bool Client::isShutdown()
 {
-       return m_shutdown || !m_mesh_update_thread.isRunning();
+       return m_shutdown || !m_mesh_update_manager.isRunning();
 }
 
 Client::~Client()
@@ -335,13 +335,12 @@ Client::~Client()
 
        deleteAuthData();
 
-       m_mesh_update_thread.stop();
-       m_mesh_update_thread.wait();
-       while (!m_mesh_update_thread.m_queue_out.empty()) {
-               MeshUpdateResult r = m_mesh_update_thread.m_queue_out.pop_frontNoEx();
+       m_mesh_update_manager.stop();
+       m_mesh_update_manager.wait();
+       
+       MeshUpdateResult r;
+       while (m_mesh_update_manager.getNextResult(r))
                delete r.mesh;
-       }
-
 
        delete m_inventory_from_server;
 
@@ -547,14 +546,14 @@ void Client::step(float dtime)
                int num_processed_meshes = 0;
                std::vector<v3s16> blocks_to_ack;
                bool force_update_shadows = false;
-               while (!m_mesh_update_thread.m_queue_out.empty())
+               MeshUpdateResult r;
+               while (m_mesh_update_manager.getNextResult(r))
                {
                        num_processed_meshes++;
 
                        MinimapMapblock *minimap_mapblock = NULL;
                        bool do_mapper_update = true;
 
-                       MeshUpdateResult r = m_mesh_update_thread.m_queue_out.pop_frontNoEx();
                        MapBlock *block = m_env.getMap().getBlockNoCreateNoEx(r.p);
                        if (block) {
                                // Delete the old mesh
@@ -1655,12 +1654,12 @@ void Client::addUpdateMeshTask(v3s16 p, bool ack_to_server, bool urgent)
        if (b == NULL)
                return;
 
-       m_mesh_update_thread.updateBlock(&m_env.getMap(), p, ack_to_server, urgent);
+       m_mesh_update_manager.updateBlock(&m_env.getMap(), p, ack_to_server, urgent);
 }
 
 void Client::addUpdateMeshTaskWithEdge(v3s16 blockpos, bool ack_to_server, bool urgent)
 {
-       m_mesh_update_thread.updateBlock(&m_env.getMap(), blockpos, ack_to_server, urgent, true);
+       m_mesh_update_manager.updateBlock(&m_env.getMap(), blockpos, ack_to_server, urgent, true);
 }
 
 void Client::addUpdateMeshTaskForNode(v3s16 nodepos, bool ack_to_server, bool urgent)
@@ -1674,7 +1673,7 @@ void Client::addUpdateMeshTaskForNode(v3s16 nodepos, bool ack_to_server, bool ur
 
        v3s16 blockpos = getNodeBlockPos(nodepos);
        v3s16 blockpos_relative = blockpos * MAP_BLOCKSIZE;
-       m_mesh_update_thread.updateBlock(&m_env.getMap(), blockpos, ack_to_server, urgent, false);
+       m_mesh_update_manager.updateBlock(&m_env.getMap(), blockpos, ack_to_server, urgent, false);
        // Leading edge
        if (nodepos.X == blockpos_relative.X)
                addUpdateMeshTask(blockpos + v3s16(-1, 0, 0), false, urgent);
@@ -1793,7 +1792,7 @@ void Client::afterContentReceived()
 
        // Start mesh update thread after setting up content definitions
        infostream<<"- Starting mesh update thread"<<std::endl;
-       m_mesh_update_thread.start();
+       m_mesh_update_manager.start();
 
        m_state = LC_Ready;
        sendReady();
index 52ca876254b0ea2bb9747387068a36027c3752d5..c05e6d32560466af7323bf03516d096ac021c021 100644 (file)
@@ -313,7 +313,7 @@ class Client : public con::PeerHandler, public InventoryManager, public IGameDef
        void addUpdateMeshTaskForNode(v3s16 nodepos, bool ack_to_server=false, bool urgent=false);
 
        void updateCameraOffset(v3s16 camera_offset)
-       { m_mesh_update_thread.m_camera_offset = camera_offset; }
+       { m_mesh_update_manager.m_camera_offset = camera_offset; }
 
        bool hasClientEvents() const { return !m_client_event_queue.empty(); }
        // Get event from queue. If queue is empty, it triggers an assertion failure.
@@ -483,7 +483,7 @@ class Client : public con::PeerHandler, public InventoryManager, public IGameDef
        RenderingEngine *m_rendering_engine;
 
 
-       MeshUpdateThread m_mesh_update_thread;
+       MeshUpdateManager m_mesh_update_manager;
        ClientEnvironment m_env;
        ParticleManager m_particle_manager;
        std::unique_ptr<con::Connection> m_con;
index ec567c8c1fdd4be3110cfef0eaa92c94e344fee8..1456b26ef09d297094495551467df90516be825c 100644 (file)
@@ -146,16 +146,26 @@ QueuedMeshUpdate *MeshUpdateQueue::pop()
        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)
+               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;
        }
        return NULL;
 }
 
+void MeshUpdateQueue::done(v3s16 pos)
+{
+       MutexAutoLock lock(m_mutex);
+       m_inflight_blocks.erase(pos);
+}
+
 CachedMapBlockData* MeshUpdateQueue::cacheBlock(Map *map, v3s16 p, UpdateMode mode,
                        size_t *cache_hit_counter)
 {
@@ -264,18 +274,62 @@ void MeshUpdateQueue::cleanupCache()
 }
 
 /*
-       MeshUpdateThread
+       MeshUpdateWorkerThread
 */
 
-MeshUpdateThread::MeshUpdateThread(Client *client):
-       UpdateThread("Mesh"),
-       m_queue_in(client)
+MeshUpdateWorkerThread::MeshUpdateWorkerThread(MeshUpdateQueue *queue_in, MeshUpdateManager *manager, v3s16 *camera_offset) :
+               UpdateThread("Mesh"), m_queue_in(queue_in), m_manager(manager), m_camera_offset(camera_offset)
 {
        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,
+void MeshUpdateWorkerThread::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 (sum)");
+
+               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;
+               r.urgent = q->urgent;
+
+               m_manager->putResult(r);
+               m_queue_in->done(q->p);
+               delete q;
+       }
+}
+
+/*
+       MeshUpdateManager
+*/
+
+MeshUpdateManager::MeshUpdateManager(Client *client):
+       m_queue_in(client)
+{
+       int number_of_threads = rangelim(g_settings->getS32("mesh_generation_threads"), 0, 8);
+
+       // Automatically use 33% of the system cores for mesh generation, max 4
+       if (number_of_threads == 0)
+               number_of_threads = MYMIN(4, Thread::getNumberOfProcessors() / 3);
+       
+       // use at least one thread
+       number_of_threads = MYMAX(1, number_of_threads);
+       infostream << "MeshUpdateManager: using " << number_of_threads << " threads" << std::endl;
+
+       for (int i = 0; i < number_of_threads; i++)
+               m_workers.push_back(std::make_unique<MeshUpdateWorkerThread>(&m_queue_in, this, &m_camera_offset));
+}
+
+void MeshUpdateManager::updateBlock(Map *map, v3s16 p, bool ack_block_to_server,
                bool urgent, bool update_neighbors)
 {
        static thread_local const bool many_neighbors =
@@ -298,24 +352,57 @@ void MeshUpdateThread::updateBlock(Map *map, v3s16 p, bool ack_block_to_server,
        deferUpdate();
 }
 
-void MeshUpdateThread::doUpdate()
+void MeshUpdateManager::putResult(const MeshUpdateResult &result)
 {
-       QueuedMeshUpdate *q;
-       while ((q = m_queue_in.pop())) {
-               if (m_generation_interval)
-                       sleep_ms(m_generation_interval);
-               ScopeProfiler sp(g_profiler, "Client: Mesh making (sum)");
+       if (result.urgent)
+               m_queue_out_urgent.push_back(result);
+       else
+               m_queue_out.push_back(result);
+}
 
-               MapBlockMesh *mesh_new = new MapBlockMesh(q->data, m_camera_offset);
+bool MeshUpdateManager::getNextResult(MeshUpdateResult &r)
+{
+       if (!m_queue_out_urgent.empty()) {
+               r = m_queue_out_urgent.pop_frontNoEx();
+               return true;
+       }
 
-               MeshUpdateResult r;
-               r.p = q->p;
-               r.mesh = mesh_new;
-               r.ack_block_to_server = q->ack_block_to_server;
-               r.urgent = q->urgent;
+       if (!m_queue_out.empty()) {
+               r = m_queue_out.pop_frontNoEx();
+               return true;
+       }
 
-               m_queue_out.push_back(r);
+       return false;
+}
 
-               delete q;
-       }
+void MeshUpdateManager::deferUpdate()
+{
+       for (auto &thread : m_workers)
+               thread->deferUpdate();
+}
+
+void MeshUpdateManager::start()
+{
+       for (auto &thread: m_workers)
+               thread->start();
+}
+
+void MeshUpdateManager::stop()
+{
+       for (auto &thread: m_workers)
+               thread->stop();
+}
+
+void MeshUpdateManager::wait()
+{
+       for (auto &thread: m_workers)
+               thread->wait();
+}
+
+bool MeshUpdateManager::isRunning()
+{
+       for (auto &thread: m_workers)
+               if (thread->isRunning())
+                       return true;
+       return false;
 }
index 09400196dd9df009cb5cca3d924bc14f7e003caa..bbb84b74a987ed7241cd368692bbed555b9d0730 100644 (file)
@@ -26,6 +26,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "mapblock_mesh.h"
 #include "threading/mutex_auto_lock.h"
 #include "util/thread.h"
+#include <vector>
+#include <memory>
 
 struct CachedMapBlockData
 {
@@ -75,6 +77,9 @@ class MeshUpdateQueue
        // Returns NULL if queue is empty
        QueuedMeshUpdate *pop();
 
+       // Marks a position as finished, unblocking the next update
+       void done(v3s16 pos);
+
        u32 size()
        {
                MutexAutoLock lock(m_mutex);
@@ -86,6 +91,7 @@ class MeshUpdateQueue
        std::vector<QueuedMeshUpdate *> m_queue;
        std::unordered_set<v3s16> m_urgents;
        std::unordered_map<v3s16, CachedMapBlockData *> m_cache;
+       std::unordered_set<v3s16> m_inflight_blocks;
        u64 m_next_cache_cleanup; // milliseconds
        std::mutex m_mutex;
 
@@ -111,25 +117,53 @@ struct MeshUpdateResult
        MeshUpdateResult() = default;
 };
 
-class MeshUpdateThread : public UpdateThread
+class MeshUpdateManager;
+
+class MeshUpdateWorkerThread : public UpdateThread
+{
+public:
+       MeshUpdateWorkerThread(MeshUpdateQueue *queue_in, MeshUpdateManager *manager, v3s16 *camera_offset);
+
+protected:
+       virtual void doUpdate();
+
+private:
+       MeshUpdateQueue *m_queue_in;
+       MeshUpdateManager *m_manager;
+       v3s16 *m_camera_offset;
+
+       // TODO: Add callback to update these when g_settings changes
+       int m_generation_interval;
+};
+
+class MeshUpdateManager
 {
 public:
-       MeshUpdateThread(Client *client);
+       MeshUpdateManager(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,
                        bool update_neighbors = false);
+       void putResult(const MeshUpdateResult &r);
+       bool getNextResult(MeshUpdateResult &r);
+
 
        v3s16 m_camera_offset;
-       MutexedQueue<MeshUpdateResult> m_queue_out;
+
+       void start();
+       void stop();
+       void wait();
+
+       bool isRunning();
 
 private:
-       MeshUpdateQueue m_queue_in;
+       void deferUpdate();
 
-       // TODO: Add callback to update these when g_settings changes
-       int m_generation_interval;
 
-protected:
-       virtual void doUpdate();
+       MeshUpdateQueue m_queue_in;
+       MutexedQueue<MeshUpdateResult> m_queue_out;
+       MutexedQueue<MeshUpdateResult> m_queue_out_urgent;
+
+       std::vector<std::unique_ptr<MeshUpdateWorkerThread>> m_workers;
 };
index 0336bd82a68c4c85d4fa98e89c3c0771fdd0d610..582219bb8a6ea4f11b3a21c1f78fe3eb7c924331 100644 (file)
@@ -491,16 +491,16 @@ u32 TextureSource::getTextureId(const std::string &name)
        infostream<<"getTextureId(): Queued: name=\""<<name<<"\""<<std::endl;
 
        // We're gonna ask the result to be put into here
-       static ResultQueue<std::string, u32, u8, u8> result_queue;
+       static thread_local ResultQueue<std::string, u32, u8, u8> result_queue;
 
        // Throw a request in
        m_get_texture_queue.add(name, 0, 0, &result_queue);
 
        try {
                while(true) {
-                       // Wait result for a second
+                       // Wait for result for up to 4 seconds (empirical value)
                        GetResult<std::string, u32, u8, u8>
-                               result = result_queue.pop_front(1000);
+                               result = result_queue.pop_front(4000);
 
                        if (result.key == name) {
                                return result.item;
index e8170788ac52563a48ac514cebcdf3e6ecce44c9..3639ae29208f309a3498453d2c0a98eb7e2e76fc 100644 (file)
@@ -44,6 +44,7 @@ void set_default_settings()
        settings->setDefault("mute_sound", "false");
        settings->setDefault("enable_mesh_cache", "false");
        settings->setDefault("mesh_generation_interval", "0");
+       settings->setDefault("mesh_generation_threads", "0");
        settings->setDefault("meshgen_block_cache_size", "20");
        settings->setDefault("enable_vbo", "true");
        settings->setDefault("free_move", "false");
index a98fa0733c3b82e391e1dbcdbb43f202e6441806..829b1c3e69969adeb648f2bf57c27a705e93a9ce 100644 (file)
@@ -673,7 +673,7 @@ void Client::handleCommand_AnnounceMedia(NetworkPacket* pkt)
 
        // Mesh update thread must be stopped while
        // updating content definitions
-       sanity_check(!m_mesh_update_thread.isRunning());
+       sanity_check(!m_mesh_update_manager.isRunning());
 
        for (u16 i = 0; i < num_files; i++) {
                std::string name, sha1_base64;
@@ -733,7 +733,7 @@ void Client::handleCommand_Media(NetworkPacket* pkt)
        if (init_phase) {
                // Mesh update thread must be stopped while
                // updating content definitions
-               sanity_check(!m_mesh_update_thread.isRunning());
+               sanity_check(!m_mesh_update_manager.isRunning());
        }
 
        for (u32 i = 0; i < num_files; i++) {
@@ -770,7 +770,7 @@ void Client::handleCommand_NodeDef(NetworkPacket* pkt)
 
        // Mesh update thread must be stopped while
        // updating content definitions
-       sanity_check(!m_mesh_update_thread.isRunning());
+       sanity_check(!m_mesh_update_manager.isRunning());
 
        // Decompress node definitions
        std::istringstream tmp_is(pkt->readLongString(), std::ios::binary);
@@ -789,7 +789,7 @@ void Client::handleCommand_ItemDef(NetworkPacket* pkt)
 
        // Mesh update thread must be stopped while
        // updating content definitions
-       sanity_check(!m_mesh_update_thread.isRunning());
+       sanity_check(!m_mesh_update_manager.isRunning());
 
        // Decompress item definitions
        std::istringstream tmp_is(pkt->readLongString(), std::ios::binary);