minetest源码解析七:Client端更新流程
客户端更新主要使用的函数是void Client::step(float dtime)
minetest->client.cpp
这个函数作用是更新客户端,客户端主动去接收服务器端发过来的信息,然后对环境等进行更新。
使用范围:都在the_game()这个函数中(minetest->game.cpp->the_game())。这个函数中共有3处使用。
(1)wait for server to accept connection 时使用,循环执行client.step()来更新客户端,这个时候游戏界面还没有出来之前,直到客户端接收到 TOCLIENT_INIT命令,从而根据命令执行更新m_state = LC_Init才退出循环。
(2)Wait until content has been received时使用,循环执行client.step()来更新客户端,这个时候游戏界面也还没有出来之前,直到客户端陆续接收到TOCLIENT_ITEMDEF、TOCLIENT_NODEDEF命令,代表内容数据接收完毕之后才退出循环。
(3)Run server, client 时使用,这个是在game循环中,不断循环执行更新客户端。
客户端更新流程图(Client::step)
流程图中核心函数说明
1.ReceiveAll()
接收数据命令,服务端发过来的,接收到的命令主要是这个形式ToClientCommand,服务端发过来的命令存在Connection对象的MutexedQueue<ConnectionEvent> m_event_queue中。
涉及到的函数
void Client::ReceiveAll()
{
for(;;)
{
if(porting::getTimeMs() > start_ms + 100)
break;
Receive();
。。。
}
}
void Client::Receive()
{
DSTACK(__FUNCTION_NAME);
SharedBuffer<u8> data;
u16 sender_peer_id;
u32 datasize = m_con.Receive(sender_peer_id, data);
ProcessData(*data, datasize, sender_peer_id);
}
m_con.Receive(sender_peer_id, data);
minetest->connection.cpp
u32 Connection::Receive(u16 &peer_id, SharedBuffer<u8> &data)
{
for(;;)
{
ConnectionEvent e = waitEvent(m_bc_receive_timeout);
switch(e.type)
{
case CONNEVENT_NONE:
throw ;
case CONNEVENT_DATA_RECEIVED:
return ;
case CONNEVENT_PEER_ADDED:
continue;
case CONNEVENT_PEER_REMOVED:
continue;
case CONNEVENT_BIND_FAILED:
throw ;
}
}
}
minetest->connection.h
MutexedQueue<ConnectionEvent> m_event_queue;
enum ConnectionEventType{
CONNEVENT_NONE,
CONNEVENT_DATA_RECEIVED,
CONNEVENT_PEER_ADDED,
CONNEVENT_PEER_REMOVED,
CONNEVENT_BIND_FAILED,
};
void Client::ProcessData(u8 *data, u32 datasize, u16 sender_peer_id)
minetest->client.cpp
这个实际对接收到的数据,主要根据接收数据中的ToClientCommand命令进行处理,有些命令直接处理,有些直接加入到事件列表中,后续再处理,返回到the_game()函数中的循环体中,主要处理时在game之后的流程中,// Read client events会对clientEvent进判断并处理。
ToClientCommand command = (ToClientCommand)readU16(&data[0]);
u8 ser_version = m_server_ser_ver;
if(command == TOCLIENT_INIT)
{ …
writeU16(&reply[0], TOSERVER_INIT2);
m_con.Send(PEER_ID_SERVER, 1, reply, true);
m_state = LC_Init;
return;
}
if(command == TOCLIENT_ACCESS_DENIED)
{
return;
}
if(ser_version == SER_FMT_VER_INVALID)
return;
/*
Handle runtime commands (运行时命令)
*/
if(command == TOCLIENT_REMOVENODE)
else if(command == TOCLIENT_ADDNODE)
else if(command == TOCLIENT_BLOCKDATA)
else if(command == TOCLIENT_INVENTORY)
else if(command == TOCLIENT_TIME_OF_DAY)
。。。
2.Packet counter
计数器
3.初始client startup 发送TOSERVER_INIT
minetest->clientserver.h ToServerCommand ToClientCommand
client创建之后第二次更新的时候。(这里是第二次,做了一个延时)
初次更新客户端时,还没有连接,程序执行到这就退出了。
涉及到的函数
Send(1, data, false);
void Client::Send(u16 channelnum, SharedBuffer<u8> data, bool reliable)
{
m_con.Send(PEER_ID_SERVER, channelnum, data, reliable);
}
minetest->connection.cpp
4.Run Map's timers and unload unused data
运行Map的定时器,获取deleted_blocks,并通知服务器端。
涉及到的函数
m_env.getMap().timerUpdate(map_timer_and_unload_dtime, g_settings->getFloat("client_unload_unused_data_timeout"), &deleted_blocks);
m_con.Send(PEER_ID_SERVER, 2, reply, true);
writeU16(&reply[0], TOSERVER_DELETEDBLOCKS);
5.Handle environment
处理环境相关的。
涉及到的函数
minetest->environment.cpp
m_env.step(dtime);主要更新光照、溺水、水流淌速度更新、熔岩、activeObject Step、ClientSimpleObject() step
Get events
for(;;)
{
ClientEnvEvent event = m_env.getClientEvent();
if(event.type == CEE_NONE) //Client环境中的客户端事件列表为空时
break;
else if(event.type == CEE_PLAYER_DAMAGE)
else if(event.type == CEE_PLAYER_BREATH)
}
ClientEnvEvent 一个结构体
minetest->environment.h
enum ClientEnvEventType
{
CEE_NONE,
CEE_PLAYER_DAMAGE,
CEE_PLAYER_BREATH
};
minetest->client.h
enum ClientEventType
{
CE_NONE,
CE_PLAYER_DAMAGE,
CE_PLAYER_FORCE_MOVE,
CE_DEATHSCREEN,
CE_SHOW_FORMSPEC,
CE_SPAWN_PARTICLE,
CE_ADD_PARTICLESPAWNER,
CE_DELETE_PARTICLESPAWNER,
CE_HUDADD,
CE_HUDRM,
CE_HUDCHANGE,
CE_SET_SKY,
CE_OVERRIDE_DAY_NIGHT_RATIO,
};
6.Send player position to server
给服务端发送玩家位置
涉及到的函数
sendPlayerPos();
void Client::sendPlayerPos()
{
。。。
writeU16(&data[0], TOSERVER_PLAYERPOS);
。。。
}
7.Replace updated meshes
更新mesh
主要涉及到的函数
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);
if(block)
{
// Delete the old mesh
// Replace with the new mesh
block->mesh = r.mesh;
} else {
delete r.mesh;
}
if(r.ack_block_to_server)
{
//Acknowledge block
writeU16(&reply[0], TOSERVER_GOTBLOCKS);
m_con.Send(PEER_ID_SERVER, 2, reply, true);
}
}
8.Load fetched media
加载获取到的media
m_media_downloader->step(this);
if (m_media_downloader->isDone())
received_media();
9.更改库存 m_inventory_from_server
如果m_inventory_from_server有值,且相隔一个interval时间,更新玩家的库存。
涉及到的函数
player->inventory = *m_inventory_from_server;
m_inventory_updated = true;
10.Update positions of sounds attached to objects
更新Object的声音位置
11.Handle removed remotely initiated sounds
// Find removed sounds and clear references to them,这些的更改一般是在Client::ProcessData中进行的。
writeU16(os, TOSERVER_REMOVED_SOUNDS);
12. LocalClientState
minetest->client.h
enum LocalClientState
{
LC_Created,
LC_Init,
LC_Ready
};
LC_Created:初始化;TOCLIENT_INIT:LC_Init ;afterContentReceived() LC_Ready
LC_Ready 只有一个地方调用 client.afterContentReceived(device,font);
内容接收之后:ItemDefManager、NodeDefManager、Media,进入game循环之前设置进去的,也就是进入循环之后状态肯定是LC_Ready .
13.m_client_event_queue 理解 共有两处 (minetest->client.h)
Queue<ClientEvent> m_client_event_queue;
enum ClientEventType
{
CE_NONE,
CE_PLAYER_DAMAGE,
CE_PLAYER_FORCE_MOVE,
CE_DEATHSCREEN,
CE_SHOW_FORMSPEC,
CE_SPAWN_PARTICLE,
CE_ADD_PARTICLESPAWNER,
CE_DELETE_PARTICLESPAWNER,
CE_HUDADD,
CE_HUDRM,
CE_HUDCHANGE,
CE_SET_SKY,
CE_OVERRIDE_DAY_NIGHT_RATIO,
};
struct ClientEvent
{
ClientEventType type;
union{
struct{
} none;
struct{
u8 amount;
} player_damage;
struct{
f32 pitch;
f32 yaw;
} player_force_move;
struct{
bool set_camera_point_target;
f32 camera_point_target_x;
f32 camera_point_target_y;
f32 camera_point_target_z;
} deathscreen;
struct{
std::string *formspec;
std::string *formname;
} show_formspec;
struct{
} textures_updated;
struct{
v3f *pos;
v3f *vel;
v3f *acc;
f32 expirationtime;
f32 size;
bool collisiondetection;
bool vertical;
std::string *texture;
} spawn_particle;
struct{
u16 amount;
f32 spawntime;
v3f *minpos;
v3f *maxpos;
v3f *minvel;
v3f *maxvel;
v3f *minacc;
v3f *maxacc;
f32 minexptime;
f32 maxexptime;
f32 minsize;
f32 maxsize;
bool collisiondetection;
bool vertical;
std::string *texture;
u32 id;
} add_particlespawner;
struct{
u32 id;
} delete_particlespawner;
struct{
u32 id;
u8 type;
v2f *pos;
std::string *name;
v2f *scale;
std::string *text;
u32 number;
u32 item;
u32 dir;
v2f *align;
v2f *offset;
v3f *world_pos;
v2s32 * size;
} hudadd;
struct{
u32 id;
} hudrm;
struct{
u32 id;
HudElementStat stat;
v2f *v2fdata;
std::string *sdata;
u32 data;
v3f *v3fdata;
v2s32 * v2s32data;
} hudchange;
struct{
video::SColor *bgcolor;
std::string *type;
std::vector<std::string> *params;
} set_sky;
struct{
bool do_override;
float ratio_f;
} override_day_night_ratio;
};
};
使用
ClientEvent Client::getClientEvent()
{
…
return m_client_event_queue.pop_front();
}
the_game() 循环体中循环执行客户端事件
{
for(;;)
{
// Read client events
for(;;)
{
ClientEvent event = client.getClientEvent();
}
}
…
}
14.m_client_event_queue 理解 共有两处 (minetest->environment.h)
std::list<ClientEnvEvent> m_client_event_queue;
enum ClientEnvEventType
{
CEE_NONE,
CEE_PLAYER_DAMAGE,
CEE_PLAYER_BREATH
};
struct ClientEnvEvent
{
ClientEnvEventType type;
union {
struct{
} none;
struct{
u8 amount;
bool send_to_server;
} player_damage;
struct{
u16 amount;
} player_breath;
};
};
Client->step()中使用,在Handle environment时循环执行完所有的环境event队列事件。
for(;;)
{
ClientEnvEvent event = m_env.getClientEvent();
if(event.type == CEE_NONE) break;
else if(event.type == CEE_PLAYER_DAMAGE)
else if(event.type == CEE_PLAYER_BREATH)
。。。
}
客户端environment的事件都是在environment的step中设置进去的。只有client的step中才会执行到environment的step。