//========= 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 #include #include 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;iRunFrame(); } 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;iRefreshed() ) { 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++; } }