Minetest源码分析十:MeshUpdateThread
minetest->client.h/client.cpp
MeshUpdateThread:Mesh 更新线程,这个线程主要是存储了需要更新的Mesh信息,且是线程安全的。类中主要是一个方法void * Thread(),以及两个成员变量MeshUpdateQueue m_queue_in,MutexedQueue<MeshUpdateResult> m_queue_out。
class MeshUpdateThread : public JThread
{
public:
void * Thread();
MeshUpdateQueue m_queue_in;
MutexedQueue
m_queue_out;
…
};
方法:
void * Thread()
方法开始是启动线程,然后是使用了一个while循环,不断的获取m_queue_in中存储的QueuedMeshUpdate数据,并生成MeshUpdateResult数据,然后存入m_queue_out变量中。MeshUpdateResult与QueuedMeshUpdate结构主要不同时一个存储的是MeshMakeData,一个是存储的由MeshMakeData生成好的MapBlockMesh。
涉及函数如下:
void * MeshUpdateThread::Thread()
{
ThreadStarted();
。。。
while(!StopRequested())
{
QueuedMeshUpdate *q = m_queue_in.pop();
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;
}
return NULL;
}
线程start:
MeshUpdateThread实例只在client对象中创建。线程的启动是在void Client::afterContentReceived(IrrlichtDevice *device, gui::IGUIFont* font) 这个函数中执行,m_mesh_update_thread.Start()。afterContentReceived是在the_game()函数开始连接好服务器、接收itemDef以及NodeDef之后执行调用的,且只有这一处调用。the_game()->Client::afterContentReceived->m_mesh_update_thread.Start()->MeshUpdateThread::Thread()
涉及函数如下:
void Client::afterContentReceived(IrrlichtDevice *device, gui::IGUIFont* font)
{
。。。
m_mesh_update_thread.Start();
。。。
}
成员变量
主要这两个:MeshUpdateQueue m_queue_in、MutexedQueue<MeshUpdateResult> m_queue_out。
MeshUpdateQueue m_queue_in
MeshUpdateQueue类:线程安全的网格更新任务队列。这个类中管理了一个std::vector<QueuedMeshUpdate*> m_queue成员变量,所以实际要更新的网格信息存在这个变量中,通过pop函数获取m_queue变量中的一条QueuedMeshUpdate数据,通过addBlock方法向m_queue添加一条QueuedMeshUpdate数据。这个QueuedMeshUpdate结构体中管理的是要更新的block信息(Mesh、位置、ack_to_server)
class MeshUpdateQueue
{
void addBlock(v3s16 p, MeshMakeData *data, bool ack_block_to_server, bool urgent);
QueuedMeshUpdate * pop();
std::vector
m_queue;
。。。
}
struct QueuedMeshUpdate
{
v3s16 p;
MeshMakeData *data;
bool ack_block_to_server;
};
m_queue_in什么时候变更值数据的?
1)线程中变更、使用数据:QueuedMeshUpdate *q = m_queue_in.pop()。一直执行这个MeshUpdateQueue类中的pop函数,等于pop(删除)一条m_queue的QueuedMeshUpdate数据。
2)线程外变更数据:基本是采用MeshUpdateQueue类中的addBlock函数,向m_queue_in对象的m_queue成员变量调价一条QueuedMeshUpdate数据。调用地方比较多,最终归纳为client->step()->ProcessData():command == TOCLIENT_ADDNODE、command == TOCLIENT_REMOVENODE、command == TOCLIENT_BLOCKDATA这3种情况。
譬如command == TOCLIENT_BLOCKDATA这种情况:
client->step()->ProcessData()
void Client::ProcessData(u8 *data, u32 datasize, u16 sender_peer_id)
{
。。。
ToClientCommand command = (ToClientCommand)readU16(&data[0]);
。。。
if(command == TOCLIENT_BLOCKDATA)
{
MapBlock *block = sector->getBlockNoCreateNoEx(p.Y);
if(block)
{
/*
Update an existing block
*/
block->deSerialize(istr, ser_version, false);
block->deSerializeNetworkSpecific(istr);
}
else
{
/*
Create a new block
*/
block = new MapBlock(&m_env.getMap(), p, this);
block->deSerialize(istr, ser_version, false);
block->deSerializeNetworkSpecific(istr);
sector->insertBlock(block);
}
/*
Add it to mesh update queue and set it to be acknowledged after update.
*/
addUpdateMeshTaskWithEdge(p, true);
}
}
addUpdateMeshTaskWithEdge(p, true)最终这个会更新MeshUpdateThread中的m_queue_in变量的m_queue属性值。
void Client::addUpdateMeshTaskWithEdge(v3s16 blockpos, bool ack_to_server, bool urgent)
{
。。。
addUpdateMeshTask(p, ack_to_server, urgent);
。。。
}
void Client::addUpdateMeshTask(v3s16 p, bool ack_to_server, bool urgent)
{
MapBlock *b = m_env.getMap().getBlockNoCreateNoEx(p);
MeshMakeData *data = new MeshMakeData(this);
data->fill(b);
// Add task to queue
m_mesh_update_thread.m_queue_in.addBlock(p, data, ack_to_server, urgent);
}
MutexedQueue<MeshUpdateResult> m_queue_out;
MutexedQueue:线程安全的FIFO队列,这是一个队列,队列中的存了一系列MeshUpdateResult结构(位置、mesh、ack_to_server)的数据。MeshUpdateResult结构用来描述一个MapBlock的mesh更新相关信息。
struct MeshUpdateResult
{
v3s16 p;
MapBlockMesh *mesh;
bool ack_block_to_server;
。。。
};
m_queue_out什么时候变更值数据的?
1)线程内变更数据:m_queue_out.push_back(r);基本直接是逐条解析m_queue_in中的MeshUpdateQueue数据,然后push_back到m_queue_out,每次循环解析一次。
2)线程外变更、使用数据:使用只在 client->step()中使用,client更新时,先receive一条数据,然后ProcessData之后,再处理replace updated mesh。这个顺序只能是先接收处理数据,然后更新,不然会有延迟 。
譬如先更新后ProcessData的情况示例:如果blockdata发生改变了,通知服务端,服务端更改好,并发送相关信息到connection对象中,如果客户端step更新时,不先获取数据,那么就没有办法去更改MeshUpdateThread对象中的MeshUpdateQueue m_queue_in变量,自然
MeshUpdateThread对象中的MutexedQueue<MeshUpdateResult> m_queue_out变量也不会更改,先处理更改Replace updated meshes,那么就会造成延时一次更新,更新的这次blockdata其实是改变之前的block数据。
若先处理了获取数据,则会执行到processdata,会触发更改MeshUpdateThread对象中的MeshUpdateQueue m_queue_in变量,从而改变MeshUpdateThread对象中的MutexedQueue<MeshUpdateResult> m_queue_out变量,最终Replace updated meshes时获取的m_queue_out变量也是改变后的数据了。
涉及到的函数
void Client::step(float dtime)
{
ReceiveAll();
。。。
/*
Replace updated meshes
*/
while(!m_mesh_update_thread.m_queue_out.empty())
{
MeshUpdateResult r = m_mesh_update_thread.m_queue_out.pop_frontNoEx();
MapBlock *block = m_env.getMap().getBlockNoCreateNoEx(r.p);
// Replace with the new mesh
block->mesh = r.mesh;
if(r.ack_block_to_server)
{
writeU16(&reply[0], TOSERVER_GOTBLOCKS);
m_con.Send(PEER_ID_SERVER, 2, reply, true);
}
}
}