2011-10-27 18:46:48 +02:00
/* Copyright (C) 2011 Wildfire Games.
2009-04-18 19:00:33 +02:00
* This file is part of 0 A . D .
*
* 0 A . D . is free software : you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation , either version 2 of the License , or
* ( at your option ) any later version .
*
* 0 A . D . is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with 0 A . D . If not , see < http : //www.gnu.org/licenses/>.
*/
2009-04-11 19:00:39 +02:00
# include "precompiled.h"
2010-06-08 00:19:05 +02:00
# include "NetServer.h"
2010-06-30 23:41:04 +02:00
# include "NetClient.h"
# include "NetMessage.h"
2009-04-11 19:00:39 +02:00
# include "NetSession.h"
2010-07-03 15:15:01 +02:00
# include "NetStats.h"
2010-06-30 23:41:04 +02:00
# include "NetTurnManager.h"
2010-06-08 00:19:05 +02:00
2010-08-10 23:49:33 +02:00
# include "lib/external_libraries/enet.h"
2010-06-08 00:19:05 +02:00
# include "ps/CLogger.h"
2010-06-30 23:41:04 +02:00
# include "scriptinterface/ScriptInterface.h"
2010-05-20 02:59:01 +02:00
# include "simulation2/Simulation2.h"
2009-04-11 19:00:39 +02:00
2010-06-30 23:41:04 +02:00
# define DEFAULT_SERVER_NAME L"Unnamed Server"
# define DEFAULT_WELCOME_MESSAGE L"Welcome"
# define MAX_CLIENTS 8
2009-04-11 19:00:39 +02:00
2011-05-29 22:57:28 +02:00
static const int CHANNEL_COUNT = 1 ;
2010-10-31 23:00:28 +01:00
/**
* enet_host_service timeout ( msecs ) .
* Smaller numbers may hurt performance ; larger numbers will
* hurt latency responding to messages from game thread .
*/
static const int HOST_SERVICE_TIMEOUT = 50 ;
2010-06-30 23:41:04 +02:00
CNetServer * g_NetServer = NULL ;
2009-04-11 19:00:39 +02:00
2010-06-30 23:41:04 +02:00
static CStr DebugName ( CNetServerSession * session )
2009-04-11 19:00:39 +02:00
{
2010-06-30 23:41:04 +02:00
if ( session = = NULL )
return " [unknown host] " ;
if ( session - > GetGUID ( ) . empty ( ) )
return " [unauthed host] " ;
return " [ " + session - > GetGUID ( ) . substr ( 0 , 8 ) + " ...] " ;
}
2009-04-11 19:00:39 +02:00
2011-10-27 18:46:48 +02:00
/**
* Async task for receiving the initial game state to be forwarded to another
* client that is rejoining an in - progress network game .
*/
class CNetFileReceiveTask_ServerRejoin : public CNetFileReceiveTask
{
NONCOPYABLE ( CNetFileReceiveTask_ServerRejoin ) ;
public :
CNetFileReceiveTask_ServerRejoin ( CNetServerWorker & server , u32 hostID )
: m_Server ( server ) , m_RejoinerHostID ( hostID )
{
}
virtual void OnComplete ( )
{
// We've received the game state from an existing player - now
// we need to send it onwards to the newly rejoining player
// Find the session corresponding to the rejoining host (if any)
CNetServerSession * session = NULL ;
for ( size_t i = 0 ; i < m_Server . m_Sessions . size ( ) ; + + i )
{
if ( m_Server . m_Sessions [ i ] - > GetHostID ( ) = = m_RejoinerHostID )
{
session = m_Server . m_Sessions [ i ] ;
break ;
}
}
if ( ! session )
{
LOGMESSAGE ( L " Net server: rejoining client disconnected before we sent to it " ) ;
return ;
}
// Store the received state file, and tell the client to start downloading it from us
// TODO: this will get kind of confused if there's multiple clients downloading in parallel;
// they'll race and get whichever happens to be the latest received by the server,
// which should still work but isn't great
m_Server . m_JoinSyncFile = m_Buffer ;
CJoinSyncStartMessage message ;
session - > SendMessage ( & message ) ;
}
private :
CNetServerWorker & m_Server ;
u32 m_RejoinerHostID ;
} ;
2010-10-31 23:00:28 +01:00
/*
* XXX : We use some non - threadsafe functions from the worker thread .
* See http : //trac.wildfiregames.com/ticket/654
*/
CNetServerWorker : : CNetServerWorker ( int autostartPlayers ) :
m_AutostartPlayers ( autostartPlayers ) ,
m_Shutdown ( false ) ,
m_ScriptInterface ( NULL ) ,
m_NextHostID ( 1 ) , m_Host ( NULL ) , m_Stats ( NULL )
2010-06-30 23:41:04 +02:00
{
m_State = SERVER_STATE_UNCONNECTED ;
2009-04-11 19:00:39 +02:00
2010-06-30 23:41:04 +02:00
m_ServerTurnManager = NULL ;
2009-04-11 19:00:39 +02:00
2010-06-30 23:41:04 +02:00
m_ServerName = DEFAULT_SERVER_NAME ;
m_WelcomeMessage = DEFAULT_WELCOME_MESSAGE ;
2009-04-11 19:00:39 +02:00
}
2010-10-31 23:00:28 +01:00
CNetServerWorker : : ~ CNetServerWorker ( )
2009-04-11 19:00:39 +02:00
{
2010-10-31 23:00:28 +01:00
if ( m_State ! = SERVER_STATE_UNCONNECTED )
{
// Tell the thread to shut down
{
CScopeLock lock ( m_WorkerMutex ) ;
m_Shutdown = true ;
}
// Wait for it to shut down cleanly
pthread_join ( m_WorkerThread , NULL ) ;
}
// Clean up resources
2010-07-03 15:15:01 +02:00
delete m_Stats ;
2010-07-02 23:28:48 +02:00
for ( size_t i = 0 ; i < m_Sessions . size ( ) ; + + i )
{
2010-07-06 21:54:17 +02:00
m_Sessions [ i ] - > DisconnectNow ( NDR_UNEXPECTED_SHUTDOWN ) ;
2010-07-02 23:28:48 +02:00
delete m_Sessions [ i ] ;
}
if ( m_Host )
{
enet_host_destroy ( m_Host ) ;
}
2010-07-06 21:54:17 +02:00
delete m_ServerTurnManager ;
2009-04-11 19:00:39 +02:00
}
2010-10-31 23:00:28 +01:00
bool CNetServerWorker : : SetupConnection ( )
2009-04-11 19:00:39 +02:00
{
2011-04-30 15:01:45 +02:00
ENSURE ( m_State = = SERVER_STATE_UNCONNECTED ) ;
ENSURE ( ! m_Host ) ;
2009-04-11 19:00:39 +02:00
2010-06-30 23:41:04 +02:00
// Bind to default host
ENetAddress addr ;
addr . host = ENET_HOST_ANY ;
2010-07-06 21:54:17 +02:00
addr . port = PS_DEFAULT_PORT ;
2009-04-11 19:00:39 +02:00
2010-06-30 23:41:04 +02:00
// Create ENet server
2011-05-29 22:57:28 +02:00
m_Host = enet_host_create ( & addr , MAX_CLIENTS , CHANNEL_COUNT , 0 , 0 ) ;
2010-06-30 23:41:04 +02:00
if ( ! m_Host )
2009-04-11 19:00:39 +02:00
{
2010-06-30 23:41:04 +02:00
LOGERROR ( L " Net server: enet_host_create failed " ) ;
2009-04-11 19:00:39 +02:00
return false ;
}
2010-10-31 23:00:28 +01:00
m_Stats = new CNetStatsTable ( ) ;
2010-07-04 21:59:05 +02:00
if ( CProfileViewer : : IsInitialised ( ) )
g_ProfileViewer . AddRootTable ( m_Stats ) ;
2010-07-03 15:15:01 +02:00
2010-06-30 23:41:04 +02:00
m_State = SERVER_STATE_PREGAME ;
2010-10-31 23:00:28 +01:00
// Launch the worker thread
int ret = pthread_create ( & m_WorkerThread , NULL , & RunThread , this ) ;
2011-04-30 15:01:45 +02:00
ENSURE ( ret = = 0 ) ;
2010-10-31 23:00:28 +01:00
2009-04-11 19:00:39 +02:00
return true ;
}
2010-10-31 23:00:28 +01:00
bool CNetServerWorker : : SendMessage ( ENetPeer * peer , const CNetMessage * message )
2009-04-11 19:00:39 +02:00
{
2011-04-30 15:01:45 +02:00
ENSURE ( m_Host ) ;
2009-04-11 19:00:39 +02:00
2010-06-30 23:41:04 +02:00
CNetServerSession * session = static_cast < CNetServerSession * > ( peer - > data ) ;
2009-04-11 19:00:39 +02:00
2010-06-30 23:41:04 +02:00
return CNetHost : : SendMessage ( message , peer , DebugName ( session ) . c_str ( ) ) ;
2009-04-11 19:00:39 +02:00
}
2010-10-31 23:00:28 +01:00
bool CNetServerWorker : : Broadcast ( const CNetMessage * message )
2009-04-11 19:00:39 +02:00
{
2011-04-30 15:01:45 +02:00
ENSURE ( m_Host ) ;
2009-04-11 19:00:39 +02:00
2010-06-30 23:41:04 +02:00
bool ok = true ;
2009-04-11 19:00:39 +02:00
2010-06-30 23:41:04 +02:00
// Send to all sessions that are active and has finished authentication
for ( size_t i = 0 ; i < m_Sessions . size ( ) ; + + i )
{
if ( m_Sessions [ i ] - > GetCurrState ( ) = = NSS_PREGAME | | m_Sessions [ i ] - > GetCurrState ( ) = = NSS_INGAME )
{
if ( ! m_Sessions [ i ] - > SendMessage ( message ) )
ok = false ;
2009-04-11 19:00:39 +02:00
2010-06-30 23:41:04 +02:00
// TODO: this does lots of repeated message serialisation if we have lots
// of remote peers; could do it more efficiently if that's a real problem
}
}
2009-04-11 19:00:39 +02:00
2010-06-30 23:41:04 +02:00
return ok ;
2009-04-11 19:00:39 +02:00
}
2010-10-31 23:00:28 +01:00
void * CNetServerWorker : : RunThread ( void * data )
2009-04-11 19:00:39 +02:00
{
2010-10-31 23:00:28 +01:00
debug_SetThreadName ( " NetServer " ) ;
2009-04-11 19:00:39 +02:00
2010-10-31 23:00:28 +01:00
static_cast < CNetServerWorker * > ( data ) - > Run ( ) ;
return NULL ;
}
void CNetServerWorker : : Run ( )
{
// To avoid the need for JS_SetContextThread, we create and use and destroy
// the script interface entirely within this network thread
2011-01-16 00:35:20 +01:00
m_ScriptInterface = new ScriptInterface ( " Engine " , " Net server " , ScriptInterface : : CreateRuntime ( ) ) ;
2010-10-31 23:00:28 +01:00
while ( true )
2010-06-30 23:41:04 +02:00
{
2010-10-31 23:00:28 +01:00
if ( ! RunStep ( ) )
break ;
2009-04-11 19:00:39 +02:00
2010-10-31 23:00:28 +01:00
// Implement autostart mode
if ( m_State = = SERVER_STATE_PREGAME & & ( int ) m_PlayerAssignments . size ( ) = = m_AutostartPlayers )
StartGame ( ) ;
2010-07-06 21:54:17 +02:00
2010-10-31 23:00:28 +01:00
// Update profiler stats
m_Stats - > LatchHostState ( m_Host ) ;
}
2009-04-11 19:00:39 +02:00
2011-10-27 18:46:48 +02:00
// Clear roots before deleting their context
2010-10-31 23:00:28 +01:00
m_GameAttributes = CScriptValRooted ( ) ;
2011-10-27 18:46:48 +02:00
m_SavedCommands . clear ( ) ;
2009-04-11 19:00:39 +02:00
2010-10-31 23:00:28 +01:00
SAFE_DELETE ( m_ScriptInterface ) ;
}
2009-04-11 19:00:39 +02:00
2010-10-31 23:00:28 +01:00
bool CNetServerWorker : : RunStep ( )
{
// Check for messages from the game thread.
// (Do as little work as possible while the mutex is held open,
// to avoid performance problems and deadlocks.)
2009-04-11 19:00:39 +02:00
2010-10-31 23:00:28 +01:00
std : : vector < std : : pair < int , CStr > > newAssignPlayer ;
std : : vector < bool > newStartGame ;
std : : vector < std : : string > newGameAttributes ;
std : : vector < u32 > newTurnLength ;
2010-06-30 23:41:04 +02:00
2010-10-31 23:00:28 +01:00
{
CScopeLock lock ( m_WorkerMutex ) ;
2010-07-06 21:54:17 +02:00
2010-10-31 23:00:28 +01:00
if ( m_Shutdown )
return false ;
2009-04-11 19:00:39 +02:00
2010-10-31 23:00:28 +01:00
newStartGame . swap ( m_StartGameQueue ) ;
newAssignPlayer . swap ( m_AssignPlayerQueue ) ;
newGameAttributes . swap ( m_GameAttributesQueue ) ;
newTurnLength . swap ( m_TurnLengthQueue ) ;
}
2009-04-11 19:00:39 +02:00
2010-10-31 23:00:28 +01:00
for ( size_t i = 0 ; i < newAssignPlayer . size ( ) ; + + i )
AssignPlayer ( newAssignPlayer [ i ] . first , newAssignPlayer [ i ] . second ) ;
2009-04-11 19:00:39 +02:00
2010-10-31 23:00:28 +01:00
if ( ! newGameAttributes . empty ( ) )
UpdateGameAttributes ( GetScriptInterface ( ) . ParseJSON ( newGameAttributes . back ( ) ) ) ;
2010-07-02 23:28:48 +02:00
2010-10-31 23:00:28 +01:00
if ( ! newTurnLength . empty ( ) )
SetTurnLength ( newTurnLength . back ( ) ) ;
2009-04-11 19:00:39 +02:00
2010-10-31 23:00:28 +01:00
// Do StartGame last, so we have the most up-to-date game attributes when we start
if ( ! newStartGame . empty ( ) )
StartGame ( ) ;
2011-10-27 18:46:48 +02:00
// Perform file transfers
for ( size_t i = 0 ; i < m_Sessions . size ( ) ; + + i )
m_Sessions [ i ] - > GetFileTransferer ( ) . Poll ( ) ;
2010-10-31 23:00:28 +01:00
// Process network events:
ENetEvent event ;
int status = enet_host_service ( m_Host , & event , HOST_SERVICE_TIMEOUT ) ;
if ( status < 0 )
{
LOGERROR ( L " CNetServerWorker: enet_host_service failed (%d) " , status ) ;
// TODO: notify game that the server has shut down
return false ;
}
2009-04-11 19:00:39 +02:00
2010-10-31 23:00:28 +01:00
if ( status = = 0 )
{
// Reached timeout with no events - try again
return true ;
}
// Process the event:
switch ( event . type )
{
case ENET_EVENT_TYPE_CONNECT :
{
// Report the client address
char hostname [ 256 ] = " (error) " ;
enet_address_get_host_ip ( & event . peer - > address , hostname , ARRAY_SIZE ( hostname ) ) ;
2012-01-01 17:43:10 +01:00
LOGMESSAGE ( L " Net server: Received connection from %hs:%u " , hostname , ( unsigned int ) event . peer - > address . port ) ;
2010-10-31 23:00:28 +01:00
// Set up a session object for this peer
CNetServerSession * session = new CNetServerSession ( * this , event . peer ) ;
m_Sessions . push_back ( session ) ;
SetupSession ( session ) ;
2011-04-30 15:01:45 +02:00
ENSURE ( event . peer - > data = = NULL ) ;
2010-10-31 23:00:28 +01:00
event . peer - > data = session ;
HandleConnect ( session ) ;
break ;
}
case ENET_EVENT_TYPE_DISCONNECT :
{
// If there is an active session with this peer, then reset and delete it
CNetServerSession * session = static_cast < CNetServerSession * > ( event . peer - > data ) ;
if ( session )
2009-04-11 19:00:39 +02:00
{
2010-10-31 23:00:28 +01:00
LOGMESSAGE ( L " Net server: Disconnected %hs " , DebugName ( session ) . c_str ( ) ) ;
// Remove the session first, so we won't send player-update messages to it
// when updating the FSM
m_Sessions . erase ( remove ( m_Sessions . begin ( ) , m_Sessions . end ( ) , session ) , m_Sessions . end ( ) ) ;
session - > Update ( ( uint ) NMT_CONNECTION_LOST , NULL ) ;
delete session ;
event . peer - > data = NULL ;
}
2009-04-11 19:00:39 +02:00
2010-10-31 23:00:28 +01:00
break ;
}
case ENET_EVENT_TYPE_RECEIVE :
{
// If there is an active session with this peer, then process the message
CNetServerSession * session = static_cast < CNetServerSession * > ( event . peer - > data ) ;
if ( session )
{
// Create message from raw data
CNetMessage * msg = CNetMessageFactory : : CreateMessage ( event . packet - > data , event . packet - > dataLength , GetScriptInterface ( ) ) ;
if ( msg )
2010-06-30 23:41:04 +02:00
{
2011-02-17 21:08:20 +01:00
LOGMESSAGE ( L " Net server: Received message %hs of size %lu from %hs " , msg - > ToString ( ) . c_str ( ) , ( unsigned long ) msg - > GetSerializedLength ( ) , DebugName ( session ) . c_str ( ) ) ;
2009-04-11 19:00:39 +02:00
2010-10-31 23:00:28 +01:00
HandleMessageReceive ( msg , session ) ;
2010-06-30 23:41:04 +02:00
2010-10-31 23:00:28 +01:00
delete msg ;
2010-06-30 23:41:04 +02:00
}
2010-10-31 23:00:28 +01:00
}
2010-06-30 23:41:04 +02:00
2010-10-31 23:00:28 +01:00
// Done using the packet
enet_packet_destroy ( event . packet ) ;
2009-04-11 19:00:39 +02:00
2010-10-31 23:00:28 +01:00
break ;
}
2010-12-11 13:35:50 +01:00
case ENET_EVENT_TYPE_NONE :
break ;
2009-04-11 19:00:39 +02:00
}
2010-10-31 23:00:28 +01:00
return true ;
2010-06-30 23:41:04 +02:00
}
2009-04-11 19:00:39 +02:00
2010-10-31 23:00:28 +01:00
void CNetServerWorker : : HandleMessageReceive ( const CNetMessage * message , CNetServerSession * session )
2010-06-30 23:41:04 +02:00
{
2011-10-27 18:46:48 +02:00
// Handle non-FSM messages first
Status status = session - > GetFileTransferer ( ) . HandleMessageReceive ( message ) ;
if ( status ! = INFO : : SKIPPED )
return ;
if ( message - > GetType ( ) = = NMT_FILE_TRANSFER_REQUEST )
{
CFileTransferRequestMessage * reqMessage = ( CFileTransferRequestMessage * ) message ;
// Rejoining client got our JoinSyncStart after we received the state from
// another client, and has now requested that we forward it to them
ENSURE ( ! m_JoinSyncFile . empty ( ) ) ;
session - > GetFileTransferer ( ) . StartResponse ( reqMessage - > m_RequestID , m_JoinSyncFile ) ;
return ;
}
2010-06-30 23:41:04 +02:00
// Update FSM
bool ok = session - > Update ( message - > GetType ( ) , ( void * ) message ) ;
if ( ! ok )
LOGERROR ( L " Net server: Error running FSM update (type=%d state=%d) " , ( int ) message - > GetType ( ) , ( int ) session - > GetCurrState ( ) ) ;
}
2009-04-11 19:00:39 +02:00
2010-10-31 23:00:28 +01:00
void CNetServerWorker : : SetupSession ( CNetServerSession * session )
2010-06-30 23:41:04 +02:00
{
void * context = session ;
// Set up transitions for session
2010-07-06 21:54:17 +02:00
session - > AddTransition ( NSS_UNCONNECTED , ( uint ) NMT_CONNECTION_LOST , NSS_UNCONNECTED ) ;
session - > AddTransition ( NSS_HANDSHAKE , ( uint ) NMT_CONNECTION_LOST , NSS_UNCONNECTED ) ;
2010-06-30 23:41:04 +02:00
session - > AddTransition ( NSS_HANDSHAKE , ( uint ) NMT_CLIENT_HANDSHAKE , NSS_AUTHENTICATE , ( void * ) & OnClientHandshake , context ) ;
2010-07-02 23:28:48 +02:00
2010-07-06 21:54:17 +02:00
session - > AddTransition ( NSS_AUTHENTICATE , ( uint ) NMT_CONNECTION_LOST , NSS_UNCONNECTED ) ;
2010-06-30 23:41:04 +02:00
session - > AddTransition ( NSS_AUTHENTICATE , ( uint ) NMT_AUTHENTICATE , NSS_PREGAME , ( void * ) & OnAuthenticate , context ) ;
2010-07-02 23:28:48 +02:00
session - > AddTransition ( NSS_PREGAME , ( uint ) NMT_CONNECTION_LOST , NSS_UNCONNECTED , ( void * ) & OnDisconnect , context ) ;
2010-06-30 23:41:04 +02:00
session - > AddTransition ( NSS_PREGAME , ( uint ) NMT_CHAT , NSS_PREGAME , ( void * ) & OnChat , context ) ;
session - > AddTransition ( NSS_PREGAME , ( uint ) NMT_LOADED_GAME , NSS_INGAME , ( void * ) & OnLoadedGame , context ) ;
2010-07-02 23:28:48 +02:00
2011-10-27 18:46:48 +02:00
session - > AddTransition ( NSS_JOIN_SYNCING , ( uint ) NMT_LOADED_GAME , NSS_INGAME , ( void * ) & OnJoinSyncingLoadedGame , context ) ;
2010-07-02 23:28:48 +02:00
session - > AddTransition ( NSS_INGAME , ( uint ) NMT_CONNECTION_LOST , NSS_UNCONNECTED , ( void * ) & OnDisconnect , context ) ;
2010-06-30 23:41:04 +02:00
session - > AddTransition ( NSS_INGAME , ( uint ) NMT_CHAT , NSS_INGAME , ( void * ) & OnChat , context ) ;
session - > AddTransition ( NSS_INGAME , ( uint ) NMT_SIMULATION_COMMAND , NSS_INGAME , ( void * ) & OnInGame , context ) ;
session - > AddTransition ( NSS_INGAME , ( uint ) NMT_SYNC_CHECK , NSS_INGAME , ( void * ) & OnInGame , context ) ;
session - > AddTransition ( NSS_INGAME , ( uint ) NMT_END_COMMAND_BATCH , NSS_INGAME , ( void * ) & OnInGame , context ) ;
2009-04-11 19:00:39 +02:00
2010-06-30 23:41:04 +02:00
// Set first state
session - > SetFirstState ( NSS_HANDSHAKE ) ;
2009-04-11 19:00:39 +02:00
}
2010-10-31 23:00:28 +01:00
bool CNetServerWorker : : HandleConnect ( CNetServerSession * session )
2009-04-11 19:00:39 +02:00
{
2010-06-30 23:41:04 +02:00
CSrvHandshakeMessage handshake ;
handshake . m_Magic = PS_PROTOCOL_MAGIC ;
handshake . m_ProtocolVersion = PS_PROTOCOL_VERSION ;
handshake . m_SoftwareVersion = PS_PROTOCOL_VERSION ;
return session - > SendMessage ( & handshake ) ;
}
2009-04-11 19:00:39 +02:00
2010-10-31 23:00:28 +01:00
void CNetServerWorker : : OnUserJoin ( CNetServerSession * session )
2009-04-11 19:00:39 +02:00
{
2010-06-30 23:41:04 +02:00
AddPlayer ( session - > GetGUID ( ) , session - > GetUserName ( ) ) ;
2009-04-11 19:00:39 +02:00
2010-06-30 23:41:04 +02:00
CGameSetupMessage gameSetupMessage ( GetScriptInterface ( ) ) ;
gameSetupMessage . m_Data = m_GameAttributes ;
session - > SendMessage ( & gameSetupMessage ) ;
2009-04-11 19:00:39 +02:00
2010-06-30 23:41:04 +02:00
CPlayerAssignmentMessage assignMessage ;
ConstructPlayerAssignmentMessage ( assignMessage ) ;
session - > SendMessage ( & assignMessage ) ;
2009-04-11 19:00:39 +02:00
}
2010-10-31 23:00:28 +01:00
void CNetServerWorker : : OnUserLeave ( CNetServerSession * session )
2010-07-02 23:28:48 +02:00
{
RemovePlayer ( session - > GetGUID ( ) ) ;
2010-11-03 01:21:52 +01:00
if ( m_ServerTurnManager )
m_ServerTurnManager - > UninitialiseClient ( session - > GetHostID ( ) ) ; // TODO: only for non-observers
// TODO: ought to switch the player controlled by that client
// back to AI control, or something?
2010-07-02 23:28:48 +02:00
}
2010-10-31 23:00:28 +01:00
void CNetServerWorker : : AddPlayer ( const CStr & guid , const CStrW & name )
2009-04-11 19:00:39 +02:00
{
2011-10-27 18:46:48 +02:00
// Find all player IDs in active use; we mustn't give them to a second player
2010-06-30 23:41:04 +02:00
std : : set < i32 > usedIDs ;
for ( PlayerAssignmentMap : : iterator it = m_PlayerAssignments . begin ( ) ; it ! = m_PlayerAssignments . end ( ) ; + + it )
2011-10-27 18:46:48 +02:00
if ( it - > second . m_Enabled )
usedIDs . insert ( it - > second . m_PlayerID ) ;
// If the player is rejoining after disconnecting, try to give them
// back their old player ID
i32 playerID = - 1 ;
bool foundPlayerID = false ;
// Try to match GUID first
for ( PlayerAssignmentMap : : iterator it = m_PlayerAssignments . begin ( ) ; it ! = m_PlayerAssignments . end ( ) ; + + it )
{
if ( ! it - > second . m_Enabled & & it - > first = = guid & & usedIDs . find ( it - > second . m_PlayerID ) = = usedIDs . end ( ) )
{
playerID = it - > second . m_PlayerID ;
foundPlayerID = true ;
m_PlayerAssignments . erase ( it ) ; // delete the old mapping, since we've got a new one now
break ;
}
}
// Try to match username next
if ( ! foundPlayerID )
{
for ( PlayerAssignmentMap : : iterator it = m_PlayerAssignments . begin ( ) ; it ! = m_PlayerAssignments . end ( ) ; + + it )
{
if ( ! it - > second . m_Enabled & & it - > second . m_Name = = name & & usedIDs . find ( it - > second . m_PlayerID ) = = usedIDs . end ( ) )
{
playerID = it - > second . m_PlayerID ;
foundPlayerID = true ;
m_PlayerAssignments . erase ( it ) ; // delete the old mapping, since we've got a new one now
break ;
}
}
}
2009-04-11 19:00:39 +02:00
2011-10-27 18:46:48 +02:00
// Otherwise pick the first free player ID
if ( ! foundPlayerID )
2009-04-11 19:00:39 +02:00
{
2011-10-27 18:46:48 +02:00
for ( playerID = 1 ; usedIDs . find ( playerID ) ! = usedIDs . end ( ) ; + + playerID )
{
// (do nothing)
}
foundPlayerID = true ;
2009-04-11 19:00:39 +02:00
}
2010-06-30 23:41:04 +02:00
PlayerAssignment assignment ;
2011-10-27 18:46:48 +02:00
assignment . m_Enabled = true ;
2010-06-30 23:41:04 +02:00
assignment . m_Name = name ;
assignment . m_PlayerID = playerID ;
m_PlayerAssignments [ guid ] = assignment ;
// Send the new assignments to all currently active players
// (which does not include the one that's just joining)
SendPlayerAssignments ( ) ;
2009-04-11 19:00:39 +02:00
}
2010-10-31 23:00:28 +01:00
void CNetServerWorker : : RemovePlayer ( const CStr & guid )
2009-04-11 19:00:39 +02:00
{
2011-10-27 18:46:48 +02:00
m_PlayerAssignments [ guid ] . m_Enabled = false ;
2009-04-11 19:00:39 +02:00
2010-07-02 23:28:48 +02:00
SendPlayerAssignments ( ) ;
2009-04-11 19:00:39 +02:00
}
2010-10-31 23:00:28 +01:00
void CNetServerWorker : : AssignPlayer ( int playerID , const CStr & guid )
2009-04-11 19:00:39 +02:00
{
2010-06-30 23:41:04 +02:00
// Remove anyone who's already assigned to this player
for ( PlayerAssignmentMap : : iterator it = m_PlayerAssignments . begin ( ) ; it ! = m_PlayerAssignments . end ( ) ; + + it )
2009-04-11 19:00:39 +02:00
{
2010-06-30 23:41:04 +02:00
if ( it - > second . m_PlayerID = = playerID )
it - > second . m_PlayerID = - 1 ;
2009-04-11 19:00:39 +02:00
}
2010-06-30 23:41:04 +02:00
// Update this host's assignment if it exists
if ( m_PlayerAssignments . find ( guid ) ! = m_PlayerAssignments . end ( ) )
m_PlayerAssignments [ guid ] . m_PlayerID = playerID ;
SendPlayerAssignments ( ) ;
2009-04-11 19:00:39 +02:00
}
2010-10-31 23:00:28 +01:00
void CNetServerWorker : : ConstructPlayerAssignmentMessage ( CPlayerAssignmentMessage & message )
2009-04-11 19:00:39 +02:00
{
2010-06-30 23:41:04 +02:00
for ( PlayerAssignmentMap : : iterator it = m_PlayerAssignments . begin ( ) ; it ! = m_PlayerAssignments . end ( ) ; + + it )
2009-04-11 19:00:39 +02:00
{
2011-10-27 18:46:48 +02:00
if ( ! it - > second . m_Enabled )
continue ;
2010-06-30 23:41:04 +02:00
CPlayerAssignmentMessage : : S_m_Hosts h ;
h . m_GUID = it - > first ;
h . m_Name = it - > second . m_Name ;
h . m_PlayerID = it - > second . m_PlayerID ;
message . m_Hosts . push_back ( h ) ;
2009-04-11 19:00:39 +02:00
}
}
2010-10-31 23:00:28 +01:00
void CNetServerWorker : : SendPlayerAssignments ( )
2009-04-11 19:00:39 +02:00
{
2010-06-30 23:41:04 +02:00
CPlayerAssignmentMessage message ;
ConstructPlayerAssignmentMessage ( message ) ;
Broadcast ( & message ) ;
2009-04-11 19:00:39 +02:00
}
2010-10-31 23:00:28 +01:00
ScriptInterface & CNetServerWorker : : GetScriptInterface ( )
2009-04-11 19:00:39 +02:00
{
2010-06-30 23:41:04 +02:00
return * m_ScriptInterface ;
2009-04-11 19:00:39 +02:00
}
2010-10-31 23:00:28 +01:00
void CNetServerWorker : : SetTurnLength ( u32 msecs )
2010-08-13 18:42:53 +02:00
{
if ( m_ServerTurnManager )
m_ServerTurnManager - > SetTurnLength ( msecs ) ;
}
2010-10-31 23:00:28 +01:00
bool CNetServerWorker : : OnClientHandshake ( void * context , CFsmEvent * event )
2009-04-11 19:00:39 +02:00
{
2011-04-30 15:01:45 +02:00
ENSURE ( event - > GetType ( ) = = ( uint ) NMT_CLIENT_HANDSHAKE ) ;
2009-04-11 19:00:39 +02:00
2010-06-30 23:41:04 +02:00
CNetServerSession * session = ( CNetServerSession * ) context ;
2010-10-31 23:00:28 +01:00
CNetServerWorker & server = session - > GetServer ( ) ;
2009-04-11 19:00:39 +02:00
2010-07-06 21:54:17 +02:00
CCliHandshakeMessage * message = ( CCliHandshakeMessage * ) event - > GetParamRef ( ) ;
if ( message - > m_ProtocolVersion ! = PS_PROTOCOL_VERSION )
2010-06-30 23:41:04 +02:00
{
2010-07-06 21:54:17 +02:00
session - > Disconnect ( NDR_INCORRECT_PROTOCOL_VERSION ) ;
return false ;
2009-04-11 19:00:39 +02:00
}
2010-07-06 21:54:17 +02:00
CSrvHandshakeResponseMessage handshakeResponse ;
handshakeResponse . m_UseProtocolVersion = PS_PROTOCOL_VERSION ;
handshakeResponse . m_Message = server . m_WelcomeMessage ;
handshakeResponse . m_Flags = 0 ;
session - > SendMessage ( & handshakeResponse ) ;
2010-06-30 23:41:04 +02:00
return true ;
2009-04-11 19:00:39 +02:00
}
2010-10-31 23:00:28 +01:00
bool CNetServerWorker : : OnAuthenticate ( void * context , CFsmEvent * event )
2009-04-11 19:00:39 +02:00
{
2011-04-30 15:01:45 +02:00
ENSURE ( event - > GetType ( ) = = ( uint ) NMT_AUTHENTICATE ) ;
2009-04-11 19:00:39 +02:00
2010-06-30 23:41:04 +02:00
CNetServerSession * session = ( CNetServerSession * ) context ;
2010-10-31 23:00:28 +01:00
CNetServerWorker & server = session - > GetServer ( ) ;
2009-04-11 19:00:39 +02:00
2011-10-27 18:46:48 +02:00
CAuthenticateMessage * message = ( CAuthenticateMessage * ) event - > GetParamRef ( ) ;
CStrW username = server . DeduplicatePlayerName ( SanitisePlayerName ( message - > m_Name ) ) ;
bool isRejoining = false ;
2010-07-06 21:54:17 +02:00
if ( server . m_State ! = SERVER_STATE_PREGAME )
{
2011-10-27 18:46:48 +02:00
// isRejoining = true; // uncomment this to test rejoining even if the player wasn't connected previously
2010-07-06 21:54:17 +02:00
2011-10-27 18:46:48 +02:00
// Search for an old disconnected player of the same name
// (TODO: if GUIDs were stable, we should use them instead)
for ( PlayerAssignmentMap : : iterator it = server . m_PlayerAssignments . begin ( ) ; it ! = server . m_PlayerAssignments . end ( ) ; + + it )
{
if ( ! it - > second . m_Enabled & & it - > second . m_Name = = username )
{
isRejoining = true ;
break ;
}
}
// Players who weren't already in the game are not allowed to join now that it's started
if ( ! isRejoining )
{
LOGMESSAGE ( L " Refused connection after game start from not-previously-known user \" %ls \" " , username . c_str ( ) ) ;
session - > Disconnect ( NDR_SERVER_ALREADY_IN_GAME ) ;
return true ;
}
}
2009-04-11 19:00:39 +02:00
2010-06-30 23:41:04 +02:00
// TODO: check server password etc?
2009-04-11 19:00:39 +02:00
2010-06-30 23:41:04 +02:00
u32 newHostID = server . m_NextHostID + + ;
2009-04-11 19:00:39 +02:00
2010-07-02 23:28:48 +02:00
session - > SetUserName ( username ) ;
2010-06-30 23:41:04 +02:00
session - > SetGUID ( message - > m_GUID ) ;
session - > SetHostID ( newHostID ) ;
2009-04-11 19:00:39 +02:00
2010-06-30 23:41:04 +02:00
CAuthenticateResultMessage authenticateResult ;
2011-10-29 16:53:13 +02:00
authenticateResult . m_Code = isRejoining ? ARC_OK_REJOINING : ARC_OK ;
2010-06-30 23:41:04 +02:00
authenticateResult . m_HostID = newHostID ;
authenticateResult . m_Message = L " Logged in " ;
session - > SendMessage ( & authenticateResult ) ;
2009-04-11 19:00:39 +02:00
2010-06-30 23:41:04 +02:00
server . OnUserJoin ( session ) ;
2009-04-11 19:00:39 +02:00
2011-10-27 18:46:48 +02:00
if ( isRejoining )
{
// Request a copy of the current game state from an existing player,
// so we can send it on to the new player
// Assume session 0 is most likely the local player, so they're
// the most efficient client to request a copy from
CNetServerSession * sourceSession = server . m_Sessions . at ( 0 ) ;
sourceSession - > GetFileTransferer ( ) . StartTask (
shared_ptr < CNetFileReceiveTask > ( new CNetFileReceiveTask_ServerRejoin ( server , newHostID ) )
) ;
session - > SetNextState ( NSS_JOIN_SYNCING ) ;
}
2010-06-30 23:41:04 +02:00
return true ;
2009-04-11 19:00:39 +02:00
}
2010-10-31 23:00:28 +01:00
bool CNetServerWorker : : OnInGame ( void * context , CFsmEvent * event )
2009-04-11 19:00:39 +02:00
{
2010-06-30 23:41:04 +02:00
// TODO: should split each of these cases into a separate method
2009-04-11 19:00:39 +02:00
2010-06-30 23:41:04 +02:00
CNetServerSession * session = ( CNetServerSession * ) context ;
2010-10-31 23:00:28 +01:00
CNetServerWorker & server = session - > GetServer ( ) ;
2009-04-11 19:00:39 +02:00
2010-06-30 23:41:04 +02:00
CNetMessage * message = ( CNetMessage * ) event - > GetParamRef ( ) ;
if ( message - > GetType ( ) = = ( uint ) NMT_SIMULATION_COMMAND )
{
CSimulationMessage * simMessage = static_cast < CSimulationMessage * > ( message ) ;
2009-04-11 19:00:39 +02:00
2010-06-30 23:41:04 +02:00
// Send it back to all clients immediately
server . Broadcast ( simMessage ) ;
2009-04-11 19:00:39 +02:00
2011-10-27 18:46:48 +02:00
// Save all the received commands
if ( server . m_SavedCommands . size ( ) < simMessage - > m_Turn + 1 )
server . m_SavedCommands . resize ( simMessage - > m_Turn + 1 ) ;
server . m_SavedCommands [ simMessage - > m_Turn ] . push_back ( * simMessage ) ;
2010-06-30 23:41:04 +02:00
// TODO: we should do some validation of ownership (clients can't send commands on behalf of opposing players)
2009-04-11 19:00:39 +02:00
2010-06-30 23:41:04 +02:00
// TODO: we shouldn't send the message back to the client that first sent it
}
else if ( message - > GetType ( ) = = ( uint ) NMT_SYNC_CHECK )
{
CSyncCheckMessage * syncMessage = static_cast < CSyncCheckMessage * > ( message ) ;
server . m_ServerTurnManager - > NotifyFinishedClientUpdate ( session - > GetHostID ( ) , syncMessage - > m_Turn , syncMessage - > m_Hash ) ;
}
else if ( message - > GetType ( ) = = ( uint ) NMT_END_COMMAND_BATCH )
{
CEndCommandBatchMessage * endMessage = static_cast < CEndCommandBatchMessage * > ( message ) ;
server . m_ServerTurnManager - > NotifyFinishedClientCommands ( session - > GetHostID ( ) , endMessage - > m_Turn ) ;
}
return true ;
2009-04-11 19:00:39 +02:00
}
2010-10-31 23:00:28 +01:00
bool CNetServerWorker : : OnChat ( void * context , CFsmEvent * event )
2009-04-11 19:00:39 +02:00
{
2011-04-30 15:01:45 +02:00
ENSURE ( event - > GetType ( ) = = ( uint ) NMT_CHAT ) ;
2009-04-11 19:00:39 +02:00
2010-06-30 23:41:04 +02:00
CNetServerSession * session = ( CNetServerSession * ) context ;
2010-10-31 23:00:28 +01:00
CNetServerWorker & server = session - > GetServer ( ) ;
2009-04-11 19:00:39 +02:00
2010-06-30 23:41:04 +02:00
CChatMessage * message = ( CChatMessage * ) event - > GetParamRef ( ) ;
2010-07-02 23:28:48 +02:00
2010-08-14 21:45:22 +02:00
message - > m_GUID = session - > GetGUID ( ) ;
2009-04-11 19:00:39 +02:00
2010-06-30 23:41:04 +02:00
server . Broadcast ( message ) ;
2009-04-11 19:00:39 +02:00
2010-06-30 23:41:04 +02:00
return true ;
2009-04-11 19:00:39 +02:00
}
2010-10-31 23:00:28 +01:00
bool CNetServerWorker : : OnLoadedGame ( void * context , CFsmEvent * event )
2009-04-11 19:00:39 +02:00
{
2011-04-30 15:01:45 +02:00
ENSURE ( event - > GetType ( ) = = ( uint ) NMT_LOADED_GAME ) ;
2009-04-11 19:00:39 +02:00
2010-06-30 23:41:04 +02:00
CNetServerSession * session = ( CNetServerSession * ) context ;
2010-10-31 23:00:28 +01:00
CNetServerWorker & server = session - > GetServer ( ) ;
2009-04-11 19:00:39 +02:00
2011-10-27 18:46:48 +02:00
// We're in the loading state, so wait until every player has loaded before
// starting the game
ENSURE ( server . m_State = = SERVER_STATE_LOADING ) ;
2010-06-30 23:41:04 +02:00
server . CheckGameLoadStatus ( session ) ;
2009-04-11 19:00:39 +02:00
2010-06-30 23:41:04 +02:00
return true ;
2009-04-11 19:00:39 +02:00
}
2011-10-27 18:46:48 +02:00
bool CNetServerWorker : : OnJoinSyncingLoadedGame ( void * context , CFsmEvent * event )
{
// A client rejoining an in-progress game has now finished loading the
// map and deserialized the initial state.
// The simulation may have progressed since then, so send any subsequent
// commands to them and set them as an active player so they can participate
// in all future turns.
//
// (TODO: if it takes a long time for them to receive and execute all these
// commands, the other players will get frozen for that time and may be unhappy;
// we could try repeating this process a few times until the client converges
// on the up-to-date state, before setting them as active.)
ENSURE ( event - > GetType ( ) = = ( uint ) NMT_LOADED_GAME ) ;
CNetServerSession * session = ( CNetServerSession * ) context ;
CNetServerWorker & server = session - > GetServer ( ) ;
CLoadedGameMessage * message = ( CLoadedGameMessage * ) event - > GetParamRef ( ) ;
u32 turn = message - > m_CurrentTurn ;
u32 readyTurn = server . m_ServerTurnManager - > GetReadyTurn ( ) ;
// Send them all commands received since their saved state,
// and turn-ended messages for any turns that have already been processed
for ( size_t i = turn + 1 ; i < std : : max ( readyTurn + 1 , ( u32 ) server . m_SavedCommands . size ( ) ) ; + + i )
{
if ( i < server . m_SavedCommands . size ( ) )
for ( size_t j = 0 ; j < server . m_SavedCommands [ i ] . size ( ) ; + + j )
session - > SendMessage ( & server . m_SavedCommands [ i ] [ j ] ) ;
if ( i < = readyTurn )
{
CEndCommandBatchMessage endMessage ;
endMessage . m_Turn = i ;
endMessage . m_TurnLength = server . m_ServerTurnManager - > GetSavedTurnLength ( i ) ;
session - > SendMessage ( & endMessage ) ;
}
}
// Tell the turn manager to expect commands from this new client
server . m_ServerTurnManager - > InitialiseClient ( session - > GetHostID ( ) , readyTurn ) ;
// Tell the client that everything has finished loading and it should start now
CLoadedGameMessage loaded ;
loaded . m_CurrentTurn = readyTurn ;
session - > SendMessage ( & loaded ) ;
return true ;
}
2010-10-31 23:00:28 +01:00
bool CNetServerWorker : : OnDisconnect ( void * context , CFsmEvent * event )
2010-07-02 23:28:48 +02:00
{
2011-04-30 15:01:45 +02:00
ENSURE ( event - > GetType ( ) = = ( uint ) NMT_CONNECTION_LOST ) ;
2010-07-02 23:28:48 +02:00
CNetServerSession * session = ( CNetServerSession * ) context ;
2010-10-31 23:00:28 +01:00
CNetServerWorker & server = session - > GetServer ( ) ;
2010-07-02 23:28:48 +02:00
2010-07-06 21:54:17 +02:00
server . OnUserLeave ( session ) ;
2010-07-02 23:28:48 +02:00
return true ;
}
2010-10-31 23:00:28 +01:00
void CNetServerWorker : : CheckGameLoadStatus ( CNetServerSession * changedSession )
2009-04-11 19:00:39 +02:00
{
2010-06-30 23:41:04 +02:00
for ( size_t i = 0 ; i < m_Sessions . size ( ) ; + + i )
2009-04-11 19:00:39 +02:00
{
2010-06-30 23:41:04 +02:00
if ( m_Sessions [ i ] ! = changedSession & & m_Sessions [ i ] - > GetCurrState ( ) ! = NSS_INGAME )
return ;
2009-04-11 19:00:39 +02:00
}
2010-06-30 23:41:04 +02:00
CLoadedGameMessage loaded ;
2011-10-27 18:46:48 +02:00
loaded . m_CurrentTurn = 0 ;
2010-06-30 23:41:04 +02:00
Broadcast ( & loaded ) ;
m_State = SERVER_STATE_INGAME ;
2009-04-11 19:00:39 +02:00
}
2010-10-31 23:00:28 +01:00
void CNetServerWorker : : StartGame ( )
2009-04-11 19:00:39 +02:00
{
2010-06-30 23:41:04 +02:00
m_ServerTurnManager = new CNetServerTurnManager ( * this ) ;
2009-04-11 19:00:39 +02:00
2010-06-30 23:41:04 +02:00
for ( size_t i = 0 ; i < m_Sessions . size ( ) ; + + i )
2011-10-27 18:46:48 +02:00
m_ServerTurnManager - > InitialiseClient ( m_Sessions [ i ] - > GetHostID ( ) , 0 ) ; // TODO: only for non-observers
2009-04-11 19:00:39 +02:00
2010-06-30 23:41:04 +02:00
m_State = SERVER_STATE_LOADING ;
2009-04-11 19:00:39 +02:00
2010-06-30 23:41:04 +02:00
// Send the final setup state to all clients
UpdateGameAttributes ( m_GameAttributes ) ;
SendPlayerAssignments ( ) ;
2009-04-11 19:00:39 +02:00
2010-06-30 23:41:04 +02:00
CGameStartMessage gameStart ;
Broadcast ( & gameStart ) ;
}
2010-10-31 23:00:28 +01:00
void CNetServerWorker : : UpdateGameAttributes ( const CScriptValRooted & attrs )
2010-06-30 23:41:04 +02:00
{
m_GameAttributes = attrs ;
if ( ! m_Host )
return ;
CGameSetupMessage gameSetupMessage ( GetScriptInterface ( ) ) ;
gameSetupMessage . m_Data = m_GameAttributes ;
Broadcast ( & gameSetupMessage ) ;
2009-04-11 19:00:39 +02:00
}
2010-07-02 23:28:48 +02:00
2010-10-31 23:00:28 +01:00
CStrW CNetServerWorker : : SanitisePlayerName ( const CStrW & original )
2010-07-02 23:28:48 +02:00
{
const size_t MAX_LENGTH = 32 ;
CStrW name = original ;
name . Replace ( L " [ " , L " { " ) ; // remove GUI tags
name . Replace ( L " ] " , L " } " ) ; // remove for symmetry
// Restrict the length
if ( name . length ( ) > MAX_LENGTH )
name = name . Left ( MAX_LENGTH ) ;
// Don't allow surrounding whitespace
name . Trim ( PS_TRIM_BOTH ) ;
// Don't allow empty name
if ( name . empty ( ) )
name = L " Anonymous " ;
return name ;
}
2010-10-31 23:00:28 +01:00
CStrW CNetServerWorker : : DeduplicatePlayerName ( const CStrW & original )
2010-07-02 23:28:48 +02:00
{
CStrW name = original ;
2010-07-06 21:54:17 +02:00
// Try names "Foo", "Foo (2)", "Foo (3)", etc
2010-07-02 23:28:48 +02:00
size_t id = 2 ;
while ( true )
{
bool unique = true ;
for ( size_t i = 0 ; i < m_Sessions . size ( ) ; + + i )
{
if ( m_Sessions [ i ] - > GetUserName ( ) = = name )
{
unique = false ;
break ;
}
}
if ( unique )
return name ;
2011-02-17 21:08:20 +01:00
name = original + L " ( " + CStrW : : FromUInt ( id + + ) + L " ) " ;
2010-07-02 23:28:48 +02:00
}
}
2010-10-31 23:00:28 +01:00
CNetServer : : CNetServer ( int autostartPlayers ) :
m_Worker ( new CNetServerWorker ( autostartPlayers ) )
{
}
CNetServer : : ~ CNetServer ( )
{
delete m_Worker ;
}
bool CNetServer : : SetupConnection ( )
{
return m_Worker - > SetupConnection ( ) ;
}
void CNetServer : : AssignPlayer ( int playerID , const CStr & guid )
{
CScopeLock lock ( m_Worker - > m_WorkerMutex ) ;
m_Worker - > m_AssignPlayerQueue . push_back ( std : : make_pair ( playerID , guid ) ) ;
}
void CNetServer : : StartGame ( )
{
CScopeLock lock ( m_Worker - > m_WorkerMutex ) ;
m_Worker - > m_StartGameQueue . push_back ( true ) ;
}
void CNetServer : : UpdateGameAttributes ( const CScriptVal & attrs , ScriptInterface & scriptInterface )
{
// Pass the attributes as JSON, since that's the easiest safe
// cross-thread way of passing script data
std : : string attrsJSON = scriptInterface . StringifyJSON ( attrs . get ( ) , false ) ;
CScopeLock lock ( m_Worker - > m_WorkerMutex ) ;
m_Worker - > m_GameAttributesQueue . push_back ( attrsJSON ) ;
}
void CNetServer : : SetTurnLength ( u32 msecs )
{
CScopeLock lock ( m_Worker - > m_WorkerMutex ) ;
m_Worker - > m_TurnLengthQueue . push_back ( msecs ) ;
}