Skip to content
Permalink
Browse files

MeshUpdateQueue: Add a MapBlock cache that minimizes the amount of Ma…

…pBlock 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)
  • Loading branch information
celeron55 committed Apr 15, 2017
1 parent 4323ad1 commit 04cc9de8f2fbcb11f133c88f02fc11504b3ea6f3
@@ -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 \
@@ -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

@@ -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
@@ -522,6 +522,7 @@ set(client_SRCS
main.cpp
mapblock_mesh.cpp
mesh.cpp
mesh_generator_thread.cpp
minimap.cpp
particles.cpp
shader.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<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)
@@ -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 @@ class Client : public con::PeerHandler, public InventoryManager, public IGameDef
float getAnimationTime();

int getCrackLevel();
v3s16 getCrackPos();
void setCrack(int level, v3s16 pos);

u16 getHP();
@@ -726,11 +652,6 @@ class Client : public con::PeerHandler, public InventoryManager, public IGameDef
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;
@@ -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");
@@ -154,6 +154,11 @@ class MapBlock /*: public NodeContainer*/
raiseModified(MOD_STATE_WRITE_NEEDED, MOD_REASON_REALLOCATE);
}

MapNode* getData()
{
return data;
}

////
//// Modification tracking methods
////

0 comments on commit 04cc9de

Please sign in to comment.
You can’t perform that action at this time.