Modified source engine (2017) developed by valve and leaked in 2020. Not for commercial purporses
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

447 lines
11 KiB

5 years ago
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================
#include "IServerRefreshResponse.h"
#include "ServerList.h"
//#include "ServerMsgHandlerDetails.h"
#include "Socket.h"
#include "proto_oob.h"
// for debugging
#include <VGUI_Controls.h>
#include <VGUI_ISystem.h>
#include <VGUI_IVGui.h>
typedef enum
{
NONE = 0,
INFO_REQUESTED,
INFO_RECEIVED
} QUERYSTATUS;
extern void v_strncpy(char *dest, const char *src, int bufsize);
#define min(a,b) (((a) < (b)) ? (a) : (b))
//-----------------------------------------------------------------------------
// Purpose: Comparison function used in query redblack tree
//-----------------------------------------------------------------------------
bool QueryLessFunc( const query_t &item1, const query_t &item2 )
{
// compare port then ip
if (item1.addr.port < item2.addr.port)
return true;
else if (item1.addr.port > item2.addr.port)
return false;
int ip1 = *(int *)&item1.addr.ip;
int ip2 = *(int *)&item2.addr.ip;
return ip1 < ip2;
}
//-----------------------------------------------------------------------------
// Purpose: Constructor
//-----------------------------------------------------------------------------
CServerList::CServerList(IServerRefreshResponse *target) : m_Queries(0, MAX_QUERY_SOCKETS, QueryLessFunc)
{
m_pResponseTarget = target;
m_iUpdateSerialNumber = 1;
// calculate max sockets based on users' rate
char speedBuf[32];
int internetSpeed;
if (!vgui::system()->GetRegistryString("HKEY_CURRENT_USER\\Software\\Valve\\Tracker\\Rate", speedBuf, sizeof(speedBuf)-1))
{
// default to DSL speed if no reg key found (an unlikely occurance)
strcpy(speedBuf, "7500");
}
internetSpeed = atoi(speedBuf);
int maxSockets = (MAX_QUERY_SOCKETS * internetSpeed) / 10000;
if (internetSpeed < 6000)
{
// reduce the number of active queries again for slow internet speeds
maxSockets /= 2;
}
m_nMaxActive = maxSockets;
m_nRampUpSpeed = 1;
m_bQuerying = false;
m_nMaxRampUp = 1;
m_nInvalidServers = 0;
m_nRefreshedServers = 0;
}
//-----------------------------------------------------------------------------
// Purpose: Destructor
//-----------------------------------------------------------------------------
CServerList::~CServerList()
{
// delete m_pQuery;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CServerList::RunFrame()
{
for(int i=0;i<m_Servers.Count();i++) {
m_Servers[i]->RunFrame();
}
QueryFrame();
}
//-----------------------------------------------------------------------------
// Purpose: gets a server from the list by id, range [0, ServerCount)
//-----------------------------------------------------------------------------
serveritem_t &CServerList::GetServer(unsigned int serverID)
{
if (m_Servers.IsValidIndex(serverID))
{
return m_Servers[serverID]->GetServer();
}
// return a dummy
static serveritem_t dummyServer;
memset(&dummyServer, 0, sizeof(dummyServer));
return dummyServer;
}
//-----------------------------------------------------------------------------
// Purpose: returns the number of servers
//-----------------------------------------------------------------------------
int CServerList::ServerCount()
{
return m_Servers.Count();
}
//-----------------------------------------------------------------------------
// Purpose: Returns the number of servers not yet pinged
//-----------------------------------------------------------------------------
int CServerList::RefreshListRemaining()
{
return m_RefreshList.Count();
}
//-----------------------------------------------------------------------------
// Purpose: returns true if the server list is still in the process of talking to servers
//-----------------------------------------------------------------------------
bool CServerList::IsRefreshing()
{
return m_bQuerying;
}
//-----------------------------------------------------------------------------
// Purpose: adds a new server to the list
//-----------------------------------------------------------------------------
unsigned int CServerList::AddNewServer(serveritem_t &server)
{
unsigned int serverID = m_Servers.AddToTail(new CServerInfo(this ,server));
m_Servers[serverID]->serverID = serverID;
return serverID;
}
//-----------------------------------------------------------------------------
// Purpose: Clears all servers from the list
//-----------------------------------------------------------------------------
void CServerList::Clear()
{
StopRefresh();
m_Servers.RemoveAll();
}
//-----------------------------------------------------------------------------
// Purpose: stops all refreshing
//-----------------------------------------------------------------------------
void CServerList::StopRefresh()
{
// Reset query context
m_Queries.RemoveAll();
// reset server received states
int count = ServerCount();
for (int i = 0; i < count; i++)
{
m_Servers[i]->received = 0;
}
m_RefreshList.RemoveAll();
// up the serial number so previous results don't interfere
m_iUpdateSerialNumber++;
m_nInvalidServers = 0;
m_nRefreshedServers = 0;
m_bQuerying = false;
m_nMaxRampUp = m_nRampUpSpeed;
}
//-----------------------------------------------------------------------------
// Purpose: marks a server to be refreshed
//-----------------------------------------------------------------------------
void CServerList::AddServerToRefreshList(unsigned int serverID)
{
if (!m_Servers.IsValidIndex(serverID))
return;
serveritem_t &server = m_Servers[serverID]->GetServer();
server.received = NONE;
m_RefreshList.AddToTail(serverID);
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CServerList::StartRefresh()
{
if (m_RefreshList.Count() > 0)
{
m_bQuerying = true;
}
}
void CServerList::ServerResponded()
{
for(int i=0;i<m_Servers.Count();i++) {
if ( m_Servers[i]->Refreshed() ) {
serveritem_t &server = m_Servers[i]->GetServer();
// copy in data necessary for filters
// add to ping times list
server.pings[0] = server.pings[1];
server.pings[1] = server.pings[2];
server.pings[2] = server.ping;
// calculate ping
int ping = CalculateAveragePing(server);
// make sure the ping changes each time so the user can see the server has updated
if (server.ping == ping && ping>0)
{
ping--;
}
server.ping = ping;
server.received = INFO_RECEIVED;
netadr_t adr;
adr.ip[0] = server.ip[0];
adr.ip[1] = server.ip[1];
adr.ip[2] = server.ip[2];
adr.ip[3] = server.ip[3];
adr.port = (server.port & 0xff) << 8 | (server.port & 0xff00) >> 8;
adr.type = NA_IP;
query_t finder;
finder.addr = adr;
m_Queries.Remove(finder);
// notify the UI of the new server info
m_pResponseTarget->ServerResponded(server);
}
}
}
//-----------------------------------------------------------------------------
// Purpose: called when a server response has timed out
//-----------------------------------------------------------------------------
void CServerList::ServerFailedToRespond()
{
}
//-----------------------------------------------------------------------------
// Purpose: recalculates a servers ping, from the last few ping times
//-----------------------------------------------------------------------------
int CServerList::CalculateAveragePing(serveritem_t &server)
{
if (server.pings[0])
{
// three pings, throw away any the most extreme and average the other two
int middlePing = 0, lowPing = 1, highPing = 2;
if (server.pings[0] < server.pings[1])
{
if (server.pings[0] > server.pings[2])
{
lowPing = 2;
middlePing = 0;
highPing = 1;
}
else if (server.pings[1] < server.pings[2])
{
lowPing = 0;
middlePing = 1;
highPing = 2;
}
else
{
lowPing = 0;
middlePing = 2;
highPing = 1;
}
}
else
{
if (server.pings[1] > server.pings[2])
{
lowPing = 2;
middlePing = 1;
highPing = 0;
}
else if (server.pings[0] < server.pings[2])
{
lowPing = 1;
middlePing = 0;
highPing = 2;
}
else
{
lowPing = 1;
middlePing = 2;
highPing = 0;
}
}
// we have the middle ping, see which it's closest to
if ((server.pings[middlePing] - server.pings[lowPing]) < (server.pings[highPing] - server.pings[middlePing]))
{
return (server.pings[middlePing] + server.pings[lowPing]) / 2;
}
else
{
return (server.pings[middlePing] + server.pings[highPing]) / 2;
}
}
else if (server.pings[1])
{
// two pings received, average them
return (server.pings[1] + server.pings[2]) / 2;
}
else
{
// only one ping received so far, use that
return server.pings[2];
}
}
//-----------------------------------------------------------------------------
// Purpose: Called every frame to check queries
//-----------------------------------------------------------------------------
void CServerList::QueryFrame()
{
if (!m_bQuerying)
return;
float curtime = CSocket::GetClock();
// walk the query list, looking for any server timeouts
unsigned short idx = m_Queries.FirstInorder();
while (m_Queries.IsValidIndex(idx))
{
query_t &query = m_Queries[idx];
if ((curtime - query.sendTime) > 1.2f)
{
// server has timed out
serveritem_t &item = m_Servers[query.serverID]->GetServer();
// mark the server
item.pings[0] = item.pings[1];
item.pings[1] = item.pings[2];
item.pings[2] = 1200;
item.ping = CalculateAveragePing(item);
if (!item.hadSuccessfulResponse)
{
// remove the server if it has never responded before
item.doNotRefresh = true;
m_nInvalidServers++;
}
// respond to the game list notifying of the lack of response
m_pResponseTarget->ServerFailedToRespond(item);
item.received = false;
// get the next server now, since we're about to delete it from query list
unsigned short nextidx = m_Queries.NextInorder(idx);
// delete the query
m_Queries.RemoveAt(idx);
// move to next item
idx = nextidx;
}
else
{
// still waiting for server result
idx = m_Queries.NextInorder(idx);
}
}
// increment the number of sockets to use
m_nMaxRampUp = min(m_nMaxActive, m_nMaxRampUp + m_nRampUpSpeed);
// see if we should send more queries
while (m_RefreshList.Count() > 0 && (int)m_Queries.Count() < m_nMaxRampUp)
{
// get the first item from the list to refresh
int currentServer = m_RefreshList[0];
if (!m_Servers.IsValidIndex(currentServer))
break;
serveritem_t &item = m_Servers[currentServer]->GetServer();
item.time = curtime;
//QueryServer(m_pQuery, currentServer);
m_Servers[currentServer]->Query();
query_t query;
netadr_t adr;
adr.ip[0] = item.ip[0];
adr.ip[1] = item.ip[1];
adr.ip[2] = item.ip[2];
adr.ip[3] = item.ip[3];
adr.port = (item.port & 0xff) << 8 | (item.port & 0xff00) >> 8;
adr.type = NA_IP;
query.addr =adr;
query.sendTime=curtime;
query.serverID=item.serverID;
m_Queries.Insert(query);
// remove the server from the refresh list
m_RefreshList.Remove((int)0);
}
// Done querying?
if (m_Queries.Count() < 1)
{
m_bQuerying = false;
m_pResponseTarget->RefreshComplete();
// up the serial number, so that we ignore any late results
m_iUpdateSerialNumber++;
}
}