Note
基于 https://github.com/ddnet/ddnet e7746435948e58ed36ab062dfad91019b86dfaac 进行讨论。
在 Teeworlds 中,客户端显示的延迟全部在服务端计算后再发回客户端显示,服务端计算 延迟的逻辑如下:
... // src/engine/server/server.cpp#L1110 else if(Msg == NETMSG_INPUT) { CClient::CInput *pInput; int64 TagTime; m_aClients[ClientID].m_LastAckedSnapshot = Unpacker.GetInt(); // 从客户端数据包中取得上次的快照 ID int IntendedTick = Unpacker.GetInt(); int Size = Unpacker.GetInt(); // check for errors if(Unpacker.Error() || Size/4 > MAX_INPUT_SIZE) return; if(m_aClients[ClientID].m_LastAckedSnapshot > 0) m_aClients[ClientID].m_SnapRate = CClient::SNAPRATE_FULL; // 根据快照 ID 获取 TagTime,实际上是上次进行快照的时间 if(m_aClients[ClientID].m_Snapshots.Get(m_aClients[ClientID].m_LastAckedSnapshot, &TagTime, 0, 0) >= 0) // 根据当前时间以及上次快照时间计算延迟 m_aClients[ClientID].m_Latency = (int)(((time_get()-TagTime)*1000)/time_freq());
Note
函数 time_freq()
返回不同平台下 time_get 获得的时间单位同毫秒的比率。
函数 DoSnapshot
保存当前游戏的状态,需要关注的代码如下:
// src/engine/server/server.cpp#611 void CServer::DoSnapshot() { ... // src/engine/server/server.cpp#L686 m_aClients[i].m_Snapshots.Add(m_CurrentGameTick, time_get(), SnapshotSize, pData, 0); ... }
DoSnapshot
函数在 CServer::Run
中被调用,不少部分靠猜,
不一定对, CServer::Run
包含了整个服务端的主要流程,其中有个游戏循环
while(m_RunServer) { ... }
,游戏循环采用了一个叫 tick 的概念来计时:
// src/engine/server/server.cpp#1690 int CServer::Run() { ... // src/engine/server/server.cpp#1756 // start game { ... // src/engine/server/server.cpp#1761 m_GameStartTime = time_get(); // 记录游戏开始时间 ... while(m_RunServer) { ... set_new_tick(); int64 t = time_get(); // 记录循环开始时间 int NewTicks = 0; // 还不到一个 tick ... // 这里是加载地图以及其他看不懂的操作,大概是游戏交互的主要部分 // 循环开始后是否超过一个 SERVER_TICK_SPEED(50ms) while(t > TickStartTime(m_CurrentGameTick+1)) { // 是,则把游戏的 tick + 1 m_CurrentGameTick++; // 并认为这轮循环是一个新的 tick NewTicks++; ... } // snap game // 如果这是个新 tick if(NewTicks) { if(g_Config.m_SvHighBandwidth || (m_CurrentGameTick%2) == 0) // 如果不使用高带宽模式的配置,以及当前 tick 不是偶数的话,快照之 DoSnapshot(); ... } }
如上,循环一开始先把 NewTicks
置 0,并在 t
保存当前时间,之后进行某些我
没看懂的的操作,接着进行判断 while(t > TickStartTime(m_CurrentGameTick+1))
,
TickStartTime
函数如下:
// src/engine/server/server.cpp#452 int64 CServer::TickStartTime(int Tick) { // 游戏开始时间 + (传入的Tick 数换算成相同时间单位) / 50 return m_GameStartTime + (time_freq()*Tick)/SERVER_TICK_SPEED; }
传入的是 m_CurrentGameTick+1
,所以猜测函数得出的是,下一个 Tick 的时间戳,
同时猜测一个时间戳的单位为 SERVER_TICK_SPEED
,即 50 (单位大概是微秒?)。
如果这个循环开始的时间以及超过下个 Tick 的开始时间,说明现在处于新的 Tick 中了,
于是:
m_CurrentGameTick++; NewTicks++;
并视情况更新快照。
那么根据这个逻辑 Client 如果掉线一阵(比如网络突然断线3秒,这时候Server端计算出的这个Client的Ping会改变么?