sys_lobby_migrate.cpp 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546
  1. /*
  2. ===========================================================================
  3. Doom 3 BFG Edition GPL Source Code
  4. Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
  5. This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
  6. Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify
  7. it under the terms of the GNU General Public License as published by
  8. the Free Software Foundation, either version 3 of the License, or
  9. (at your option) any later version.
  10. Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful,
  11. but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. GNU General Public License for more details.
  14. You should have received a copy of the GNU General Public License
  15. along with Doom 3 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>.
  16. In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below.
  17. If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
  18. ===========================================================================
  19. */
  20. #pragma hdrstop
  21. #include "../idlib/precompiled.h"
  22. #include "sys_lobby.h"
  23. idCVar net_migration_debug( "net_migration_debug", "0", CVAR_BOOL, "debug" );
  24. idCVar net_migration_disable( "net_migration_disable", "0", CVAR_BOOL, "debug" );
  25. idCVar net_migration_forcePeerAsHost( "net_migration_forcePeerAsHost", "-1", CVAR_INTEGER, "When set to >-1, it forces that peer number to be the new host during migration" );
  26. /*
  27. ========================
  28. idLobby::IsBetterHost
  29. ========================
  30. */
  31. bool idLobby::IsBetterHost( int ping1, lobbyUserID_t userId1, int ping2, lobbyUserID_t userId2 ) {
  32. if ( lobbyType == TYPE_PARTY ) {
  33. return userId1 < userId2; // Only use user id for party, since ping doesn't matter
  34. }
  35. if ( ping1 < ping2 ) {
  36. // Better ping wins
  37. return true;
  38. } else if ( ping1 == ping2 && userId1 < userId2 ) {
  39. // User id is tie breaker
  40. return true;
  41. }
  42. return false;
  43. }
  44. /*
  45. ========================
  46. idLobby::FindMigrationInviteIndex
  47. ========================
  48. */
  49. int idLobby::FindMigrationInviteIndex( lobbyAddress_t & address ) {
  50. if ( migrationInfo.state == MIGRATE_NONE ) {
  51. return -1;
  52. }
  53. for ( int i = 0; i < migrationInfo.invites.Num(); i++ ) {
  54. if ( migrationInfo.invites[i].address.Compare( address, true ) ) {
  55. return i;
  56. }
  57. }
  58. return -1;
  59. }
  60. /*
  61. ========================
  62. idLobby::UpdateHostMigration
  63. ========================
  64. */
  65. void idLobby::UpdateHostMigration() {
  66. int time = Sys_Milliseconds();
  67. // If we are picking a new host, then update that
  68. if ( migrationInfo.state == MIGRATE_PICKING_HOST ) {
  69. const int MIGRATION_PICKING_HOST_TIMEOUT_IN_SECONDS = 20; // FIXME: set back to 5 // Give other hosts 5 seconds
  70. if ( time - migrationInfo.migrationStartTime > session->GetTitleStorageInt( "MIGRATION_PICKING_HOST_TIMEOUT_IN_SECONDS", MIGRATION_PICKING_HOST_TIMEOUT_IN_SECONDS ) * 1000 ) {
  71. // Just become the host if we haven't heard from a host in awhile
  72. BecomeHost();
  73. } else {
  74. return;
  75. }
  76. }
  77. // See if we are a new migrated host that needs to invite the original members back
  78. if ( migrationInfo.state != MIGRATE_BECOMING_HOST ) {
  79. return;
  80. }
  81. if ( lobbyBackend == NULL || lobbyBackend->GetState() != idLobbyBackend::STATE_READY ) {
  82. return;
  83. }
  84. if ( state != STATE_IDLE ) {
  85. return;
  86. }
  87. if ( !IsHost() ) {
  88. return;
  89. }
  90. const int MIGRATION_TIMEOUT_IN_SECONDS = 30; // FIXME: setting to 30 for dev purposes. 10 seems more reasonable. Need to make unloading game / loading lobby async
  91. const int MIGRATION_INVITE_TIME_IN_SECONDS = 2;
  92. if ( migrationInfo.invites.Num() == 0 || time - migrationInfo.migrationStartTime > session->GetTitleStorageInt( "MIGRATION_TIMEOUT_IN_SECONDS", MIGRATION_TIMEOUT_IN_SECONDS ) * 1000 ) {
  93. // Either everyone acked, or we timed out, just keep who we have, and stop sending invites
  94. EndMigration();
  95. return;
  96. }
  97. // Send invites to anyone who hasn't responded
  98. for ( int i = 0; i < migrationInfo.invites.Num(); i++ ) {
  99. if ( time - migrationInfo.invites[i].lastInviteTime < session->GetTitleStorageInt( "MIGRATION_INVITE_TIME_IN_SECONDS", MIGRATION_INVITE_TIME_IN_SECONDS ) * 1000 ) {
  100. continue; // Not enough time passed
  101. }
  102. // Mark the time
  103. migrationInfo.invites[i].lastInviteTime = time;
  104. byte buffer[ idPacketProcessor::MAX_PACKET_SIZE - 2 ];
  105. idBitMsg outmsg( buffer, sizeof( buffer ) );
  106. // Have lobbyBackend fill out msg with connection info
  107. lobbyConnectInfo_t connectInfo = lobbyBackend->GetConnectInfo();
  108. connectInfo.WriteToMsg( outmsg );
  109. // Let them know whether or not this was from in game
  110. outmsg.WriteBool( migrationInfo.persistUntilGameEndsData.wasMigratedGame );
  111. NET_VERBOSE_PRINT( "NET: Sending migration invite to %s\n", migrationInfo.invites[i].address.ToString() );
  112. // Send the migration invite
  113. SendConnectionLess( migrationInfo.invites[i].address, OOB_MIGRATE_INVITE, outmsg.GetReadData(), outmsg.GetSize() );
  114. }
  115. }
  116. /*
  117. ========================
  118. idLobby::BuildMigrationInviteList
  119. ========================
  120. */
  121. void idLobby::BuildMigrationInviteList( bool inviteOldHost ) {
  122. migrationInfo.invites.Clear();
  123. // Build a list of addresses we will send invites to (gather all unique remote addresses from the session user list)
  124. for ( int i = 0; i < GetNumLobbyUsers(); i++ ) {
  125. lobbyUser_t * user = GetLobbyUser( i );
  126. if ( !verify( user != NULL ) ) {
  127. continue;
  128. }
  129. if ( user->IsDisconnected() ) {
  130. continue;
  131. }
  132. if ( IsSessionUserIndexLocal( i ) ) {
  133. migrationInfo.ourPingMs = user->pingMs;
  134. migrationInfo.ourUserId = user->lobbyUserID;
  135. migrationInfo.persistUntilGameEndsData.ourGameData = user->migrationGameData;
  136. NET_VERBOSE_PRINT( "^2NET: Migration game data for local user is index %d \n", user->migrationGameData );
  137. continue; // Only interested in remote users
  138. }
  139. if ( !inviteOldHost && user->peerIndex == -1 ) {
  140. continue; // Don't invite old host if told not to do so
  141. }
  142. if ( FindMigrationInviteIndex( user->address ) == -1 ) {
  143. migrationInvite_t invite;
  144. invite.address = user->address;
  145. invite.pingMs = user->pingMs;
  146. invite.userId = user->lobbyUserID;
  147. invite.migrationGameData = user->migrationGameData;
  148. invite.lastInviteTime = 0;
  149. NET_VERBOSE_PRINT( "^2NET: Migration game data for user %s is index %d \n", user->gamertag, user->migrationGameData );
  150. migrationInfo.invites.Append( invite );
  151. }
  152. }
  153. }
  154. /*
  155. ========================
  156. idLobby::PickNewHost
  157. ========================
  158. */
  159. void idLobby::PickNewHost( bool forceMe, bool inviteOldHost ) {
  160. if ( IsHost() ) {
  161. idLib::Printf( "PickNewHost: Already host of session %s\n", GetLobbyName() );
  162. return;
  163. }
  164. sessionCB->PrePickNewHost( *this, forceMe, inviteOldHost );
  165. }
  166. /*
  167. ========================
  168. idLobby::PickNewHostInternal
  169. ========================
  170. */
  171. void idLobby::PickNewHostInternal( bool forceMe, bool inviteOldHost ) {
  172. if ( migrationInfo.state == MIGRATE_PICKING_HOST ) {
  173. return; // Already picking new host
  174. }
  175. idLib::Printf( "PickNewHost: Started picking new host %s.\n", GetLobbyName() );
  176. if ( IsHost() ) {
  177. idLib::Printf( "PickNewHost: Already host of session %s\n", GetLobbyName() );
  178. return;
  179. }
  180. // Find the user with the lowest ping
  181. int bestUserIndex = -1;
  182. int bestPingMs = 0;
  183. lobbyUserID_t bestUserId;
  184. for ( int i = 0; i < GetNumLobbyUsers(); i++ ) {
  185. lobbyUser_t * user = GetLobbyUser( i );
  186. if ( !verify( user != NULL ) ) {
  187. continue;
  188. }
  189. if ( user->IsDisconnected() ) {
  190. continue;
  191. }
  192. if ( user->peerIndex == -1 ) {
  193. continue; // Don't try and pick old host
  194. }
  195. if ( bestUserIndex == -1 || IsBetterHost( user->pingMs, user->lobbyUserID, bestPingMs, bestUserId ) ) {
  196. bestUserIndex = i;
  197. bestPingMs = user->pingMs;
  198. bestUserId = user->lobbyUserID;
  199. }
  200. if ( user->peerIndex == net_migration_forcePeerAsHost.GetInteger() ) {
  201. bestUserIndex = i;
  202. bestPingMs = user->pingMs;
  203. bestUserId = user->lobbyUserID;
  204. break;
  205. }
  206. }
  207. // Remember when we first started picking a new host
  208. migrationInfo.state = MIGRATE_PICKING_HOST;
  209. migrationInfo.migrationStartTime = Sys_Milliseconds();
  210. migrationInfo.persistUntilGameEndsData.wasMigratedGame = sessionCB->GetState() == idSession::INGAME;
  211. if ( bestUserIndex == -1 ) { // This can happen if we call PickNewHost on an lobby that was Shutdown
  212. NET_VERBOSE_PRINT( "MIGRATION: PickNewHost was called on an lobby that was Shutdown\n" );
  213. BecomeHost();
  214. return;
  215. }
  216. NET_VERBOSE_PRINT( "MIGRATION: Chose user index %d (%s) for new host\n", bestUserIndex, GetLobbyUser( bestUserIndex )->gamertag );
  217. bool bestWasLocal = IsSessionUserIndexLocal( bestUserIndex ); // Check before shutting down the lobby
  218. migrateMsgFlags = parms.matchFlags; // Save off match parms
  219. // Build invite list
  220. BuildMigrationInviteList( inviteOldHost );
  221. // If the best user is on this machine, then we become the host now, otherwise, wait for a new host to contact us
  222. if ( forceMe || bestWasLocal ) {
  223. BecomeHost();
  224. }
  225. }
  226. /*
  227. ========================
  228. idLobby::BecomeHost
  229. ========================
  230. */
  231. void idLobby::BecomeHost() {
  232. if ( !verify( migrationInfo.state == MIGRATE_PICKING_HOST ) ) {
  233. idLib::Printf( "BecomeHost: Must be called from PickNewHost.\n" );
  234. EndMigration();
  235. return;
  236. }
  237. if ( IsHost() ) {
  238. idLib::Printf( "BecomeHost: Already host of session.\n" );
  239. EndMigration();
  240. return;
  241. }
  242. if ( !sessionCB->BecomingHost( *this ) ) {
  243. EndMigration();
  244. return;
  245. }
  246. idLib::Printf( "BecomeHost: Sending %i invites on %s.\n", migrationInfo.invites.Num(), GetLobbyName() );
  247. migrationInfo.state = MIGRATE_BECOMING_HOST;
  248. migrationInfo.migrationStartTime = Sys_Milliseconds();
  249. if ( lobbyBackend == NULL ) {
  250. // If we don't have a lobbyBackend, then just create one
  251. Shutdown();
  252. StartCreating();
  253. return;
  254. }
  255. // Shutdown the current lobby, but keep the lobbyBackend (we'll migrate it)
  256. Shutdown( true );
  257. // Migrate the lobbyBackend to host
  258. lobbyBackend->BecomeHost( migrationInfo.invites.Num() );
  259. // Wait for it to complete
  260. SetState( STATE_CREATE_LOBBY_BACKEND );
  261. }
  262. /*
  263. ========================
  264. idLobby::EndMigration
  265. This gets called when we are done migrating, and invites will no longer be sent out.
  266. ========================
  267. */
  268. void idLobby::EndMigration() {
  269. if ( migrationInfo.state == MIGRATE_NONE ) {
  270. idLib::Printf( "idSessionLocal::EndMigration: Not migrating.\n" );
  271. return;
  272. }
  273. sessionCB->MigrationEnded( *this );
  274. if ( lobbyBackend != NULL ) {
  275. lobbyBackend->FinishBecomeHost();
  276. }
  277. migrationInfo.state = MIGRATE_NONE;
  278. migrationInfo.invites.Clear();
  279. }
  280. /*
  281. ========================
  282. idLobby::ResetAllMigrationState
  283. This will reset all state related to host migration. Should be called
  284. at match end so our next game is not treated as a migrated game
  285. ========================
  286. */
  287. void idLobby::ResetAllMigrationState() {
  288. migrationInfo.state = MIGRATE_NONE;
  289. migrationInfo.invites.Clear();
  290. migrationInfo.persistUntilGameEndsData.Clear();
  291. migrateMsgFlags = 0;
  292. common->Dialog().ClearDialog( GDM_MIGRATING );
  293. common->Dialog().ClearDialog( GDM_MIGRATING_WAITING );
  294. common->Dialog().ClearDialog( GDM_MIGRATING_RELAUNCHING );
  295. }
  296. /*
  297. ========================
  298. idLobby::GetMigrationGameData
  299. This will setup the passed in idBitMsg to either read or write from the global migration game data buffer
  300. ========================
  301. */
  302. bool idLobby::GetMigrationGameData( idBitMsg &msg, bool reading ) {
  303. if ( reading ) {
  304. if ( !IsMigratedStatsGame() || !migrationInfo.persistUntilGameEndsData.wasMigratedHost ) {
  305. // This was not a migrated session, we have no migration data
  306. return false;
  307. }
  308. msg.InitRead( migrationInfo.persistUntilGameEndsData.gameData, sizeof( migrationInfo.persistUntilGameEndsData.gameData ) );
  309. } else {
  310. migrationInfo.persistUntilGameEndsData.hasGameData = true;
  311. memset( migrationInfo.persistUntilGameEndsData.gameData, 0, sizeof( migrationInfo.persistUntilGameEndsData.gameData ) );
  312. msg.InitWrite( migrationInfo.persistUntilGameEndsData.gameData, sizeof( migrationInfo.persistUntilGameEndsData.gameData ) );
  313. }
  314. return true;
  315. }
  316. /*
  317. ========================
  318. idLobby::GetMigrationGameDataUser
  319. This will setup the passed in idBitMsg to either read or write from the user's migration game data buffer
  320. ========================
  321. */
  322. bool idLobby::GetMigrationGameDataUser( lobbyUserID_t lobbyUserID, idBitMsg & msg, bool reading ) {
  323. const int userNum = GetLobbyUserIndexByID( lobbyUserID );
  324. if ( !verify( userNum >=0 && userNum < MAX_PLAYERS ) ) {
  325. return false;
  326. }
  327. lobbyUser_t * u = GetLobbyUser( userNum );
  328. if ( u != NULL ) {
  329. if ( reading ) {
  330. if ( !IsMigratedStatsGame() || !migrationInfo.persistUntilGameEndsData.wasMigratedHost ) {
  331. // This was not a migrated session, we have no migration data
  332. return false;
  333. }
  334. if ( u->migrationGameData >= 0 && u->migrationGameData < MAX_PLAYERS ) {
  335. msg.InitRead( migrationInfo.persistUntilGameEndsData.gameDataUser[ u->migrationGameData ], sizeof( migrationInfo.persistUntilGameEndsData.gameDataUser[ 0 ] ) );
  336. } else {
  337. // We don't have migration data for this user
  338. idLib::Warning( "No migration data for user %d in a migrated game (%d)", userNum, u->migrationGameData );
  339. return false;
  340. }
  341. } else {
  342. // Writing
  343. migrationInfo.persistUntilGameEndsData.hasGameData = true;
  344. u->migrationGameData = userNum;
  345. memset( migrationInfo.persistUntilGameEndsData.gameDataUser[ userNum ], 0, sizeof( migrationInfo.persistUntilGameEndsData.gameDataUser[0] ) );
  346. msg.InitWrite( migrationInfo.persistUntilGameEndsData.gameDataUser[ userNum ], sizeof( migrationInfo.persistUntilGameEndsData.gameDataUser[0] ) );
  347. }
  348. return true;
  349. }
  350. return false;
  351. }
  352. /*
  353. ========================
  354. idLobby::HandleMigrationGameData
  355. ========================
  356. */
  357. void idLobby::HandleMigrationGameData( idBitMsg & msg ) {
  358. // Receives game migration data from the server. Just save off the raw data. If we ever become host we'll let the game code read
  359. // that chunk in (we can't do anything with it now anyways: we don't have entities or any server code to read it in to)
  360. migrationInfo.persistUntilGameEndsData.hasGameData = true;
  361. // Reset each user's migration game data. If we don't receive new data for them in this msg, we don't want to use the old data
  362. for ( int i=0; i < GetNumLobbyUsers(); i++ ) {
  363. lobbyUser_t * u = GetLobbyUser( i );
  364. if ( u != NULL ) {
  365. u->migrationGameData = -1;
  366. }
  367. }
  368. msg.ReadData( migrationInfo.persistUntilGameEndsData.gameData, sizeof( migrationInfo.persistUntilGameEndsData.gameData ) );
  369. int numUsers = msg.ReadByte();
  370. int dataIndex=0;
  371. for ( int i=0; i < numUsers; i++ ) {
  372. lobbyUserID_t lobbyUserID;
  373. lobbyUserID.ReadFromMsg( msg );
  374. lobbyUser_t * user = GetLobbyUser( GetLobbyUserIndexByID( lobbyUserID ) );
  375. if ( user != NULL ) {
  376. NET_VERBOSE_PRINT( "NET: Got migration data[%d] for user %s\n", dataIndex, user->gamertag );
  377. user->migrationGameData = dataIndex;
  378. msg.ReadData( migrationInfo.persistUntilGameEndsData.gameDataUser[ dataIndex ], sizeof( migrationInfo.persistUntilGameEndsData.gameDataUser[ dataIndex ] ) );
  379. dataIndex++;
  380. }
  381. }
  382. }
  383. /*
  384. ========================
  385. idLobby::SendMigrationGameData
  386. ========================
  387. */
  388. void idLobby::SendMigrationGameData() {
  389. if ( net_migration_disable.GetBool() ) {
  390. return;
  391. }
  392. if ( sessionCB->GetState() != idSession::INGAME ) {
  393. return;
  394. }
  395. if ( !migrationInfo.persistUntilGameEndsData.hasGameData ) {
  396. // Haven't been given any migration game data yet
  397. return;
  398. }
  399. const int now = Sys_Milliseconds();
  400. if ( nextSendMigrationGameTime > now ) {
  401. return;
  402. }
  403. byte packetData[ idPacketProcessor::MAX_MSG_SIZE ];
  404. idBitMsg msg( packetData, sizeof(packetData) );
  405. // Write global data
  406. msg.WriteData( &migrationInfo.persistUntilGameEndsData.gameData, sizeof( migrationInfo.persistUntilGameEndsData.gameData ) );
  407. msg.WriteByte( GetNumLobbyUsers() );
  408. // Write user data
  409. for ( int userIndex = 0; userIndex < GetNumLobbyUsers(); ++userIndex ) {
  410. lobbyUser_t * u = GetLobbyUser( userIndex );
  411. if ( u->IsDisconnected() || u->migrationGameData < 0 ) {
  412. continue;
  413. }
  414. u->lobbyUserID.WriteToMsg( msg );
  415. msg.WriteData( migrationInfo.persistUntilGameEndsData.gameDataUser[ u->migrationGameData ], sizeof( migrationInfo.persistUntilGameEndsData.gameDataUser[ u->migrationGameData ] ) );
  416. }
  417. // Send to 1 peer
  418. for ( int i=0; i < peers.Num(); i++ ) {
  419. int peerToSend = ( nextSendMigrationGamePeer + i ) % peers.Num();
  420. if ( peers[ peerToSend ].IsConnected() && peers[ peerToSend ].loaded ) {
  421. if ( peers[ peerToSend ].packetProc->NumQueuedReliables() > idPacketProcessor::MAX_RELIABLE_QUEUE / 2 ) {
  422. // This is kind of a hack for development so we don't DC clients by sending them too many reliable migration messages
  423. // when they aren't responding. Doesn't seem like a horrible thing to have in a shipping product but is not necessary.
  424. NET_VERBOSE_PRINT("NET: Skipping reliable game migration data msg because client reliable queue is > half full\n");
  425. } else {
  426. if ( net_migration_debug.GetBool() ) {
  427. idLib::Printf( "NET: Sending migration game data to peer %d. size: %d\n", peerToSend, msg.GetSize() );
  428. }
  429. QueueReliableMessage( peerToSend, RELIABLE_MIGRATION_GAME_DATA, msg.GetReadData(), msg.GetSize() );
  430. }
  431. break;
  432. }
  433. }
  434. // Increment next send time / next send peer
  435. nextSendMigrationGamePeer++;
  436. if ( nextSendMigrationGamePeer >= peers.Num() ) {
  437. nextSendMigrationGamePeer = 0;
  438. }
  439. nextSendMigrationGameTime = now + MIGRATION_GAME_DATA_INTERVAL_MS;
  440. }