2502: Improved dummy switching r=def- a=Fireball-Teeworlds

- Fix a use-after-free when there are no new snapshots for the cl_dummy tee after the switch: #2499 .
- When one of the tees is hooked, cycling through them will no longer show phantom hook for the other tee: https://youtu.be/mxVT4pdyGnU.
- Dummy switch might happen a bit quicker since it doesn't depend on receiving new snapshots.
- Simplified code: m_LastDummy2 is no more.



Co-authored-by: Fireball <fireball.teeworlds@gmail.com>
This commit is contained in:
bors[bot] 2020-07-14 06:31:43 +00:00 committed by GitHub
commit 69593a3191
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 98 additions and 81 deletions

View file

@ -221,6 +221,7 @@ public:
virtual void OnRconType(bool UsernameReq) = 0; virtual void OnRconType(bool UsernameReq) = 0;
virtual void OnRconLine(const char *pLine) = 0; virtual void OnRconLine(const char *pLine) = 0;
virtual void OnInit() = 0; virtual void OnInit() = 0;
virtual void InvalidateSnapshot() = 0;
virtual void OnNewSnapshot() = 0; virtual void OnNewSnapshot() = 0;
virtual void OnEnterGame() = 0; virtual void OnEnterGame() = 0;
virtual void OnShutdown() = 0; virtual void OnShutdown() = 0;

View file

@ -339,7 +339,6 @@ CClient::CClient() : m_DemoPlayer(&m_SnapshotDelta)
m_CurrentInput[0] = 0; m_CurrentInput[0] = 0;
m_CurrentInput[1] = 0; m_CurrentInput[1] = 0;
m_LastDummy = 0; m_LastDummy = 0;
m_LastDummy2 = 0;
mem_zero(&m_aInputs, sizeof(m_aInputs)); mem_zero(&m_aInputs, sizeof(m_aInputs));
@ -505,12 +504,6 @@ void CClient::SendInput()
if(m_PredTick[g_Config.m_ClDummy] <= 0) if(m_PredTick[g_Config.m_ClDummy] <= 0)
return; return;
if(m_LastDummy != (bool)g_Config.m_ClDummy)
{
m_LastDummy = g_Config.m_ClDummy;
GameClient()->OnDummySwap();
}
bool Force = false; bool Force = false;
// fetch input // fetch input
for(int Dummy = 0; Dummy < 2; Dummy++) for(int Dummy = 0; Dummy < 2; Dummy++)
@ -848,6 +841,9 @@ void CClient::DummyDisconnect(const char *pReason)
m_NetClient[CLIENT_DUMMY].Disconnect(pReason); m_NetClient[CLIENT_DUMMY].Disconnect(pReason);
g_Config.m_ClDummy = 0; g_Config.m_ClDummy = 0;
m_RconAuthed[1] = 0; m_RconAuthed[1] = 0;
m_aSnapshots[1][SNAP_CURRENT] = 0;
m_aSnapshots[1][SNAP_PREV] = 0;
m_ReceivedSnapshots[1] = 0;
m_DummyConnected = false; m_DummyConnected = false;
GameClient()->OnDummyDisconnect(); GameClient()->OnDummyDisconnect();
} }
@ -2574,8 +2570,15 @@ void CClient::Update()
Disconnect(); Disconnect();
} }
} }
else if(State() == IClient::STATE_ONLINE && m_ReceivedSnapshots[g_Config.m_ClDummy] >= 3) else if(State() == IClient::STATE_ONLINE)
{ {
if(m_LastDummy != (bool)g_Config.m_ClDummy)
{
// Invalidate references to !m_ClDummy snapshots
GameClient()->InvalidateSnapshot();
GameClient()->OnDummySwap();
}
if(m_ReceivedSnapshots[!g_Config.m_ClDummy] >= 3) if(m_ReceivedSnapshots[!g_Config.m_ClDummy] >= 3)
{ {
// switch dummy snapshot // switch dummy snapshot
@ -2605,92 +2608,100 @@ void CClient::Update()
} }
} }
// switch snapshot if(m_ReceivedSnapshots[g_Config.m_ClDummy] >= 3)
int Repredict = 0;
int64 Freq = time_freq();
int64 Now = m_GameTime[g_Config.m_ClDummy].Get(time_get());
int64 PredNow = m_PredictedTime.Get(time_get());
while(1)
{ {
CSnapshotStorage::CHolder *pCur = m_aSnapshots[g_Config.m_ClDummy][SNAP_CURRENT]; // switch snapshot
int64 TickStart = (pCur->m_Tick)*time_freq()/50; int Repredict = 0;
int64 Freq = time_freq();
int64 Now = m_GameTime[g_Config.m_ClDummy].Get(time_get());
int64 PredNow = m_PredictedTime.Get(time_get());
if(TickStart < Now) if(m_LastDummy != (bool)g_Config.m_ClDummy && m_aSnapshots[g_Config.m_ClDummy][SNAP_CURRENT] && m_aSnapshots[g_Config.m_ClDummy][SNAP_PREV])
{ {
CSnapshotStorage::CHolder *pNext = m_aSnapshots[g_Config.m_ClDummy][SNAP_CURRENT]->m_pNext; // Load snapshot for m_ClDummy
if(pNext) GameClient()->OnNewSnapshot();
Repredict = 1;
}
while(1)
{
CSnapshotStorage::CHolder *pCur = m_aSnapshots[g_Config.m_ClDummy][SNAP_CURRENT];
int64 TickStart = (pCur->m_Tick)*time_freq()/50;
if(TickStart < Now)
{ {
m_aSnapshots[g_Config.m_ClDummy][SNAP_PREV] = m_aSnapshots[g_Config.m_ClDummy][SNAP_CURRENT]; CSnapshotStorage::CHolder *pNext = m_aSnapshots[g_Config.m_ClDummy][SNAP_CURRENT]->m_pNext;
m_aSnapshots[g_Config.m_ClDummy][SNAP_CURRENT] = pNext; if(pNext)
// set ticks
m_CurGameTick[g_Config.m_ClDummy] = m_aSnapshots[g_Config.m_ClDummy][SNAP_CURRENT]->m_Tick;
m_PrevGameTick[g_Config.m_ClDummy] = m_aSnapshots[g_Config.m_ClDummy][SNAP_PREV]->m_Tick;
if(m_LastDummy2 == (bool)g_Config.m_ClDummy && m_aSnapshots[g_Config.m_ClDummy][SNAP_CURRENT] && m_aSnapshots[g_Config.m_ClDummy][SNAP_PREV])
{ {
GameClient()->OnNewSnapshot(); m_aSnapshots[g_Config.m_ClDummy][SNAP_PREV] = m_aSnapshots[g_Config.m_ClDummy][SNAP_CURRENT];
Repredict = 1; m_aSnapshots[g_Config.m_ClDummy][SNAP_CURRENT] = pNext;
// set ticks
m_CurGameTick[g_Config.m_ClDummy] = m_aSnapshots[g_Config.m_ClDummy][SNAP_CURRENT]->m_Tick;
m_PrevGameTick[g_Config.m_ClDummy] = m_aSnapshots[g_Config.m_ClDummy][SNAP_PREV]->m_Tick;
if(m_aSnapshots[g_Config.m_ClDummy][SNAP_CURRENT] && m_aSnapshots[g_Config.m_ClDummy][SNAP_PREV])
{
GameClient()->OnNewSnapshot();
Repredict = 1;
}
} }
else
break;
} }
else else
break; break;
} }
else
break;
}
if(m_LastDummy2 != (bool)g_Config.m_ClDummy)
{
m_LastDummy2 = g_Config.m_ClDummy;
}
if(m_aSnapshots[g_Config.m_ClDummy][SNAP_CURRENT] && m_aSnapshots[g_Config.m_ClDummy][SNAP_PREV]) if(m_aSnapshots[g_Config.m_ClDummy][SNAP_CURRENT] && m_aSnapshots[g_Config.m_ClDummy][SNAP_PREV])
{
int64 CurtickStart = (m_aSnapshots[g_Config.m_ClDummy][SNAP_CURRENT]->m_Tick)*time_freq()/50;
int64 PrevtickStart = (m_aSnapshots[g_Config.m_ClDummy][SNAP_PREV]->m_Tick)*time_freq()/50;
int PrevPredTick = (int)(PredNow*50/time_freq());
int NewPredTick = PrevPredTick+1;
m_GameIntraTick[g_Config.m_ClDummy] = (Now - PrevtickStart) / (float)(CurtickStart-PrevtickStart);
m_GameTickTime[g_Config.m_ClDummy] = (Now - PrevtickStart) / (float)Freq; //(float)SERVER_TICK_SPEED);
CurtickStart = NewPredTick*time_freq()/50;
PrevtickStart = PrevPredTick*time_freq()/50;
m_PredIntraTick[g_Config.m_ClDummy] = (PredNow - PrevtickStart) / (float)(CurtickStart-PrevtickStart);
if(NewPredTick < m_aSnapshots[g_Config.m_ClDummy][SNAP_PREV]->m_Tick-SERVER_TICK_SPEED || NewPredTick > m_aSnapshots[g_Config.m_ClDummy][SNAP_PREV]->m_Tick+SERVER_TICK_SPEED)
{ {
m_pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "client", "prediction time reset!"); int64 CurtickStart = (m_aSnapshots[g_Config.m_ClDummy][SNAP_CURRENT]->m_Tick)*time_freq()/50;
m_PredictedTime.Init(m_aSnapshots[g_Config.m_ClDummy][SNAP_CURRENT]->m_Tick*time_freq()/50); int64 PrevtickStart = (m_aSnapshots[g_Config.m_ClDummy][SNAP_PREV]->m_Tick)*time_freq()/50;
int PrevPredTick = (int)(PredNow*50/time_freq());
int NewPredTick = PrevPredTick+1;
m_GameIntraTick[g_Config.m_ClDummy] = (Now - PrevtickStart) / (float)(CurtickStart-PrevtickStart);
m_GameTickTime[g_Config.m_ClDummy] = (Now - PrevtickStart) / (float)Freq; //(float)SERVER_TICK_SPEED);
CurtickStart = NewPredTick*time_freq()/50;
PrevtickStart = PrevPredTick*time_freq()/50;
m_PredIntraTick[g_Config.m_ClDummy] = (PredNow - PrevtickStart) / (float)(CurtickStart-PrevtickStart);
if(NewPredTick < m_aSnapshots[g_Config.m_ClDummy][SNAP_PREV]->m_Tick-SERVER_TICK_SPEED || NewPredTick > m_aSnapshots[g_Config.m_ClDummy][SNAP_PREV]->m_Tick+SERVER_TICK_SPEED)
{
m_pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "client", "prediction time reset!");
m_PredictedTime.Init(m_aSnapshots[g_Config.m_ClDummy][SNAP_CURRENT]->m_Tick*time_freq()/50);
}
if(NewPredTick > m_PredTick[g_Config.m_ClDummy])
{
m_PredTick[g_Config.m_ClDummy] = NewPredTick;
Repredict = 1;
// send input
SendInput();
}
} }
if(NewPredTick > m_PredTick[g_Config.m_ClDummy]) // only do sane predictions
if(Repredict)
{ {
m_PredTick[g_Config.m_ClDummy] = NewPredTick; if(m_PredTick[g_Config.m_ClDummy] > m_CurGameTick[g_Config.m_ClDummy] && m_PredTick[g_Config.m_ClDummy] < m_CurGameTick[g_Config.m_ClDummy]+50)
Repredict = 1; GameClient()->OnPredict();
}
// send input // fetch server info if we don't have it
SendInput(); if(State() >= IClient::STATE_LOADING &&
m_CurrentServerInfoRequestTime >= 0 &&
time_get() > m_CurrentServerInfoRequestTime)
{
m_ServerBrowser.RequestCurrentServer(m_ServerAddress);
m_CurrentServerInfoRequestTime = time_get()+time_freq()*2;
} }
} }
// only do sane predictions m_LastDummy = (bool)g_Config.m_ClDummy;
if(Repredict)
{
if(m_PredTick[g_Config.m_ClDummy] > m_CurGameTick[g_Config.m_ClDummy] && m_PredTick[g_Config.m_ClDummy] < m_CurGameTick[g_Config.m_ClDummy]+50)
GameClient()->OnPredict();
}
// fetch server info if we don't have it
if(State() >= IClient::STATE_LOADING &&
m_CurrentServerInfoRequestTime >= 0 &&
time_get() > m_CurrentServerInfoRequestTime)
{
m_ServerBrowser.RequestCurrentServer(m_ServerAddress);
m_CurrentServerInfoRequestTime = time_get()+time_freq()*2;
}
} }
// STRESS TEST: join the server again // STRESS TEST: join the server again

View file

@ -186,7 +186,6 @@ class CClient : public IClient, public CDemoPlayer::IListener
int m_CurrentInput[2]; int m_CurrentInput[2];
bool m_LastDummy; bool m_LastDummy;
bool m_LastDummy2;
bool m_DummySendConnInfo; bool m_DummySendConnInfo;
// graphs // graphs

View file

@ -511,10 +511,10 @@ void CGameClient::OnConnected()
void CGameClient::OnReset() void CGameClient::OnReset()
{ {
// clear out the invalid pointers
m_LastNewPredictedTick[0] = -1; m_LastNewPredictedTick[0] = -1;
m_LastNewPredictedTick[1] = -1; m_LastNewPredictedTick[1] = -1;
mem_zero(&g_GameClient.m_Snap, sizeof(g_GameClient.m_Snap));
InvalidateSnapshot();
for(int i = 0; i < MAX_CLIENTS; i++) for(int i = 0; i < MAX_CLIENTS; i++)
m_aClients[i].Reset(); m_aClients[i].Reset();
@ -1084,13 +1084,18 @@ static CGameInfo GetGameInfo(const CNetObj_GameInfoEx *pInfoEx, int InfoExSize,
return Info; return Info;
} }
void CGameClient::OnNewSnapshot() void CGameClient::InvalidateSnapshot()
{ {
m_NewTick = true; // clear all pointers
// clear out the invalid pointers
mem_zero(&g_GameClient.m_Snap, sizeof(g_GameClient.m_Snap)); mem_zero(&g_GameClient.m_Snap, sizeof(g_GameClient.m_Snap));
m_Snap.m_LocalClientID = -1; m_Snap.m_LocalClientID = -1;
}
void CGameClient::OnNewSnapshot()
{
InvalidateSnapshot();
m_NewTick = true;
// secure snapshot // secure snapshot
{ {

View file

@ -346,6 +346,7 @@ public:
virtual void OnConsoleInit(); virtual void OnConsoleInit();
virtual void OnStateChange(int NewState, int OldState); virtual void OnStateChange(int NewState, int OldState);
virtual void OnMessage(int MsgId, CUnpacker *pUnpacker, bool IsDummy = 0); virtual void OnMessage(int MsgId, CUnpacker *pUnpacker, bool IsDummy = 0);
virtual void InvalidateSnapshot();
virtual void OnNewSnapshot(); virtual void OnNewSnapshot();
virtual void OnPredict(); virtual void OnPredict();
virtual void OnActivateEditor(); virtual void OnActivateEditor();