Minetest源码分析十:MeshUpdateThread

Minetest源码分析十:MeshUpdateThread



minetest->client.h/client.cpp

MeshUpdateThread:Mesh 更新线程,这个线程主要是存储了需要更新的Mesh信息,且是线程安全的。类中主要是一个方法void * Thread(),以及两个成员变量MeshUpdateQueue m_queue_inMutexedQueue<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变量中。MeshUpdateResultQueuedMeshUpdate结构主要不同时一个存储的是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_inMutexedQueue<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_queueQueuedMeshUpdate数据。


2)线程外变更数据:基本是采用MeshUpdateQueue类中的addBlock函数,向m_queue_in对象的m_queue成员变量调价一条QueuedMeshUpdate数据。调用地方比较多,最终归纳为client->step()->ProcessData():command == TOCLIENT_ADDNODEcommand == TOCLIENT_REMOVENODEcommand == 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_backm_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);
		}
	}
}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值