Pyrogenesis  13997
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
NetServer.cpp
Go to the documentation of this file.
1 /* Copyright (C) 2011 Wildfire Games.
2  * This file is part of 0 A.D.
3  *
4  * 0 A.D. is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation, either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * 0 A.D. is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
16  */
17 
18 #include "precompiled.h"
19 
20 #include "NetServer.h"
21 
22 #include "NetClient.h"
23 #include "NetMessage.h"
24 #include "NetSession.h"
25 #include "NetStats.h"
26 #include "NetTurnManager.h"
27 
29 #include "ps/CLogger.h"
32 
33 #define DEFAULT_SERVER_NAME L"Unnamed Server"
34 #define DEFAULT_WELCOME_MESSAGE L"Welcome"
35 #define MAX_CLIENTS 8
36 
37 static const int CHANNEL_COUNT = 1;
38 
39 /**
40  * enet_host_service timeout (msecs).
41  * Smaller numbers may hurt performance; larger numbers will
42  * hurt latency responding to messages from game thread.
43  */
44 static const int HOST_SERVICE_TIMEOUT = 50;
45 
47 
48 static CStr DebugName(CNetServerSession* session)
49 {
50  if (session == NULL)
51  return "[unknown host]";
52  if (session->GetGUID().empty())
53  return "[unauthed host]";
54  return "[" + session->GetGUID().substr(0, 8) + "...]";
55 }
56 
57 /**
58  * Async task for receiving the initial game state to be forwarded to another
59  * client that is rejoining an in-progress network game.
60  */
62 {
64 public:
66  : m_Server(server), m_RejoinerHostID(hostID)
67  {
68  }
69 
70  virtual void OnComplete()
71  {
72  // We've received the game state from an existing player - now
73  // we need to send it onwards to the newly rejoining player
74 
75  // Find the session corresponding to the rejoining host (if any)
76  CNetServerSession* session = NULL;
77  for (size_t i = 0; i < m_Server.m_Sessions.size(); ++i)
78  {
79  if (m_Server.m_Sessions[i]->GetHostID() == m_RejoinerHostID)
80  {
81  session = m_Server.m_Sessions[i];
82  break;
83  }
84  }
85 
86  if (!session)
87  {
88  LOGMESSAGE(L"Net server: rejoining client disconnected before we sent to it");
89  return;
90  }
91 
92  // Store the received state file, and tell the client to start downloading it from us
93  // TODO: this will get kind of confused if there's multiple clients downloading in parallel;
94  // they'll race and get whichever happens to be the latest received by the server,
95  // which should still work but isn't great
97  CJoinSyncStartMessage message;
98  session->SendMessage(&message);
99  }
100 
101 private:
104 };
105 
106 /*
107  * XXX: We use some non-threadsafe functions from the worker thread.
108  * See http://trac.wildfiregames.com/ticket/654
109  */
110 
111 CNetServerWorker::CNetServerWorker(int autostartPlayers) :
112  m_AutostartPlayers(autostartPlayers),
113  m_Shutdown(false),
114  m_ScriptInterface(NULL),
115  m_NextHostID(1), m_Host(NULL), m_Stats(NULL)
116 {
118 
119  m_ServerTurnManager = NULL;
120 
123 }
124 
126 {
128  {
129  // Tell the thread to shut down
130  {
132  m_Shutdown = true;
133  }
134 
135  // Wait for it to shut down cleanly
137  }
138 
139  // Clean up resources
140 
141  delete m_Stats;
142 
143  for (size_t i = 0; i < m_Sessions.size(); ++i)
144  {
145  m_Sessions[i]->DisconnectNow(NDR_UNEXPECTED_SHUTDOWN);
146  delete m_Sessions[i];
147  }
148 
149  if (m_Host)
150  {
151  enet_host_destroy(m_Host);
152  }
153 
154  delete m_ServerTurnManager;
155 }
156 
158 {
160  ENSURE(!m_Host);
161 
162  // Bind to default host
163  ENetAddress addr;
164  addr.host = ENET_HOST_ANY;
165  addr.port = PS_DEFAULT_PORT;
166 
167  // Create ENet server
168  m_Host = enet_host_create(&addr, MAX_CLIENTS, CHANNEL_COUNT, 0, 0);
169  if (!m_Host)
170  {
171  LOGERROR(L"Net server: enet_host_create failed");
172  return false;
173  }
174 
175  m_Stats = new CNetStatsTable();
177  g_ProfileViewer.AddRootTable(m_Stats);
178 
180 
181  // Launch the worker thread
182  int ret = pthread_create(&m_WorkerThread, NULL, &RunThread, this);
183  ENSURE(ret == 0);
184 
185  return true;
186 }
187 
189 {
190  ENSURE(m_Host);
191 
192  CNetServerSession* session = static_cast<CNetServerSession*>(peer->data);
193 
194  return CNetHost::SendMessage(message, peer, DebugName(session).c_str());
195 }
196 
198 {
199  ENSURE(m_Host);
200 
201  bool ok = true;
202 
203  // Send to all sessions that are active and has finished authentication
204  for (size_t i = 0; i < m_Sessions.size(); ++i)
205  {
206  if (m_Sessions[i]->GetCurrState() == NSS_PREGAME || m_Sessions[i]->GetCurrState() == NSS_INGAME)
207  {
208  if (!m_Sessions[i]->SendMessage(message))
209  ok = false;
210 
211  // TODO: this does lots of repeated message serialisation if we have lots
212  // of remote peers; could do it more efficiently if that's a real problem
213  }
214  }
215 
216  return ok;
217 }
218 
220 {
221  debug_SetThreadName("NetServer");
222 
223  static_cast<CNetServerWorker*>(data)->Run();
224 
225  return NULL;
226 }
227 
229 {
230  // To avoid the need for JS_SetContextThread, we create and use and destroy
231  // the script interface entirely within this network thread
232  m_ScriptInterface = new ScriptInterface("Engine", "Net server", ScriptInterface::CreateRuntime());
233 
234  while (true)
235  {
236  if (!RunStep())
237  break;
238 
239  // Implement autostart mode
241  StartGame();
242 
243  // Update profiler stats
245  }
246 
247  // Clear roots before deleting their context
249  m_SavedCommands.clear();
250 
252 }
253 
255 {
256  // Check for messages from the game thread.
257  // (Do as little work as possible while the mutex is held open,
258  // to avoid performance problems and deadlocks.)
259 
260  std::vector<std::pair<int, CStr> > newAssignPlayer;
261  std::vector<bool> newStartGame;
262  std::vector<std::string> newGameAttributes;
263  std::vector<u32> newTurnLength;
264 
265  {
267 
268  if (m_Shutdown)
269  return false;
270 
271  newStartGame.swap(m_StartGameQueue);
272  newAssignPlayer.swap(m_AssignPlayerQueue);
273  newGameAttributes.swap(m_GameAttributesQueue);
274  newTurnLength.swap(m_TurnLengthQueue);
275  }
276 
277  for (size_t i = 0; i < newAssignPlayer.size(); ++i)
278  AssignPlayer(newAssignPlayer[i].first, newAssignPlayer[i].second);
279 
280  if (!newGameAttributes.empty())
281  UpdateGameAttributes(GetScriptInterface().ParseJSON(newGameAttributes.back()));
282 
283  if (!newTurnLength.empty())
284  SetTurnLength(newTurnLength.back());
285 
286  // Do StartGame last, so we have the most up-to-date game attributes when we start
287  if (!newStartGame.empty())
288  StartGame();
289 
290  // Perform file transfers
291  for (size_t i = 0; i < m_Sessions.size(); ++i)
292  m_Sessions[i]->GetFileTransferer().Poll();
293 
294  // Process network events:
295 
296  ENetEvent event;
297  int status = enet_host_service(m_Host, &event, HOST_SERVICE_TIMEOUT);
298  if (status < 0)
299  {
300  LOGERROR(L"CNetServerWorker: enet_host_service failed (%d)", status);
301  // TODO: notify game that the server has shut down
302  return false;
303  }
304 
305  if (status == 0)
306  {
307  // Reached timeout with no events - try again
308  return true;
309  }
310 
311  // Process the event:
312 
313  switch (event.type)
314  {
315  case ENET_EVENT_TYPE_CONNECT:
316  {
317  // Report the client address
318  char hostname[256] = "(error)";
319  enet_address_get_host_ip(&event.peer->address, hostname, ARRAY_SIZE(hostname));
320  LOGMESSAGE(L"Net server: Received connection from %hs:%u", hostname, (unsigned int)event.peer->address.port);
321 
322  // Set up a session object for this peer
323 
324  CNetServerSession* session = new CNetServerSession(*this, event.peer);
325 
326  m_Sessions.push_back(session);
327 
328  SetupSession(session);
329 
330  ENSURE(event.peer->data == NULL);
331  event.peer->data = session;
332 
333  HandleConnect(session);
334 
335  break;
336  }
337 
338  case ENET_EVENT_TYPE_DISCONNECT:
339  {
340  // If there is an active session with this peer, then reset and delete it
341 
342  CNetServerSession* session = static_cast<CNetServerSession*>(event.peer->data);
343  if (session)
344  {
345  LOGMESSAGE(L"Net server: Disconnected %hs", DebugName(session).c_str());
346 
347  // Remove the session first, so we won't send player-update messages to it
348  // when updating the FSM
349  m_Sessions.erase(remove(m_Sessions.begin(), m_Sessions.end(), session), m_Sessions.end());
350 
351  session->Update((uint)NMT_CONNECTION_LOST, NULL);
352 
353  delete session;
354  event.peer->data = NULL;
355  }
356 
357  break;
358  }
359 
360  case ENET_EVENT_TYPE_RECEIVE:
361  {
362  // If there is an active session with this peer, then process the message
363 
364  CNetServerSession* session = static_cast<CNetServerSession*>(event.peer->data);
365  if (session)
366  {
367  // Create message from raw data
368  CNetMessage* msg = CNetMessageFactory::CreateMessage(event.packet->data, event.packet->dataLength, GetScriptInterface());
369  if (msg)
370  {
371  LOGMESSAGE(L"Net server: Received message %hs of size %lu from %hs", msg->ToString().c_str(), (unsigned long)msg->GetSerializedLength(), DebugName(session).c_str());
372 
373  HandleMessageReceive(msg, session);
374 
375  delete msg;
376  }
377  }
378 
379  // Done using the packet
380  enet_packet_destroy(event.packet);
381 
382  break;
383  }
384 
385  case ENET_EVENT_TYPE_NONE:
386  break;
387  }
388 
389  return true;
390 }
391 
393 {
394  // Handle non-FSM messages first
395  Status status = session->GetFileTransferer().HandleMessageReceive(message);
396  if (status != INFO::SKIPPED)
397  return;
398 
399  if (message->GetType() == NMT_FILE_TRANSFER_REQUEST)
400  {
401  CFileTransferRequestMessage* reqMessage = (CFileTransferRequestMessage*)message;
402 
403  // Rejoining client got our JoinSyncStart after we received the state from
404  // another client, and has now requested that we forward it to them
405 
406  ENSURE(!m_JoinSyncFile.empty());
407  session->GetFileTransferer().StartResponse(reqMessage->m_RequestID, m_JoinSyncFile);
408 
409  return;
410  }
411 
412  // Update FSM
413  bool ok = session->Update(message->GetType(), (void*)message);
414  if (!ok)
415  LOGERROR(L"Net server: Error running FSM update (type=%d state=%d)", (int)message->GetType(), (int)session->GetCurrState());
416 }
417 
419 {
420  void* context = session;
421 
422  // Set up transitions for session
423 
425 
426  session->AddTransition(NSS_HANDSHAKE, (uint)NMT_CONNECTION_LOST, NSS_UNCONNECTED);
428 
429  session->AddTransition(NSS_AUTHENTICATE, (uint)NMT_CONNECTION_LOST, NSS_UNCONNECTED);
430  session->AddTransition(NSS_AUTHENTICATE, (uint)NMT_AUTHENTICATE, NSS_PREGAME, (void*)&OnAuthenticate, context);
431 
432  session->AddTransition(NSS_PREGAME, (uint)NMT_CONNECTION_LOST, NSS_UNCONNECTED, (void*)&OnDisconnect, context);
433  session->AddTransition(NSS_PREGAME, (uint)NMT_CHAT, NSS_PREGAME, (void*)&OnChat, context);
434  session->AddTransition(NSS_PREGAME, (uint)NMT_LOADED_GAME, NSS_INGAME, (void*)&OnLoadedGame, context);
435 
436  session->AddTransition(NSS_JOIN_SYNCING, (uint)NMT_LOADED_GAME, NSS_INGAME, (void*)&OnJoinSyncingLoadedGame, context);
437 
438  session->AddTransition(NSS_INGAME, (uint)NMT_CONNECTION_LOST, NSS_UNCONNECTED, (void*)&OnDisconnect, context);
439  session->AddTransition(NSS_INGAME, (uint)NMT_CHAT, NSS_INGAME, (void*)&OnChat, context);
440  session->AddTransition(NSS_INGAME, (uint)NMT_SIMULATION_COMMAND, NSS_INGAME, (void*)&OnInGame, context);
441  session->AddTransition(NSS_INGAME, (uint)NMT_SYNC_CHECK, NSS_INGAME, (void*)&OnInGame, context);
442  session->AddTransition(NSS_INGAME, (uint)NMT_END_COMMAND_BATCH, NSS_INGAME, (void*)&OnInGame, context);
443 
444  // Set first state
445  session->SetFirstState(NSS_HANDSHAKE);
446 }
447 
449 {
450  CSrvHandshakeMessage handshake;
451  handshake.m_Magic = PS_PROTOCOL_MAGIC;
452  handshake.m_ProtocolVersion = PS_PROTOCOL_VERSION;
453  handshake.m_SoftwareVersion = PS_PROTOCOL_VERSION;
454  return session->SendMessage(&handshake);
455 }
456 
458 {
459  AddPlayer(session->GetGUID(), session->GetUserName());
460 
461  CGameSetupMessage gameSetupMessage(GetScriptInterface());
462  gameSetupMessage.m_Data = m_GameAttributes;
463  session->SendMessage(&gameSetupMessage);
464 
465  CPlayerAssignmentMessage assignMessage;
466  ConstructPlayerAssignmentMessage(assignMessage);
467  session->SendMessage(&assignMessage);
468 }
469 
471 {
472  RemovePlayer(session->GetGUID());
473 
475  m_ServerTurnManager->UninitialiseClient(session->GetHostID()); // TODO: only for non-observers
476 
477  // TODO: ought to switch the player controlled by that client
478  // back to AI control, or something?
479 }
480 
481 void CNetServerWorker::AddPlayer(const CStr& guid, const CStrW& name)
482 {
483  // Find all player IDs in active use; we mustn't give them to a second player
484  std::set<i32> usedIDs;
485  for (PlayerAssignmentMap::iterator it = m_PlayerAssignments.begin(); it != m_PlayerAssignments.end(); ++it)
486  if (it->second.m_Enabled)
487  usedIDs.insert(it->second.m_PlayerID);
488 
489  // If the player is rejoining after disconnecting, try to give them
490  // back their old player ID
491 
492  i32 playerID = -1;
493  bool foundPlayerID = false;
494 
495  // Try to match GUID first
496  for (PlayerAssignmentMap::iterator it = m_PlayerAssignments.begin(); it != m_PlayerAssignments.end(); ++it)
497  {
498  if (!it->second.m_Enabled && it->first == guid && usedIDs.find(it->second.m_PlayerID) == usedIDs.end())
499  {
500  playerID = it->second.m_PlayerID;
501  foundPlayerID = true;
502  m_PlayerAssignments.erase(it); // delete the old mapping, since we've got a new one now
503  break;
504  }
505  }
506 
507  // Try to match username next
508  if (!foundPlayerID)
509  {
510  for (PlayerAssignmentMap::iterator it = m_PlayerAssignments.begin(); it != m_PlayerAssignments.end(); ++it)
511  {
512  if (!it->second.m_Enabled && it->second.m_Name == name && usedIDs.find(it->second.m_PlayerID) == usedIDs.end())
513  {
514  playerID = it->second.m_PlayerID;
515  foundPlayerID = true;
516  m_PlayerAssignments.erase(it); // delete the old mapping, since we've got a new one now
517  break;
518  }
519  }
520  }
521 
522  // Otherwise pick the first free player ID
523  if (!foundPlayerID)
524  {
525  for (playerID = 1; usedIDs.find(playerID) != usedIDs.end(); ++playerID)
526  {
527  // (do nothing)
528  }
529  }
530 
531  PlayerAssignment assignment;
532  assignment.m_Enabled = true;
533  assignment.m_Name = name;
534  assignment.m_PlayerID = playerID;
535  m_PlayerAssignments[guid] = assignment;
536 
537  // Send the new assignments to all currently active players
538  // (which does not include the one that's just joining)
540 }
541 
542 void CNetServerWorker::RemovePlayer(const CStr& guid)
543 {
544  m_PlayerAssignments[guid].m_Enabled = false;
545 
547 }
548 
549 void CNetServerWorker::AssignPlayer(int playerID, const CStr& guid)
550 {
551  // Remove anyone who's already assigned to this player
552  for (PlayerAssignmentMap::iterator it = m_PlayerAssignments.begin(); it != m_PlayerAssignments.end(); ++it)
553  {
554  if (it->second.m_PlayerID == playerID)
555  it->second.m_PlayerID = -1;
556  }
557 
558  // Update this host's assignment if it exists
559  if (m_PlayerAssignments.find(guid) != m_PlayerAssignments.end())
560  m_PlayerAssignments[guid].m_PlayerID = playerID;
561 
563 }
564 
565 void CNetServerWorker::ConstructPlayerAssignmentMessage(CPlayerAssignmentMessage& message)
566 {
567  for (PlayerAssignmentMap::iterator it = m_PlayerAssignments.begin(); it != m_PlayerAssignments.end(); ++it)
568  {
569  if (!it->second.m_Enabled)
570  continue;
571 
572  CPlayerAssignmentMessage::S_m_Hosts h;
573  h.m_GUID = it->first;
574  h.m_Name = it->second.m_Name;
575  h.m_PlayerID = it->second.m_PlayerID;
576  message.m_Hosts.push_back(h);
577  }
578 }
579 
581 {
582  CPlayerAssignmentMessage message;
584  Broadcast(&message);
585 }
586 
588 {
589  return *m_ScriptInterface;
590 }
591 
593 {
596 }
597 
599 {
600  ENSURE(event->GetType() == (uint)NMT_CLIENT_HANDSHAKE);
601 
602  CNetServerSession* session = (CNetServerSession*)context;
603  CNetServerWorker& server = session->GetServer();
604 
605  CCliHandshakeMessage* message = (CCliHandshakeMessage*)event->GetParamRef();
606  if (message->m_ProtocolVersion != PS_PROTOCOL_VERSION)
607  {
609  return false;
610  }
611 
612  CSrvHandshakeResponseMessage handshakeResponse;
613  handshakeResponse.m_UseProtocolVersion = PS_PROTOCOL_VERSION;
614  handshakeResponse.m_Message = server.m_WelcomeMessage;
615  handshakeResponse.m_Flags = 0;
616  session->SendMessage(&handshakeResponse);
617 
618  return true;
619 }
620 
621 bool CNetServerWorker::OnAuthenticate(void* context, CFsmEvent* event)
622 {
623  ENSURE(event->GetType() == (uint)NMT_AUTHENTICATE);
624 
625  CNetServerSession* session = (CNetServerSession*)context;
626  CNetServerWorker& server = session->GetServer();
627 
628  CAuthenticateMessage* message = (CAuthenticateMessage*)event->GetParamRef();
629 
630  CStrW username = server.DeduplicatePlayerName(SanitisePlayerName(message->m_Name));
631 
632  bool isRejoining = false;
633 
634  if (server.m_State != SERVER_STATE_PREGAME)
635  {
636 // isRejoining = true; // uncomment this to test rejoining even if the player wasn't connected previously
637 
638  // Search for an old disconnected player of the same name
639  // (TODO: if GUIDs were stable, we should use them instead)
640  for (PlayerAssignmentMap::iterator it = server.m_PlayerAssignments.begin(); it != server.m_PlayerAssignments.end(); ++it)
641  {
642  if (!it->second.m_Enabled && it->second.m_Name == username)
643  {
644  isRejoining = true;
645  break;
646  }
647  }
648 
649  // Players who weren't already in the game are not allowed to join now that it's started
650  if (!isRejoining)
651  {
652  LOGMESSAGE(L"Refused connection after game start from not-previously-known user \"%ls\"", username.c_str());
654  return true;
655  }
656  }
657 
658  // TODO: check server password etc?
659 
660  u32 newHostID = server.m_NextHostID++;
661 
662  session->SetUserName(username);
663  session->SetGUID(message->m_GUID);
664  session->SetHostID(newHostID);
665 
666  CAuthenticateResultMessage authenticateResult;
667  authenticateResult.m_Code = isRejoining ? ARC_OK_REJOINING : ARC_OK;
668  authenticateResult.m_HostID = newHostID;
669  authenticateResult.m_Message = L"Logged in";
670  session->SendMessage(&authenticateResult);
671 
672  server.OnUserJoin(session);
673 
674  if (isRejoining)
675  {
676  // Request a copy of the current game state from an existing player,
677  // so we can send it on to the new player
678 
679  // Assume session 0 is most likely the local player, so they're
680  // the most efficient client to request a copy from
681  CNetServerSession* sourceSession = server.m_Sessions.at(0);
682  sourceSession->GetFileTransferer().StartTask(
683  shared_ptr<CNetFileReceiveTask>(new CNetFileReceiveTask_ServerRejoin(server, newHostID))
684  );
685 
686  session->SetNextState(NSS_JOIN_SYNCING);
687  }
688 
689  return true;
690 }
691 
692 bool CNetServerWorker::OnInGame(void* context, CFsmEvent* event)
693 {
694  // TODO: should split each of these cases into a separate method
695 
696  CNetServerSession* session = (CNetServerSession*)context;
697  CNetServerWorker& server = session->GetServer();
698 
699  CNetMessage* message = (CNetMessage*)event->GetParamRef();
700  if (message->GetType() == (uint)NMT_SIMULATION_COMMAND)
701  {
702  CSimulationMessage* simMessage = static_cast<CSimulationMessage*> (message);
703 
704  // Send it back to all clients immediately
705  server.Broadcast(simMessage);
706 
707  // Save all the received commands
708  if (server.m_SavedCommands.size() < simMessage->m_Turn + 1)
709  server.m_SavedCommands.resize(simMessage->m_Turn + 1);
710  server.m_SavedCommands[simMessage->m_Turn].push_back(*simMessage);
711 
712  // TODO: we should do some validation of ownership (clients can't send commands on behalf of opposing players)
713 
714  // TODO: we shouldn't send the message back to the client that first sent it
715  }
716  else if (message->GetType() == (uint)NMT_SYNC_CHECK)
717  {
718  CSyncCheckMessage* syncMessage = static_cast<CSyncCheckMessage*> (message);
719  server.m_ServerTurnManager->NotifyFinishedClientUpdate(session->GetHostID(), syncMessage->m_Turn, syncMessage->m_Hash);
720  }
721  else if (message->GetType() == (uint)NMT_END_COMMAND_BATCH)
722  {
723  CEndCommandBatchMessage* endMessage = static_cast<CEndCommandBatchMessage*> (message);
724  server.m_ServerTurnManager->NotifyFinishedClientCommands(session->GetHostID(), endMessage->m_Turn);
725  }
726 
727  return true;
728 }
729 
730 bool CNetServerWorker::OnChat(void* context, CFsmEvent* event)
731 {
732  ENSURE(event->GetType() == (uint)NMT_CHAT);
733 
734  CNetServerSession* session = (CNetServerSession*)context;
735  CNetServerWorker& server = session->GetServer();
736 
737  CChatMessage* message = (CChatMessage*)event->GetParamRef();
738 
739  message->m_GUID = session->GetGUID();
740 
741  server.Broadcast(message);
742 
743  return true;
744 }
745 
746 bool CNetServerWorker::OnLoadedGame(void* context, CFsmEvent* event)
747 {
748  ENSURE(event->GetType() == (uint)NMT_LOADED_GAME);
749 
750  CNetServerSession* session = (CNetServerSession*)context;
751  CNetServerWorker& server = session->GetServer();
752 
753  // We're in the loading state, so wait until every player has loaded before
754  // starting the game
756  server.CheckGameLoadStatus(session);
757 
758  return true;
759 }
760 
762 {
763  // A client rejoining an in-progress game has now finished loading the
764  // map and deserialized the initial state.
765  // The simulation may have progressed since then, so send any subsequent
766  // commands to them and set them as an active player so they can participate
767  // in all future turns.
768  //
769  // (TODO: if it takes a long time for them to receive and execute all these
770  // commands, the other players will get frozen for that time and may be unhappy;
771  // we could try repeating this process a few times until the client converges
772  // on the up-to-date state, before setting them as active.)
773 
774  ENSURE(event->GetType() == (uint)NMT_LOADED_GAME);
775 
776  CNetServerSession* session = (CNetServerSession*)context;
777  CNetServerWorker& server = session->GetServer();
778 
779  CLoadedGameMessage* message = (CLoadedGameMessage*)event->GetParamRef();
780 
781  u32 turn = message->m_CurrentTurn;
782  u32 readyTurn = server.m_ServerTurnManager->GetReadyTurn();
783 
784  // Send them all commands received since their saved state,
785  // and turn-ended messages for any turns that have already been processed
786  for (size_t i = turn + 1; i < std::max(readyTurn+1, (u32)server.m_SavedCommands.size()); ++i)
787  {
788  if (i < server.m_SavedCommands.size())
789  for (size_t j = 0; j < server.m_SavedCommands[i].size(); ++j)
790  session->SendMessage(&server.m_SavedCommands[i][j]);
791 
792  if (i <= readyTurn)
793  {
794  CEndCommandBatchMessage endMessage;
795  endMessage.m_Turn = i;
796  endMessage.m_TurnLength = server.m_ServerTurnManager->GetSavedTurnLength(i);
797  session->SendMessage(&endMessage);
798  }
799  }
800 
801  // Tell the turn manager to expect commands from this new client
802  server.m_ServerTurnManager->InitialiseClient(session->GetHostID(), readyTurn);
803 
804  // Tell the client that everything has finished loading and it should start now
805  CLoadedGameMessage loaded;
806  loaded.m_CurrentTurn = readyTurn;
807  session->SendMessage(&loaded);
808 
809  return true;
810 }
811 
812 bool CNetServerWorker::OnDisconnect(void* context, CFsmEvent* event)
813 {
814  ENSURE(event->GetType() == (uint)NMT_CONNECTION_LOST);
815 
816  CNetServerSession* session = (CNetServerSession*)context;
817  CNetServerWorker& server = session->GetServer();
818 
819  server.OnUserLeave(session);
820 
821  return true;
822 }
823 
825 {
826  for (size_t i = 0; i < m_Sessions.size(); ++i)
827  {
828  if (m_Sessions[i] != changedSession && m_Sessions[i]->GetCurrState() != NSS_INGAME)
829  return;
830  }
831 
832  CLoadedGameMessage loaded;
833  loaded.m_CurrentTurn = 0;
834  Broadcast(&loaded);
835 
837 }
838 
840 {
842 
843  for (size_t i = 0; i < m_Sessions.size(); ++i)
844  m_ServerTurnManager->InitialiseClient(m_Sessions[i]->GetHostID(), 0); // TODO: only for non-observers
845 
847 
848  // Send the final setup state to all clients
851 
852  CGameStartMessage gameStart;
853  Broadcast(&gameStart);
854 }
855 
857 {
858  m_GameAttributes = attrs;
859 
860  if (!m_Host)
861  return;
862 
863  CGameSetupMessage gameSetupMessage(GetScriptInterface());
864  gameSetupMessage.m_Data = m_GameAttributes;
865  Broadcast(&gameSetupMessage);
866 }
867 
868 CStrW CNetServerWorker::SanitisePlayerName(const CStrW& original)
869 {
870  const size_t MAX_LENGTH = 32;
871 
872  CStrW name = original;
873  name.Replace(L"[", L"{"); // remove GUI tags
874  name.Replace(L"]", L"}"); // remove for symmetry
875 
876  // Restrict the length
877  if (name.length() > MAX_LENGTH)
878  name = name.Left(MAX_LENGTH);
879 
880  // Don't allow surrounding whitespace
881  name.Trim(PS_TRIM_BOTH);
882 
883  // Don't allow empty name
884  if (name.empty())
885  name = L"Anonymous";
886 
887  return name;
888 }
889 
890 CStrW CNetServerWorker::DeduplicatePlayerName(const CStrW& original)
891 {
892  CStrW name = original;
893 
894  // Try names "Foo", "Foo (2)", "Foo (3)", etc
895  size_t id = 2;
896  while (true)
897  {
898  bool unique = true;
899  for (size_t i = 0; i < m_Sessions.size(); ++i)
900  {
901  if (m_Sessions[i]->GetUserName() == name)
902  {
903  unique = false;
904  break;
905  }
906  }
907 
908  if (unique)
909  return name;
910 
911  name = original + L" (" + CStrW::FromUInt(id++) + L")";
912  }
913 }
914 
915 
916 
917 
918 CNetServer::CNetServer(int autostartPlayers) :
919  m_Worker(new CNetServerWorker(autostartPlayers))
920 {
921 }
922 
924 {
925  delete m_Worker;
926 }
927 
929 {
930  return m_Worker->SetupConnection();
931 }
932 
933 void CNetServer::AssignPlayer(int playerID, const CStr& guid)
934 {
936  m_Worker->m_AssignPlayerQueue.push_back(std::make_pair(playerID, guid));
937 }
938 
940 {
942  m_Worker->m_StartGameQueue.push_back(true);
943 }
944 
945 void CNetServer::UpdateGameAttributes(const CScriptVal& attrs, ScriptInterface& scriptInterface)
946 {
947  // Pass the attributes as JSON, since that's the easiest safe
948  // cross-thread way of passing script data
949  std::string attrsJSON = scriptInterface.StringifyJSON(attrs.get(), false);
950 
952  m_Worker->m_GameAttributesQueue.push_back(attrsJSON);
953 }
954 
956 {
958  m_Worker->m_TurnLengthQueue.push_back(msecs);
959 }
pthread_t m_WorkerThread
Definition: NetServer.h:306
NONCOPYABLE(CNetFileReceiveTask_ServerRejoin)
#define PS_PROTOCOL_VERSION
Definition: NetMessages.h:31
ScriptInterface * m_ScriptInterface
Internal script context for (de)serializing script messages, and for storing game attributes...
Definition: NetServer.h:264
void StartGame()
Call from the GUI to asynchronously notify all clients that they should start loading the game...
Definition: NetServer.cpp:939
void UninitialiseClient(int client)
Inform the turn manager that a previously-initialised client has left the game and will no longer be ...
static bool OnClientHandshake(void *context, CFsmEvent *event)
Definition: NetServer.cpp:598
void OnUserLeave(CNetServerSession *session)
Definition: NetServer.cpp:470
CStrW m_Name
Player name.
Definition: NetHost.h:45
static void * RunThread(void *data)
Definition: NetServer.cpp:219
void LatchHostState(const ENetHost *host)
Definition: NetStats.cpp:127
std::vector< std::vector< CSimulationMessage > > m_SavedCommands
A copy of all simulation commands received so far, indexed by turn number, to simplify support for re...
Definition: NetServer.h:291
CNetFileTransferer & GetFileTransferer()
Definition: NetSession.h:145
#define LOGERROR
Definition: CLogger.h:35
static bool OnJoinSyncingLoadedGame(void *context, CFsmEvent *event)
Definition: NetServer.cpp:761
std::string m_JoinSyncFile
The latest copy of the simulation state, received from an existing client when a new client has asked...
Definition: NetServer.h:297
Represents a signal in the state machine that a change has occurred.
Definition: fsm.h:53
void CheckGameLoadStatus(CNetServerSession *changedSession)
Definition: NetServer.cpp:824
bool SetupConnection()
Begin listening for network connections.
Definition: NetServer.cpp:928
void HandleMessageReceive(const CNetMessage *message, CNetServerSession *session)
Definition: NetServer.cpp:392
#define i32
Definition: types.h:36
Async task for receiving the initial game state to be forwarded to another client that is rejoining a...
Definition: NetServer.cpp:61
virtual bool SendMessage(const CNetMessage *message)
Send a message to the client.
Definition: NetSession.cpp:190
#define LOGMESSAGE
Definition: CLogger.h:32
bool SetupConnection()
Begin listening for network connections.
Definition: NetServer.cpp:157
void StartGame()
Call from the GUI to notify all clients that they should start loading the game.
Definition: NetServer.cpp:839
Locks a CMutex over this object&#39;s lifetime.
Definition: ThreadUtil.h:73
u32 GetHostID() const
Definition: NetSession.h:122
void AddPlayer(const CStr &guid, const CStrW &name)
Definition: NetServer.cpp:481
void UpdateGameAttributes(const CScriptValRooted &attrs)
Call from the GUI to update the game setup attributes.
Definition: NetServer.cpp:856
static CStr DebugName(CNetServerSession *session)
Definition: NetServer.cpp:48
CNetServerWorker & GetServer()
Definition: NetSession.h:114
const jsval & get() const
Returns the current value.
Definition: ScriptVal.h:38
static bool OnChat(void *context, CFsmEvent *event)
Definition: NetServer.cpp:730
static bool OnInGame(void *context, CFsmEvent *event)
Definition: NetServer.cpp:692
CNetServer * g_NetServer
Global network server for the standard game.
Definition: NetServer.cpp:46
A trivial wrapper around a jsval.
Definition: ScriptVal.h:29
void SetHostID(u32 id)
Definition: NetSession.h:123
i32 m_PlayerID
The player that the given host controls, or -1 if none (observer)
Definition: NetHost.h:48
Trim all white space from the end of the string.
Definition: CStr.h:35
void Disconnect(u32 reason)
Sends a disconnection notification to the client, and sends a NMT_CONNECTION_LOST message to the sess...
Definition: NetSession.cpp:178
void SetUserName(const CStrW &name)
Definition: NetSession.h:120
void NotifyFinishedClientUpdate(int client, u32 turn, const std::string &hash)
CNetServerWorker(int autostartPlayers)
Definition: NetServer.cpp:111
static CStrW SanitisePlayerName(const CStrW &original)
Make a player name &#39;nicer&#39; by limiting the length and removing forbidden characters etc...
Definition: NetServer.cpp:868
struct _ENetPeer ENetPeer
Definition: NetHost.h:30
std::string StringifyJSON(jsval obj, bool indent=true)
Stringify to a JSON string, UTF-8 encoded.
u32 GetSavedTurnLength(u32 turn)
Returns the turn length that was used for the given turn.
#define MAX_CLIENTS
Definition: NetServer.cpp:35
CScriptValRooted ParseJSON(const std::string &string_utf8)
Parse a UTF-8-encoded JSON string.
static bool SendMessage(const CNetMessage *message, ENetPeer *peer, const char *peerName)
Transmit a message to the given peer.
Definition: NetHost.cpp:26
#define ARRAY_SIZE(name)
void SetNextState(unsigned int nextState)
Definition: fsm.h:155
void StartTask(const shared_ptr< CNetFileReceiveTask > &task)
Registers a file-receiving task.
LIB_API void debug_SetThreadName(const char *name)
inform the debugger of the current thread&#39;s name.
Definition: bdbg.cpp:126
std::vector< std::string > m_GameAttributesQueue
Definition: NetServer.h:314
CNetServer(int autostartPlayers=-1)
Construct a new network server.
Definition: NetServer.cpp:918
void StartResponse(u32 requestID, const std::string &data)
Registers data to be sent in response to a request.
#define ENSURE(expr)
ensure the expression &lt;expr&gt; evaluates to non-zero.
Definition: debug.h:282
void SetTurnLength(u32 msecs)
Set the turn length to a fixed value.
Definition: NetServer.cpp:955
Status HandleMessageReceive(const CNetMessage *message)
Should be called when a message is received from the network.
#define DEFAULT_SERVER_NAME
Definition: NetServer.cpp:33
int pthread_create(pthread_t *thread_id, const void *attr, void *(*func)(void *), void *arg)
Definition: wpthread.cpp:636
void AssignPlayer(int playerID, const CStr &guid)
Call from the GUI to update the player assignments.
Definition: NetServer.cpp:933
void SetTurnLength(u32 msecs)
ENetHost * m_Host
Definition: NetServer.h:272
#define PS_DEFAULT_PORT
Definition: NetMessages.h:32
void AssignPlayer(int playerID, const CStr &guid)
Call from the GUI to update the player assignments.
Definition: NetServer.cpp:549
std::vector< std::pair< int, CStr > > m_AssignPlayerQueue
Definition: NetServer.h:312
CScriptValRooted m_GameAttributes
Definition: NetServer.h:268
Asynchronous file-receiving task.
CStrW DeduplicatePlayerName(const CStrW &original)
Make a player name unique, if it matches any existing session&#39;s name.
Definition: NetServer.cpp:890
void InitialiseClient(int client, u32 turn)
Inform the turn manager of a new client who will be sending commands.
Special message type for updated to game startup settings.
Definition: NetMessage.h:134
void UpdateGameAttributes(const CScriptVal &attrs, ScriptInterface &scriptInterface)
Call from the GUI to update the game setup attributes.
Definition: NetServer.cpp:945
void SetupSession(CNetServerSession *session)
Definition: NetServer.cpp:418
#define SAFE_DELETE(p)
delete memory ensuing from new and set the pointer to zero (thus making double-frees safe / a no-op) ...
static const int HOST_SERVICE_TIMEOUT
enet_host_service timeout (msecs).
Definition: NetServer.cpp:44
Network server interface.
Definition: NetServer.h:97
void ConstructPlayerAssignmentMessage(CPlayerAssignmentMessage &message)
Definition: NetServer.cpp:565
#define DEFAULT_WELCOME_MESSAGE
Definition: NetServer.cpp:34
ENet connection statistics profiler table.
Definition: NetStats.h:36
i64 Status
Error handling system.
Definition: status.h:171
std::vector< bool > m_StartGameQueue
Definition: NetServer.h:313
static bool IsInitialised()
Definition: Singleton.h:63
bool Update(unsigned int eventType, void *pEventData)
Definition: fsm.cpp:393
std::vector< u32 > m_TurnLengthQueue
Definition: NetServer.h:315
std::vector< CNetServerSession * > m_Sessions
Definition: NetServer.h:273
NetMessageType GetType() const
Retrieves the message type.
Definition: NetMessage.h:46
The base class for all network messages exchanged within the game.
Definition: NetMessage.h:32
void OnUserJoin(CNetServerSession *session)
Definition: NetServer.cpp:457
void NotifyFinishedClientCommands(int client, u32 turn)
int m_AutostartPlayers
Definition: NetServer.h:270
const CStrW & GetUserName() const
Definition: NetSession.h:119
CNetServerWorker * m_Worker
Definition: NetServer.h:145
friend class CNetFileReceiveTask_ServerRejoin
Definition: NetServer.h:180
bool m_Enabled
Whether the player is currently connected and active.
Definition: NetHost.h:42
unsigned int GetCurrState(void) const
Definition: fsm.h:154
bool HandleConnect(CNetServerSession *session)
Definition: NetServer.cpp:448
const CStr & GetGUID() const
Definition: NetSession.h:116
CNetStatsTable * m_Stats
Definition: NetServer.h:275
const Status SKIPPED
Definition: status.h:392
CStrW m_WelcomeMessage
Definition: NetServer.h:280
#define u32
Definition: types.h:41
void SetTurnLength(u32 msecs)
Set the turn length to a fixed value.
Definition: NetServer.cpp:592
CFsmTransition * AddTransition(unsigned int state, unsigned int eventType, unsigned int nextState)
Definition: fsm.cpp:272
virtual size_t GetSerializedLength() const
Retrieves the size in bytes of the serialized message.
Definition: NetMessage.cpp:78
static bool OnAuthenticate(void *context, CFsmEvent *event)
Definition: NetServer.cpp:621
CNetFileReceiveTask_ServerRejoin(CNetServerWorker &server, u32 hostID)
Definition: NetServer.cpp:65
#define g_ProfileViewer
NetServerState m_State
Definition: NetServer.h:277
Network client/server sessions.
virtual void OnComplete()
Called when m_Buffer contains the full received data.
Definition: NetServer.cpp:70
bool Broadcast(const CNetMessage *message)
Send a message to all clients who have completed the full connection process (i.e.
Definition: NetServer.cpp:197
static bool OnLoadedGame(void *context, CFsmEvent *event)
Definition: NetServer.cpp:746
bool SendMessage(ENetPeer *peer, const CNetMessage *message)
Send a message to the given network peer.
Definition: NetServer.cpp:188
Abstraction around a SpiderMonkey JSContext.
void SendPlayerAssignments()
Definition: NetServer.cpp:580
int pthread_join(pthread_t thread, void **value_ptr)
Definition: wpthread.cpp:679
CScriptValRooted m_Data
Definition: NetMessage.h:145
unsigned int GetType(void) const
Definition: fsm.h:61
ScriptInterface & GetScriptInterface()
Get the script context used for game attributes.
Definition: NetServer.cpp:587
void SetFirstState(unsigned int firstState)
Definition: fsm.cpp:366
CStrW m_ServerName
Definition: NetServer.h:279
u32 GetReadyTurn()
Returns the latest turn for which all clients are ready; they will have already been told to execute ...
CNetServerTurnManager * m_ServerTurnManager
Definition: NetServer.h:284
PlayerAssignmentMap m_PlayerAssignments
Definition: NetServer.h:266
static shared_ptr< ScriptRuntime > CreateRuntime(int runtimeSize=DEFAULT_RUNTIME_SIZE)
Returns a runtime, which can used to initialise any number of ScriptInterfaces contexts.
static bool OnDisconnect(void *context, CFsmEvent *event)
Definition: NetServer.cpp:812
Network server worker thread.
Definition: NetServer.h:160
The server&#39;s end of a network session.
Definition: NetSession.h:107
void * GetParamRef(void)
Definition: fsm.h:62
static CNetMessage * CreateMessage(const void *pData, size_t dataSize, ScriptInterface &scriptInterface)
Factory method which creates a message object based on the given data.
Definition: NetMessage.cpp:94
void RemovePlayer(const CStr &guid)
Definition: NetServer.cpp:542
virtual CStr ToString() const
Returns a string representation for the message.
Definition: NetMessage.cpp:84
void SetGUID(const CStr &guid)
Definition: NetSession.h:117
CMutex m_WorkerMutex
Definition: NetServer.h:307
The server-side counterpart to CNetClientTurnManager.
static const int CHANNEL_COUNT
Definition: NetServer.cpp:37
#define PS_PROTOCOL_MAGIC
Definition: NetMessages.h:29
Special message type for simulation commands.
Definition: NetMessage.h:113