From 8529e2b14f3a04940a2491854568d4b8d7fb563e Mon Sep 17 00:00:00 2001 From: janwas Date: Wed, 25 Jun 2008 20:34:23 +0000 Subject: [PATCH] dacian's network files were not yet completely in SVN. added the remaining files, removed obsolete ones This was SVN commit r6105. --- source/network/Client.cpp | 483 -------------- source/network/Client.h | 98 --- source/network/NetClient.cpp | 985 +++++++++++++++++++++++++++ source/network/NetClient.h | 159 +++++ source/network/NetJsEvents.h | 147 ++++ source/network/NetMessages.h | 247 +++++++ source/network/NetServer.cpp | 1039 +++++++++++++++++++++++++++++ source/network/NetServer.h | 343 ++++++++++ source/network/NetSession.cpp | 895 +++++++++++++++++++++++++ source/network/NetSession.h | 341 ++++++++++ source/network/Server.cpp | 454 ------------- source/network/Server.h | 153 ----- source/network/ServerSession.cpp | 214 ------ source/network/ServerSession.h | 74 -- source/network/Session.cpp | 8 - source/network/Session.h | 71 -- source/network/SessionManager.cpp | 86 --- source/network/SessionManager.h | 55 -- source/network/fsm.cpp | 445 ++++++++++++ source/network/fsm.h | 193 ++++++ 20 files changed, 4794 insertions(+), 1696 deletions(-) delete mode 100644 source/network/Client.cpp delete mode 100644 source/network/Client.h create mode 100644 source/network/NetClient.cpp create mode 100644 source/network/NetClient.h create mode 100644 source/network/NetJsEvents.h create mode 100644 source/network/NetMessages.h create mode 100644 source/network/NetServer.cpp create mode 100644 source/network/NetServer.h create mode 100644 source/network/NetSession.cpp create mode 100644 source/network/NetSession.h delete mode 100644 source/network/Server.cpp delete mode 100644 source/network/Server.h delete mode 100644 source/network/ServerSession.cpp delete mode 100644 source/network/ServerSession.h delete mode 100644 source/network/Session.cpp delete mode 100644 source/network/Session.h delete mode 100644 source/network/SessionManager.cpp delete mode 100644 source/network/SessionManager.h create mode 100644 source/network/fsm.cpp create mode 100644 source/network/fsm.h diff --git a/source/network/Client.cpp b/source/network/Client.cpp deleted file mode 100644 index eaa95ec57d..0000000000 --- a/source/network/Client.cpp +++ /dev/null @@ -1,483 +0,0 @@ -#include "precompiled.h" - -#include "scripting/DOMEvent.h" -#include "scripting/JSConversions.h" -#include "scripting/ScriptableObject.h" -#include "Client.h" -#include "JSEvents.h" -#include "ps/CStr.h" -#include "ps/CLogger.h" -#include "ps/CConsole.h" -#include "ps/Game.h" -#include "ps/Globals.h" // g_frequencyFilter -#include "ps/GameAttributes.h" -#include "simulation/Simulation.h" - -#define LOG_CAT_NET "net" - -CNetClient *g_NetClient=NULL; - -CNetClient::CServerSession::CServerSession(int sessionID, const CStrW& name): - m_SessionID(sessionID), - m_Name(name) -{ -} - -void CNetClient::CServerSession::ScriptingInit() -{ - AddProperty(L"id", &CNetClient::CServerSession::m_SessionID, true); - AddProperty(L"name", &CNetClient::CServerSession::m_Name, true); - - CJSObject::ScriptingInit("NetClient_ServerSession"); -} - -CNetClient::CNetClient(CGame *pGame, CGameAttributes *pGameAttribs): - CNetSession(ConnectHandler), - m_JSI_ServerSessions(&m_ServerSessions), - m_pLocalPlayerSlot(NULL), - m_pGame(pGame), - m_pGameAttributes(pGameAttribs), - m_TurnPending(false) -{ - m_pGame->GetSimulation()->SetTurnManager(this); - - g_ScriptingHost.SetGlobal("g_NetClient", OBJECT_TO_JSVAL(GetScript())); -} - -CNetClient::~CNetClient() -{ - g_ScriptingHost.SetGlobal("g_NetClient", JSVAL_NULL); - - SessionMap::iterator it=m_ServerSessions.begin(); - for (; it != m_ServerSessions.end(); ++it) - { - delete it->second; - } -} - -void CNetClient::ScriptingInit() -{ - AddMethod("beginConnect", 1); - - AddProperty(L"onStartGame", &CNetClient::m_OnStartGame); - AddProperty(L"onChat", &CNetClient::m_OnChat); - AddProperty(L"onConnectComplete", &CNetClient::m_OnConnectComplete); - AddProperty(L"onDisconnect", &CNetClient::m_OnDisconnect); - AddProperty(L"onClientConnect", &CNetClient::m_OnClientConnect); - AddProperty(L"onClientDisconnect", &CNetClient::m_OnClientDisconnect); - - AddProperty(L"password", &CNetClient::m_Password); - AddProperty(L"playerName", &CNetClient::m_Name); - AddProperty(L"sessionId", &CNetClient::m_SessionID); - - AddProperty(L"sessions", &CNetClient::m_JSI_ServerSessions); - CJSMap::ScriptingInit("NetClient_SessionMap"); - CJSObject::ScriptingInit("NetClient"); - - // Also initialize session objects - CNetClient::CServerSession::ScriptingInit(); -} - -bool CNetClient::JSI_BeginConnect(JSContext* UNUSED(cx), uintN argc, jsval *argv) -{ - CStr connectHostName; - unsigned connectPort=PS_DEFAULT_PORT; - if (argc >= 1) - { - connectHostName=g_ScriptingHost.ValueToString(argv[0]); - } - if (argc >= 2) - { - connectPort=ToPrimitive(argv[1]); - } - - PS_RESULT res=BeginConnect(connectHostName.c_str(), connectPort); - if (res != PS_OK) - { - LOG(CLogger::Error, LOG_CAT_NET, "CNetClient::JSI_Connect(): BeginConnect error: %s", res); - return false; - } - else - return true; -} - -/* TEMPLATE FOR MESSAGE HANDLERS: - -bool CNetClient::Handler(CNetMessage *pMsg, CNetSession *pSession) -{ - CNetClient *pClient=(CNetClient *)pSession; - switch (pMsg->GetType()) - { - case XXX: - break; - default: - UNHANDLED(pMsg); - } - HANDLED(pMsg); -} -*/ - -#define UNHANDLED(_pMsg) return false; -#define HANDLED(_pMsg) delete _pMsg; return true; -#define TAKEN(_pMsg) return true; -// Uglily assumes the arguments are called pMsg and pSession (which they are -// all through this file) -#define CHAIN(_chainHandler) STMT(if (_chainHandler(pMsg, pSession)) return true;) - -bool CNetClient::ConnectHandler(CNetMessage *pMsg, CNetSession *pSession) -{ - CNetClient *pClient=(CNetClient *)pSession; - - switch (pMsg->GetType()) - { - case NMT_CONNECT_COMPLETE: - pClient->m_pMessageHandler=HandshakeHandler; - if (pClient->m_OnConnectComplete.Defined()) - { - CConnectCompleteEvent evt(CStr((char *)PS_OK), true); - pClient->m_OnConnectComplete.DispatchEvent(pClient->GetScript(), &evt); - } - break; - case NMT_ERROR: - { - CNetErrorMessage *msg=(CNetErrorMessage *)pMsg; - LOG(CLogger::Error, LOG_CAT_NET, "CNetClient::ConnectHandler(): Connect Failed: %s", msg->m_Error); - if (pClient->m_OnConnectComplete.Defined()) - { - CConnectCompleteEvent evt(CStr(msg->m_Error), false); - pClient->m_OnConnectComplete.DispatchEvent(pClient->GetScript(), &evt); - } - break; - } - default: - UNHANDLED(pMsg); - } - HANDLED(pMsg); -} - -bool CNetClient::BaseHandler(CNetMessage *pMsg, CNetSession *pSession) -{ - CNetClient *pClient=(CNetClient *)pSession; - switch (pMsg->GetType()) - { - case NMT_ERROR: - { - CNetErrorMessage *msg=(CNetErrorMessage *)pMsg; - if (msg->m_State == SS_UNCONNECTED) - { - CStr message=msg->m_Error; - CDisconnectEvent evt(message); - if (pClient->m_OnDisconnect.Defined()) - pClient->m_OnDisconnect.DispatchEvent(pClient->GetScript(), &evt); - } - break; - } - default: - UNHANDLED(pMsg); - } - HANDLED(pMsg); -} - -bool CNetClient::HandshakeHandler(CNetMessage *pMsg, CNetSession *pSession) -{ - CNetClient *pClient=(CNetClient *)pSession; - - CHAIN(BaseHandler); - - switch (pMsg->GetType()) - { - case NMT_ServerHandshake: - { - CClientHandshake *msg=new CClientHandshake(); - msg->m_MagicResponse=PS_PROTOCOL_MAGIC_RESPONSE; - msg->m_ProtocolVersion=PS_PROTOCOL_VERSION; - msg->m_SoftwareVersion=PS_PROTOCOL_VERSION; - pClient->Push(msg); - break; - } - case NMT_ServerHandshakeResponse: - { - CAuthenticate *msg=new CAuthenticate(); - msg->m_Name=pClient->m_Name; - msg->m_Password=pClient->m_Password; - - pClient->m_pMessageHandler=AuthenticateHandler; - pClient->Push(msg); - - break; - } - default: - UNHANDLED(pMsg); - } - HANDLED(pMsg); -} - -bool CNetClient::AuthenticateHandler(CNetMessage *pMsg, CNetSession *pSession) -{ - CNetClient *pClient=(CNetClient *)pSession; - - CHAIN(BaseHandler); - - switch (pMsg->GetType()) - { - case NMT_AuthenticationResult: - { - CAuthenticationResult *msg=(CAuthenticationResult *)pMsg; - if (msg->m_Code != NRC_OK) - { - LOG(CLogger::Error, LOG_CAT_NET, "CNetClient::AuthenticateHandler(): Authentication failed: %ls", msg->m_Message.c_str()); - } - else - { - LOG(CLogger::Normal, LOG_CAT_NET, "CNetClient::AuthenticateHandler(): Authenticated!"); - pClient->m_SessionID=msg->m_SessionID; - pClient->m_pMessageHandler=PreGameHandler; - } - break; - } - default: - UNHANDLED(pMsg); - } - HANDLED(pMsg); -} - -bool CNetClient::PreGameHandler(CNetMessage *pMsg, CNetSession *pSession) -{ - CNetClient *pClient=(CNetClient *)pSession; - - CHAIN(BaseHandler); - CHAIN(ChatHandler); - - switch (pMsg->GetType()) - { - case NMT_StartGame: - { - pClient->OnStartGameMessage(); - HANDLED(pMsg); - } - case NMT_ClientConnect: - { - CClientConnect *msg=(CClientConnect *)pMsg; - for (size_t i=0;im_Clients.size();i++) - { - pClient->OnClientConnect(msg->m_Clients[i].m_SessionID, - msg->m_Clients[i].m_Name); - } - HANDLED(pMsg); - } - case NMT_ClientDisconnect: - { - CClientDisconnect *msg=(CClientDisconnect *)pMsg; - pClient->OnClientDisconnect(msg->m_SessionID); - HANDLED(pMsg); - } - case NMT_SetGameConfig: - { - CSetGameConfig *msg=(CSetGameConfig *)pMsg; - for (size_t i=0;im_Values.size();i++) - { - pClient->m_pGameAttributes->SetValue(msg->m_Values[i].m_Name, msg->m_Values[i].m_Value); - } - HANDLED(pMsg); - } - case NMT_AssignPlayerSlot: - { - CAssignPlayerSlot *msg=(CAssignPlayerSlot *)pMsg; - // FIXME Validate slot id to prevent us from going boom - CPlayerSlot *pSlot=pClient->m_pGameAttributes->GetSlot(msg->m_SlotID); - if (pSlot == pClient->m_pLocalPlayerSlot) - pClient->m_pLocalPlayerSlot=NULL; - switch (msg->m_Assignment) - { - case PS_ASSIGN_SESSION: - if ((int)msg->m_SessionID == (int)pClient->m_SessionID) // squelch bogus sign mismatch warning - pClient->m_pLocalPlayerSlot=pSlot; - pSlot->AssignToSessionID(msg->m_SessionID); - break; - case PS_ASSIGN_CLOSED: - pSlot->AssignClosed(); - break; - case PS_ASSIGN_OPEN: - pSlot->AssignOpen(); - break; - default: - LOG(CLogger::Warning, LOG_CAT_NET, "CNetClient::PreGameHandler(): Received an invalid slot assignment %s", msg->GetString().c_str()); - } - HANDLED(pMsg); - } - case NMT_SetPlayerConfig: - { - CSetPlayerConfig *msg=(CSetPlayerConfig *)pMsg; - // FIXME Check player ID - CPlayer *pPlayer=pClient->m_pGameAttributes->GetPlayer(msg->m_PlayerID); - for (size_t i=0;im_Values.size();i++) - { - pPlayer->SetValue(msg->m_Values[i].m_Name, msg->m_Values[i].m_Value); - } - HANDLED(pMsg); - } - default: - { - UNHANDLED(pMsg); - } - } -} - -bool CNetClient::InGameHandler(CNetMessage *pMsg, CNetSession *pSession) -{ - CNetClient *pClient=(CNetClient *)pSession; - ENetMessageType msgType=pMsg->GetType(); - - CHAIN(BaseHandler); - CHAIN(ChatHandler); - - if (msgType >= NMT_COMMAND_FIRST && msgType < NMT_COMMAND_LAST) - { - pClient->QueueIncomingMessage(pMsg); - TAKEN(pMsg); - } - - switch (msgType) - { - case NMT_EndCommandBatch: - { - CEndCommandBatch *msg=(CEndCommandBatch *)pMsg; - pClient->SetTurnLength(1, msg->m_TurnLength); - - //debug_printf("Got end of batch, setting NewTurnPending\n"); - pClient->m_TurnPending = true; - // We will ack the turn when our simulation calls NewTurn. - - HANDLED(pMsg); - } - - default: - UNHANDLED(pMsg); - } -} - -void CNetClient::QueueIncomingMessage(CNetMessage *pMsg) -{ - CScopeLock lock(m_Mutex); - debug_printf("Got a command! queueing it to 2 turns from now\n"); - QueueMessage(2, pMsg); -} - -bool CNetClient::ChatHandler(CNetMessage *pMsg, CNetSession *pSession) -{ - CNetClient *pClient=(CNetClient *)pSession; - switch (pMsg->GetType()) - { - case NMT_ChatMessage: - { - CChatMessage *msg=(CChatMessage *)pMsg; - g_Console->ReceivedChatMessage(msg->m_Sender, msg->m_Message); - if (pClient->m_OnChat.Defined()) - { - CChatEvent evt(msg->m_Sender, msg->m_Message); - pClient->m_OnChat.DispatchEvent(pClient->GetScript(), &evt); - } - HANDLED(pMsg); - } - default: - UNHANDLED(pMsg); - } -} - -void CNetClient::OnClientConnect(int sessionID, const CStrW& name) -{ - // Find existing server session, if any, and delete it - SessionMap::iterator it=m_ServerSessions.find(sessionID); - if (it != m_ServerSessions.end()) - { - delete it->second; - m_ServerSessions.erase(it); - } - - // Insert new serversession - m_ServerSessions[sessionID]=new CServerSession(sessionID, name); - - // Call JS Callback - if (m_OnClientConnect.Defined()) - { - CClientConnectEvent evt(sessionID, name); - m_OnClientConnect.DispatchEvent(GetScript(), &evt); - } -} - -void CNetClient::OnClientDisconnect(int sessionID) -{ - SessionMap::iterator it=m_ServerSessions.find(sessionID); - if (it == m_ServerSessions.end()) - { - LOG(CLogger::Warning, LOG_CAT_NET, "Server said session %d disconnected. I don't know of such a session."); - } - - // Call JS Callback - if (m_OnClientConnect.Defined()) - { - CClientDisconnectEvent evt(it->second->m_SessionID, it->second->m_Name); - m_OnClientConnect.DispatchEvent(GetScript(), &evt); - } - - // Delete server session and remove from map - delete it->second; - m_ServerSessions.erase(it); -} - -void CNetClient::OnStartGameMessage() -{ - m_pMessageHandler=InGameHandler; - debug_assert( m_OnStartGame.Defined() ); - CStartGameEvent evt; - m_OnStartGame.DispatchEvent(GetScript(), &evt); -} - -int CNetClient::StartGame() -{ - if (m_pGame->StartGame(m_pGameAttributes) != PSRETURN_OK) - { - // TODO: Send a failed-to-launch-game message and drop out. - return -1; - } - else - { - debug_printf("Client StartGame - sending end-of-batch ack\n"); - // Send an end-of-batch message for turn 0 to signal that we're ready. - CEndCommandBatch *pMsg=new CEndCommandBatch(); - pMsg->m_TurnLength=1000/g_frequencyFilter->StableFrequency(); - Push(pMsg); - return 0; - } -} - -CPlayer* CNetClient::GetLocalPlayer() -{ - return m_pLocalPlayerSlot->GetPlayer(); -} - -bool CNetClient::NewTurnReady() -{ - return m_TurnPending; -} - -void CNetClient::NewTurn() -{ - CScopeLock lock(m_Mutex); - - RotateBatches(); - ClearBatch(2); - m_TurnPending = false; - - //debug_printf("In NewTurn - sending ack\n"); - CEndCommandBatch *pMsg=new CEndCommandBatch(); - pMsg->m_TurnLength=1000/g_frequencyFilter->StableFrequency(); // JW: it'd probably be nicer to get the FPS as a parameter - Push(pMsg); -} - -void CNetClient::QueueLocalCommand(CNetMessage *pMsg) -{ - // Don't save these locally, since they'll be bounced by the server anyway - //debug_printf("Sending command from client\n"); - Push(pMsg); -} diff --git a/source/network/Client.h b/source/network/Client.h deleted file mode 100644 index 7a13f4b785..0000000000 --- a/source/network/Client.h +++ /dev/null @@ -1,98 +0,0 @@ -#ifndef INCLUDED_NETWORK_CLIENT -#define INCLUDED_NETWORK_CLIENT - -#include "Session.h" - -#include "simulation/TurnManager.h" -#include "simulation/ScriptObject.h" -#include "scripting/ScriptableObject.h" -#include "ps/CStr.h" -#include "ps/ThreadUtil.h" -#include "ps/scripting/JSMap.h" - -#include - -class CPlayerSlot; -class CPlayer; -class CGame; -class CGameAttributes; - -class CNetClient: public CNetSession, protected CTurnManager, public CJSObject -{ - class CServerSession: public CJSObject - { - public: - CServerSession(int sessionID, const CStrW& name); - - int m_SessionID; - CStrW m_Name; - - static void ScriptingInit(); - }; - typedef std::map SessionMap; - - SessionMap m_ServerSessions; - CJSMap m_JSI_ServerSessions; - - CStrW m_Password; - int m_SessionID; - - CPlayerSlot *m_pLocalPlayerSlot; - CGame *m_pGame; - CGameAttributes *m_pGameAttributes; - - // JS event scripts - CScriptObject m_OnStartGame; - CScriptObject m_OnChat; - CScriptObject m_OnConnectComplete; - CScriptObject m_OnDisconnect; - CScriptObject m_OnClientConnect; - CScriptObject m_OnClientDisconnect; - - void OnClientConnect(int sessionID, const CStrW& name); - void OnClientDisconnect(int sessionID); - void OnStartGameMessage(); - void QueueIncomingMessage(CNetMessage *pMsg); - - // JS Interface Functions - bool JSI_BeginConnect(JSContext *cx, uintN argc, jsval *argv); - - // Are we currently in a locally-yet-unsimulated turn? - // This is set to true when we receive a command batch and cleared in NewTurn(). - // The server also ensures that it does not send a new turn until we ack one. - bool m_TurnPending; - - // Mutex for accessing batches - CMutex m_Mutex; - -protected: - virtual bool NewTurnReady(); - virtual void NewTurn(); - virtual void QueueLocalCommand(CNetMessage *pMsg); - -public: - CNetClient(CGame *pGame, CGameAttributes *pGameAttribs); - virtual ~CNetClient(); - - // Launch a game through this client - int StartGame(); - - // Get a pointer to our player - CPlayer* GetLocalPlayer(); - - static MessageHandler ConnectHandler; - - static MessageHandler BaseHandler; // Common to all connected states - static MessageHandler HandshakeHandler; - static MessageHandler AuthenticateHandler; - - static MessageHandler ChatHandler; // Common to pre-game and later - static MessageHandler PreGameHandler; - static MessageHandler InGameHandler; - - static void ScriptingInit(); -}; - -extern CNetClient *g_NetClient; - -#endif //INCLUDED_NETWORK_CLIENT diff --git a/source/network/NetClient.cpp b/source/network/NetClient.cpp new file mode 100644 index 0000000000..13594cb7e1 --- /dev/null +++ b/source/network/NetClient.cpp @@ -0,0 +1,985 @@ +/** + *----------------------------------------------------------------------------- + * FILE : NetClient.cpp + * PROJECT : 0 A.D. + * DESCRIPTION : Network client class implementation file + *----------------------------------------------------------------------------- + */ + +// INCLUDES +#include "precompiled.h" +#include "NetClient.h" +#include "NetJsEvents.h" +#include "network.h" +#include "NetServer.h" +#include "scripting/DOMEvent.h" +#include "scripting/JSConversions.h" +#include "scripting/ScriptableObject.h" +#include "ps/CStr.h" +#include "ps/CLogger.h" +#include "ps/CConsole.h" +#include "ps/Game.h" +#include "ps/Globals.h" +#include "ps/GameAttributes.h" +#include "simulation/Simulation.h" + +// DECLARATIONS +#pragma warning( disable : 4100 ) + +#define LOG_CAT_NET "net" + +CNetClient *g_NetClient=NULL; +extern int fps; + +//----------------------------------------------------------------------------- +// Name: CServerPlayer() +// Desc: Constructor +//----------------------------------------------------------------------------- +CServerPlayer::CServerPlayer( uint sessionID, const CStr& nickname ) +{ + m_SessionID = sessionID; + m_Nickname = nickname; +} + +//----------------------------------------------------------------------------- +// Name: ~CServerPlayer() +// Desc: Destructor +//----------------------------------------------------------------------------- +CServerPlayer::~CServerPlayer( void ) +{ +} + +//----------------------------------------------------------------------------- +// Name: ScriptingInit() +// Desc: +//----------------------------------------------------------------------------- +void CServerPlayer::ScriptingInit( void ) +{ + AddProperty(L"id", &CServerPlayer::m_SessionID, true); + AddProperty(L"name", &CServerPlayer::m_Nickname, true); + + CJSObject::ScriptingInit( "NetClient_ServerSession" ); +} + +//----------------------------------------------------------------------------- +// Name: CNetClient() +// Desc: Constructor +//----------------------------------------------------------------------------- +CNetClient::CNetClient( CGame* pGame, CGameAttributes* pGameAttribs ) +: m_JsPlayers( &m_Players ) +{ + m_pLocalPlayerSlot = NULL; + m_pGame = pGame; + m_pGameAttributes = pGameAttribs; + m_TurnPending = false; + + m_pGame->GetSimulation()->SetTurnManager(this); + + g_ScriptingHost.SetGlobal("g_NetClient", OBJECT_TO_JSVAL(GetScript())); +} + +//----------------------------------------------------------------------------- +// Name: ~CNetClient() +// Desc: Destructor +//----------------------------------------------------------------------------- +CNetClient::~CNetClient() +{ + // Release resources + PlayerMap::iterator it = m_Players.begin(); + for ( ; it != m_Players.end(); it++ ) + { + CServerPlayer *pCurrPlayer = it->second; + if ( pCurrPlayer ) delete pCurrPlayer; + } + + m_Players.clear(); + + g_ScriptingHost.SetGlobal("g_NetClient", JSVAL_NULL); +} + +//----------------------------------------------------------------------------- +// Name: ScriptingInit() +// Desc: +//----------------------------------------------------------------------------- +void CNetClient::ScriptingInit() +{ + AddMethod("beginConnect", 1); + + AddProperty(L"onStartGame", &CNetClient::m_OnStartGame); + AddProperty(L"onChat", &CNetClient::m_OnChat); + AddProperty(L"onConnectComplete", &CNetClient::m_OnConnectComplete); + AddProperty(L"onDisconnect", &CNetClient::m_OnDisconnect); + AddProperty(L"onClientConnect", &CNetClient::m_OnPlayerJoin); + AddProperty(L"onClientDisconnect", &CNetClient::m_OnPlayerLeave); + + AddProperty(L"password", &CNetClient::m_Password); + AddProperty(L"playerName", &CNetClient::m_Nickname); + //AddProperty(L"sessionId", &CNetClient::m_SessionID); + + AddProperty(L"sessions", &CNetClient::m_JsPlayers); + + CJSMap< PlayerMap >::ScriptingInit("NetClient_SessionMap"); + CJSObject::ScriptingInit("NetClient"); +} + +//----------------------------------------------------------------------------- +// Name: Run() +// Desc: Connect to server and start main loop +//----------------------------------------------------------------------------- +bool CNetClient::SetupConnection( JSContext* pContext, uintN argc, jsval* argv ) +{ + uint port = DEFAULT_HOST_PORT; + + // Validate parameters + if ( argc == 0 ) return false; + + // Build host information + CStr host = g_ScriptingHost.ValueToString( argv[0] ); + if ( argc == 2 ) port = ToPrimitive< uint >( argv[ 1 ] ); + + // Create client host + if ( !Create() ) return false; + + // Connect to server + return Connect( host, port ); +} + +//----------------------------------------------------------------------------- +// Name: SetupSession() +// Desc: Setup client session upon creation +//----------------------------------------------------------------------------- +bool CNetClient::SetupSession( CNetSession* pSession ) +{ + // Validate parameters + if ( !pSession ) return false; + + FsmActionCtx* pContext = new FsmActionCtx; + if ( !pContext ) return false; + + pContext->pHost = this; + pContext->pSession = pSession; + + // Setup transitions for session + pSession->AddTransition( NCS_CONNECT, ( uint )NMT_SERVER_HANDSHAKE, NCS_HANDSHAKE, &OnHandshake, pContext ); + + pSession->AddTransition( NCS_HANDSHAKE, ( uint )NMT_ERROR, NCS_CONNECT, &OnError, pContext ); + pSession->AddTransition( NCS_HANDSHAKE, ( uint )NMT_SERVER_HANDSHAKE_RESPONSE, NCS_AUTHENTICATE, &OnHandshake, pContext ); + + pSession->AddTransition( NCS_AUTHENTICATE, ( uint )NMT_ERROR, NCS_CONNECT, &OnError, pContext ); + pSession->AddTransition( NCS_AUTHENTICATE, ( uint )NMT_AUTHENTICATE_RESULT, NCS_PREGAME, &OnAuthenticate, pContext ); + + pSession->AddTransition( NCS_PREGAME, ( uint )NMT_ERROR, NCS_CONNECT, &OnError, pContext ); + pSession->AddTransition( NCS_PREGAME, ( uint )NMT_GAME_SETUP, NCS_PREGAME, &OnPreGame, pContext ); + pSession->AddTransition( NCS_PREGAME, ( uint )NMT_ASSIGN_PLAYER_SLOT, NCS_PREGAME, &OnPreGame, pContext ); + pSession->AddTransition( NCS_PREGAME, ( uint )NMT_PLAYER_CONFIG, NCS_PREGAME, &OnPreGame, pContext ); + pSession->AddTransition( NCS_PREGAME, ( uint )NMT_PLAYER_JOIN, NCS_PREGAME, &OnPlayerJoin, pContext ); + pSession->AddTransition( NCS_PREGAME, ( uint )NMT_GAME_START, NCS_INGAME, &OnStartGame, pContext ); + + pSession->AddTransition( NCS_INGAME, ( uint )NMT_CHAT, NCS_INGAME, &OnChat, pContext ); + pSession->AddTransition( NCS_INGAME, ( uint )NMT_GOTO, NCS_INGAME, &OnInGame, pContext ); + pSession->AddTransition( NCS_INGAME, ( uint )NMT_PATROL, NCS_INGAME, &OnInGame, pContext ); + pSession->AddTransition( NCS_INGAME, ( uint )NMT_ADD_WAYPOINT, NCS_INGAME, &OnInGame, pContext ); + pSession->AddTransition( NCS_INGAME, ( uint )NMT_GENERIC, NCS_INGAME, &OnInGame, pContext ); + pSession->AddTransition( NCS_INGAME, ( uint )NMT_PRODUCE, NCS_INGAME, &OnInGame, pContext ); + pSession->AddTransition( NCS_INGAME, ( uint )NMT_PLACE_OBJECT, NCS_INGAME, &OnInGame, pContext ); + pSession->AddTransition( NCS_INGAME, ( uint )NMT_RUN, NCS_INGAME, &OnInGame, pContext ); + pSession->AddTransition( NCS_INGAME, ( uint )NMT_NOTIFY_REQUEST, NCS_INGAME, &OnInGame, pContext ); + pSession->AddTransition( NCS_INGAME, ( uint )NMT_FORMATION_GOTO, NCS_INGAME, &OnInGame, pContext ); + pSession->AddTransition( NCS_INGAME, ( uint )NMT_FORMATION_GENERIC, NCS_INGAME, &OnInGame, pContext ); + pSession->AddTransition( NCS_INGAME, ( uint )NMT_END_COMMAND_BATCH, NCS_INGAME, &OnInGame, pContext ); + + // Set first state + pSession->SetFirstState( NCS_CONNECT ); + + return true; +} + +//----------------------------------------------------------------------------- +// Name: HandleConnect() +// Desc: Called when the client successfully connected to server +//----------------------------------------------------------------------------- +bool CNetClient::HandleConnect( CNetSession* pSession ) +{ + // Validate parameters + if ( !pSession ) return false; + + return true; +} + +//----------------------------------------------------------------------------- +// Name: HandleDisconnect() +// Desc: Called when the client disconnected from the server +//----------------------------------------------------------------------------- +bool CNetClient::HandleDisconnect( CNetSession *pSession ) +{ + // Validate parameters + if ( !pSession ) return false; + + return true; +} + +//----------------------------------------------------------------------------- +// Name: OnError() +// Desc: +//----------------------------------------------------------------------------- +bool CNetClient::OnError( void* pContext, CFsmEvent* pEvent ) +{ + // Validate parameters + if ( !pEvent || !pContext ) return false; + + // Error event? + if ( pEvent->GetType() != NMT_ERROR ) return true; + + CNetClient* pClient = ( CNetClient* )( ( FsmActionCtx* )pContext )->pHost; + assert( pClient ); + + CErrorMessage* pMessage = ( CErrorMessage* )pEvent->GetParamRef(); + if ( pMessage ) + { + LOG( CLogger::Error, LOG_CAT_NET, "CNetClient::OnError(): Error description %s", pMessage->m_Error ); + + if ( pClient->m_OnConnectComplete.Defined() ) + { + CConnectCompleteEvent connectComplete( ( CStrW )pMessage->m_Error, false ); + pClient->m_OnConnectComplete.DispatchEvent( pClient->GetScript(), &connectComplete ); + } + } + + return true; +} + +//----------------------------------------------------------------------------- +// Name: OnPlayer() +// Desc: +//----------------------------------------------------------------------------- +bool CNetClient::OnPlayerJoin( void* pContext, CFsmEvent* pEvent ) +{ + // Validate parameters + if ( !pEvent || !pContext ) return false; + + // Connect event? + if ( pEvent->GetType() != NMT_PLAYER_JOIN ) return true; + + CNetClient* pClient = ( CNetClient* )( ( FsmActionCtx* )pContext )->pHost; + assert( pClient ); + + CPlayerJoinMessage* pMessage = ( CPlayerJoinMessage* )pEvent->GetParamRef(); + if ( pMessage ) + { + for ( uint i = 0; i < pMessage->m_Clients.size(); i++ ) + { + pClient->OnPlayer( pMessage->m_Clients[ i ].m_SessionID, pMessage->m_Clients[ i ].m_Name ); + } + + if ( pClient->m_OnConnectComplete.Defined() ) + { + CConnectCompleteEvent connectComplete( ( CStrW )PS_OK, true ); + pClient->m_OnConnectComplete.DispatchEvent( pClient->GetScript(), &connectComplete ); + } + } + + return true; +} + +//----------------------------------------------------------------------------- +// Name: OnHandshake() +// Desc: +//----------------------------------------------------------------------------- +bool CNetClient::OnHandshake( void* pContext, CFsmEvent* pEvent ) +{ + // Validate parameters + if ( !pEvent || !pContext ) return false; + + CNetClient* pClient = ( CNetClient* )( ( FsmActionCtx* )pContext )->pHost; + CNetSession* pSession = ( ( FsmActionCtx* )pContext )->pSession; + + assert( pClient ); + assert( pSession ); + + switch ( pEvent->GetType() ) + { + //case NMT_ERROR: + + // CNetClient::OnError( pContext, pEvent ); + // break; + + case NMT_SERVER_HANDSHAKE: + { + CCliHandshakeMessage handshake; + handshake.m_MagicResponse = PS_PROTOCOL_MAGIC_RESPONSE; + handshake.m_ProtocolVersion = PS_PROTOCOL_VERSION; + handshake.m_SoftwareVersion = PS_PROTOCOL_VERSION; + ( ( CNetHost* )pClient )->SendMessage( pSession, &handshake ); + } + break; + + case NMT_SERVER_HANDSHAKE_RESPONSE: + { + CAuthenticateMessage authenticate; + authenticate.m_Name = pClient->m_Nickname; + authenticate.m_Password = pClient->m_Password; + ( ( CNetHost* )pClient )->SendMessage( pSession, &authenticate ); + } + break; + } + + return true; +} + +//----------------------------------------------------------------------------- +// Name: OnAuthenticate() +// Desc: +//----------------------------------------------------------------------------- +bool CNetClient::OnAuthenticate( void* pContext, CFsmEvent* pEvent ) +{ + // Validate parameters + if ( !pEvent || !pContext ) return false; + + CNetClient* pClient = ( CNetClient* )( ( FsmActionCtx* )pContext )->pHost; + CNetSession* pSession = ( ( FsmActionCtx* )pContext )->pSession; + + assert( pClient ); + assert( pSession ); + + if ( pEvent->GetType() == NMT_ERROR ) + { + // return CNetClient::OnError( pContext, pEvent ); + } + else if ( pEvent->GetType() == NMT_AUTHENTICATE_RESULT ) + { + CAuthenticateResultMessage* pMessage =( CAuthenticateResultMessage* )pEvent->GetParamRef(); + if ( !pMessage ) return true; + + LOG(CLogger::Error, LOG_CAT_NET, "CNetClient::OnAuthenticate(): Authentication result: %s", pMessage->m_Message.c_str() ); + + pSession->SetID( pMessage->m_SessionID ); + } + + return true; +} + +//----------------------------------------------------------------------------- +// Name: OnPreGame() +// Desc: +//----------------------------------------------------------------------------- +bool CNetClient::OnPreGame( void* pContext, CFsmEvent* pEvent ) +{ + // Validate parameters + if ( !pEvent || !pContext ) return false; + + CNetClient* pClient = ( CNetClient* )( ( FsmActionCtx* )pContext )->pHost; + CNetSession* pSession = ( CNetSession* )( ( FsmActionCtx* )pContext )->pSession; + + assert( pClient ); + assert( pSession ); + + switch ( pEvent->GetType() ) + { + + //CHAIN(BaseHandler); + //CHAIN(ChatHandler); + +// case NMT_GAME_START: + +// pClient->StartGame(); +// break; + + case NMT_PLAYER_LEAVE: + { + CPlayerLeaveMessage* pMessage = ( CPlayerLeaveMessage* )pEvent->GetParamRef(); + if ( !pMessage ) return false; + + pClient->OnPlayerLeave( pMessage->m_SessionID ); + } + break; + + case NMT_GAME_SETUP: + { + CGameSetupMessage* pMessage = ( CGameSetupMessage* )pEvent->GetParamRef(); + + for ( uint i = 0; i < pMessage->m_Values.size(); i++ ) + { + pClient->m_pGameAttributes->SetValue( pMessage->m_Values[ i ].m_Name, pMessage->m_Values[ i ].m_Value ); + } + } + break; + + case NMT_ASSIGN_PLAYER_SLOT: + { + CAssignPlayerSlotMessage* pMessage = ( CAssignPlayerSlotMessage* )pEvent->GetParamRef(); + + // FIXME Validate slot id to prevent us from going boom + CPlayerSlot* pSlot = pClient->m_pGameAttributes->GetSlot( pMessage->m_SlotID ); + if ( pSlot == pClient->m_pLocalPlayerSlot ) + pClient->m_pLocalPlayerSlot = NULL; + + switch ( pMessage->m_Assignment ) + { + case ASSIGN_SESSION: + { + // TODO: Check where is the best place to assign client's session ID + //if ((int)msg->m_SessionID == (int)pClient->m_SessionID) + // pClient->m_pLocalPlayerSlot=pSlot; + + pSession->SetID( pMessage->m_SessionID ); + + pClient->m_pLocalPlayerSlot = pSlot; + + pSlot->AssignToSessionID( pMessage->m_SessionID ); + } + break; + + case ASSIGN_CLOSED: + pSlot->AssignClosed(); + break; + + case ASSIGN_OPEN: + pSlot->AssignOpen(); + break; + + default: + LOG( CLogger::Warning, LOG_CAT_NET, "Invalid slot assignment %s", pMessage->ToString().c_str() ); + break; + } + } + break; + + case NMT_PLAYER_CONFIG: + { + CPlayerConfigMessage* pMessage = ( CPlayerConfigMessage* )pEvent->GetParamRef(); + + // FIXME Check player ID + CPlayer* pPlayer = pClient->m_pGameAttributes->GetPlayer( pMessage->m_PlayerID ); + + for ( uint i = 0; i < pMessage->m_Values.size(); i++ ) + { + pPlayer->SetValue( pMessage->m_Values[i].m_Name, pMessage->m_Values[i].m_Value ); + } + } + break; + } + + return true; +} + +//----------------------------------------------------------------------------- +// Name: OnInGame() +// Desc: +//----------------------------------------------------------------------------- +bool CNetClient::OnInGame( void *pContext, CFsmEvent* pEvent ) +{ + // Validate parameters + if ( !pEvent || !pContext ) return false; + + CNetClient* pClient = ( CNetClient* )( ( FsmActionCtx* )pContext )->pHost; + assert( pClient ); + + // ??? + if ( pEvent->GetType() >= NMT_COMMAND_FIRST && pEvent->GetType() < NMT_COMMAND_LAST ) + { + if ( pEvent->GetParamRef() ) + { + pClient->QueueMessage( 1, ( CNetMessage* )pEvent->GetParamRef() ); + + return true; + } + } + + switch ( pEvent->GetType() ) + { + //case NMT_ERROR: + + // CNetClient::OnError( pContext, pEvent ); + // break; + + //case NMT_CHAT: + + // CNetClient::OnChat( pContext, pEvent ); + // break; + + case NMT_END_COMMAND_BATCH: + { + CEndCommandBatchMessage* pMessage = ( CEndCommandBatchMessage* )pEvent->GetParamRef(); + if ( !pMessage ) return false; + + pClient->SetTurnLength( 1, pMessage->m_TurnLength ); + + pClient->m_TurnPending = true; + + // FIXME When the command batch has ended, we should start accepting + // commands for the next turn. This will be accomplished by calling + // NewTurn. *BUT* we shouldn't prematurely proceed game simulation + // since this will produce jerky playback (everything expects a sim + // turn to have a certain duration). + + // We should make sure that any commands received after this message + // are queued in the next batch (#2 instead of #1). If we're already + // putting everything new in batch 2 - we should fast-forward a bit to + // catch up with the server. + } + } + + return true; +} + +//----------------------------------------------------------------------------- +// Name: OnChat() +// Desc: +//----------------------------------------------------------------------------- +bool CNetClient::OnChat( void* pContext, CFsmEvent* pEvent ) +{ + // Validate parameters + if ( !pEvent || !pContext ) return false; + + CNetClient* pClient = ( CNetClient* )( ( FsmActionCtx* )pContext )->pHost; + + if ( pEvent->GetType() == NMT_CHAT ) + { + CChatMessage* pMessage = ( CChatMessage* )pEvent->GetParamRef(); + if ( !pMessage ) return false; + + g_Console->ReceivedChatMessage( pMessage->m_Sender, pMessage->m_Message ); + + if ( pClient->m_OnChat.Defined() ) + { + CChatEvent evt( pMessage->m_Sender, pMessage->m_Message ); + pClient->m_OnChat.DispatchEvent( pClient->GetScript(), &evt ); + } + } + + return true; +} + +//----------------------------------------------------------------------------- +// Name: OnStartGame() +// Desc: +//----------------------------------------------------------------------------- +bool CNetClient::OnStartGame( void* pContext, CFsmEvent* pEvent ) +{ + // Validate parameters + if ( !pEvent || !pContext ) return false; + + CNetClient* pClient = ( CNetClient* )( ( FsmActionCtx* )pContext )->pHost; + + if ( pClient->m_OnStartGame.Defined() ) + { + CStartGameEvent event; + pClient->m_OnStartGame.DispatchEvent( pClient->GetScript(), &event ); + } + + return true; +} + +//----------------------------------------------------------------------------- +// Name: OnPlayerJoin() +// Desc: +//----------------------------------------------------------------------------- +void CNetClient::OnPlayer( uint ID, const CStr& name ) +{ + CServerPlayer* pNewPlayer = new CServerPlayer( ID, name ); + if ( !pNewPlayer ) return; + + // Store new player + m_Players[ ID ] = pNewPlayer; + + // Call JS Callback + if ( m_OnPlayerJoin.Defined() ) + { + CClientConnectEvent event( ID, name ); + m_OnPlayerJoin.DispatchEvent( GetScript(), &event ); + } +} + +//----------------------------------------------------------------------------- +// Name: OnPlayerLeave() +// Desc: +//----------------------------------------------------------------------------- +void CNetClient::OnPlayerLeave( uint ID ) +{ + // Lookup player + PlayerMap::iterator it = m_Players.find( ID ); + if ( it == m_Players.end() ) + { + LOG( CLogger::Warning, LOG_CAT_NET, "CNetClient::OnPlayerLeav(): No such player %d.", ID ); + return; + } + + // Call JS Callback + if ( m_OnPlayerLeave.Defined() && it->second ) + { + CClientDisconnectEvent event( it->second->GetSessionID(), it->second->GetNickname() ); + m_OnPlayerLeave.DispatchEvent( GetScript(), &event ); + } + + // Remove player from internal map + m_Players.erase( it ); +} + +//----------------------------------------------------------------------------- +// Name: StartGame() +// Desc: +//----------------------------------------------------------------------------- +int CNetClient::StartGame( void ) +{ + assert ( m_pGame ); + assert ( m_pGameAttributes ); + + if ( m_pGame->StartGame( m_pGameAttributes ) != PSRETURN_OK ) return -1; + + // Send an end-of-batch message for turn 0 to signal that we're ready. + CEndCommandBatchMessage endBatch; + endBatch.m_TurnLength = 1000 / g_frequencyFilter->StableFrequency(); + + CNetSession* pSession = GetSession( 0 ); + CNetHost::SendMessage( pSession, &endBatch ); + + return 0; +} + +//----------------------------------------------------------------------------- +// Name: GetLocalPlayer() +// Desc: +//----------------------------------------------------------------------------- +CPlayer* CNetClient::GetLocalPlayer() +{ + return m_pLocalPlayerSlot->GetPlayer(); +} + +//----------------------------------------------------------------------------- +// Name: NewTurn() +// Desc: +//----------------------------------------------------------------------------- +void CNetClient::NewTurn() +{ + CScopeLock lock(m_Mutex); + + RotateBatches(); + ClearBatch(2); + m_TurnPending = false; + + //debug_printf("In NewTurn - sending ack\n"); + CEndCommandBatchMessage* pMsg = new CEndCommandBatchMessage; + pMsg->m_TurnLength=1000/g_frequencyFilter->StableFrequency(); // JW: it'd probably be nicer to get the FPS as a parameter + + CNetSession* pSession = GetSession( 0 ); + CNetHost::SendMessage( pSession, pMsg ); +} + +//----------------------------------------------------------------------------- +// Name: QueueLocalCommand() +// Desc: +//----------------------------------------------------------------------------- +void CNetClient::QueueLocalCommand( CNetMessage* pMessage ) +{ + if ( !pMessage ) return; + + // Don't save these locally, since they'll be bounced by the server anyway + CNetSession* pSession = GetSession( 0 ); + CNetHost::SendMessage( pSession, pMessage ); +} + +/* +void CNetClient::OnStartGameMessage() +{ + m_pMessageHandler=CNetClient::InGameHandler; + debug_assert( m_OnStartGame.Defined() ); + CStartGameEvent evt; + m_OnStartGame.DispatchEvent(GetScript(), &evt); +} + +int CNetClient::StartGame() +{ + if (m_pGame->StartGame(m_pGameAttributes) != PSRETURN_OK) + { + return -1; + } + else + { + return 0; + } +} + +CPlayer* CNetClient::GetLocalPlayer() +{ + return m_pLocalPlayerSlot->GetPlayer(); +} + +void CNetClient::NewTurn() +{ + RotateBatches(); + ClearBatch(2); + + CEndCommandBatchMessage *pMsg=new CEndCommandBatchMessage(); + pMsg->m_TurnLength=1000/fps; + + if ( m_Session ) + m_Session->SendMessage( *pMsg ); +} + +void CNetClient::QueueLocalCommand(CNetMessage *pMsg) +{ + // Don't save these locally, since they'll be bounced by the server anyway + if ( m_ServerSession ) + m_Session->SendMessage( *pMsg ); +} + +bool CNetClient::ConnectHandler(CNetMessage *pMsg, CNetSession *pSession) +{ + CNetClient *pClient=(CNetClient *)pSession; + + switch (pMsg->GetHeader().GetType()) + { + case NMT_CONNECT_COMPLETE: + pClient->m_pMessageHandler=HandshakeHandler; + if (pClient->m_OnConnectComplete.Defined()) + { + CConnectCompleteEvent evt(CStr((char *)PS_OK), true); + pClient->m_OnConnectComplete.DispatchEvent(pClient->GetScript(), &evt); + } + break; + case NMT_ERROR: + { + CNetErrorMessage *msg=(CNetErrorMessage *)pMsg; + LOG(ERROR, LOG_CAT_NET, "CNetClient::ConnectHandler(): Connect Failed: %s", msg->m_Error); + if (pClient->m_OnConnectComplete.Defined()) + { + CConnectCompleteEvent evt(CStr(msg->m_Error), false); + pClient->m_OnConnectComplete.DispatchEvent(pClient->GetScript(), &evt); + } + break; + } + default: + UNHANDLED(pMsg); + } + HANDLED(pMsg); +} + +bool CNetClient::BaseHandler(CNetMessage *pMsg, CNetSession *pSession) +{ + CNetClient *pClient=(CNetClient *)pSession; + switch (pMsg->GetType()) + { + case NMT_ERROR: + { + CNetErrorMessage *msg=(CNetErrorMessage *)pMsg; + if (msg->m_State == SS_UNCONNECTED) + { + CStr message=msg->m_Error; + CDisconnectEvent evt(message); + if (pClient->m_OnDisconnect.Defined()) + pClient->m_OnDisconnect.DispatchEvent(pClient->GetScript(), &evt); + } + break; + } + default: + UNHANDLED(pMsg); + } + HANDLED(pMsg); +} + +bool CNetClient::HandshakeHandler(CNetMessage *pMsg, CNetSession *pSession) +{ + CNetClient *pClient=(CNetClient *)pSession; + + CHAIN(BaseHandler); + + switch (pMsg->GetType()) + { + case NMT_ServerHandshake: + { + CClientHandshake *msg=new CClientHandshake(); + msg->m_MagicResponse=PS_PROTOCOL_MAGIC_RESPONSE; + msg->m_ProtocolVersion=PS_PROTOCOL_VERSION; + msg->m_SoftwareVersion=PS_PROTOCOL_VERSION; + pClient->Push(msg); + break; + } + case NMT_ServerHandshakeResponse: + { + CAuthenticate *msg=new CAuthenticate(); + msg->m_Name=pClient->m_Name; + msg->m_Password=pClient->m_Password; + + pClient->m_pMessageHandler=AuthenticateHandler; + pClient->Push(msg); + + break; + } + default: + UNHANDLED(pMsg); + } + HANDLED(pMsg); +} + +bool CNetClient::AuthenticateHandler(CNetMessage *pMsg, CNetSession *pSession) +{ + CNetClient *pClient=(CNetClient *)pSession; + + CHAIN(BaseHandler); + + switch (pMsg->GetType()) + { + case NMT_AuthenticationResult: + { + CAuthenticationResult *msg=(CAuthenticationResult *)pMsg; + if (msg->m_Code != NRC_OK) + { + LOG(ERROR, LOG_CAT_NET, "CNetClient::AuthenticateHandler(): Authentication failed: %ls", msg->m_Message.c_str()); + } + else + { + LOG(NORMAL, LOG_CAT_NET, "CNetClient::AuthenticateHandler(): Authenticated!"); + pClient->m_SessionID=msg->m_SessionID; + pClient->m_pMessageHandler=PreGameHandler; + } + break; + } + default: + UNHANDLED(pMsg); + } + HANDLED(pMsg); +} + +bool CNetClient::PreGameHandler(CNetMessage *pMsg, CNetSession *pSession) +{ + CNetClient *pClient=(CNetClient *)pSession; + + CHAIN(BaseHandler); + CHAIN(ChatHandler); + + switch (pMsg->GetType()) + { + case NMT_StartGame: + { + pClient->OnStartGameMessage(); + HANDLED(pMsg); + } + case NMT_ClientConnect: + { + CClientConnect *msg=(CClientConnect *)pMsg; + for (uint i=0;im_Clients.size();i++) + { + pClient->OnClientConnect(msg->m_Clients[i].m_SessionID, + msg->m_Clients[i].m_Name); + } + HANDLED(pMsg); + } + case NMT_ClientDisconnect: + { + CClientDisconnect *msg=(CClientDisconnect *)pMsg; + pClient->OnClientDisconnect(msg->m_SessionID); + HANDLED(pMsg); + } + case NMT_SetGameConfig: + { + CSetGameConfig *msg=(CSetGameConfig *)pMsg; + for (uint i=0;im_Values.size();i++) + { + pClient->m_pGameAttributes->SetValue(msg->m_Values[i].m_Name, msg->m_Values[i].m_Value); + } + HANDLED(pMsg); + } + case NMT_AssignPlayerSlot: + { + CAssignPlayerSlot *msg=(CAssignPlayerSlot *)pMsg; + // FIXME Validate slot id to prevent us from going boom + CPlayerSlot *pSlot=pClient->m_pGameAttributes->GetSlot(msg->m_SlotID); + if (pSlot == pClient->m_pLocalPlayerSlot) + pClient->m_pLocalPlayerSlot=NULL; + switch (msg->m_Assignment) + { + case PS_ASSIGN_SESSION: + if ((int)msg->m_SessionID == (int)pClient->m_SessionID) // squelch bogus sign mismatch warning + pClient->m_pLocalPlayerSlot=pSlot; + pSlot->AssignToSessionID(msg->m_SessionID); + break; + case PS_ASSIGN_CLOSED: + pSlot->AssignClosed(); + break; + case PS_ASSIGN_OPEN: + pSlot->AssignOpen(); + break; + default: + LOG(WARNING, LOG_CAT_NET, "CNetClient::PreGameHandler(): Received an invalid slot assignment %s", msg->GetString().c_str()); + } + HANDLED(pMsg); + } + case NMT_SetPlayerConfig: + { + CSetPlayerConfig *msg=(CSetPlayerConfig *)pMsg; + // FIXME Check player ID + CPlayer *pPlayer=pClient->m_pGameAttributes->GetPlayer(msg->m_PlayerID); + for (uint i=0;im_Values.size();i++) + { + pPlayer->SetValue(msg->m_Values[i].m_Name, msg->m_Values[i].m_Value); + } + HANDLED(pMsg); + } + default: + { + UNHANDLED(pMsg); + } + } +} + +bool CNetClient::InGameHandler(CNetMessage *pMsg, CNetSession *pSession) +{ + CNetClient *pClient=(CNetClient *)pSession; + ENetMessageType msgType=pMsg->GetType(); + + CHAIN(BaseHandler); + CHAIN(ChatHandler); + + if (msgType >= NMT_COMMAND_FIRST && msgType < NMT_COMMAND_LAST) + { + pClient->QueueMessage(1, pMsg); + TAKEN(pMsg); + } + + switch (msgType) + { + case NMT_EndCommandBatch: + { + CEndCommandBatch *msg=(CEndCommandBatch *)pMsg; + pClient->SetTurnLength(1, msg->m_TurnLength); + + // FIXME When the command batch has ended, we should start accepting + // commands for the next turn. This will be accomplished by calling + // NewTurn. *BUT* we shouldn't prematurely proceed game simulation + // since this will produce jerky playback (everything expects a sim + // turn to have a certain duration). + + // We should make sure that any commands received after this message + // are queued in the next batch (#2 instead of #1). If we're already + // putting everything new in batch 2 - we should fast-forward a bit to + // catch up with the server. + + HANDLED(pMsg); + } + + default: + UNHANDLED(pMsg); + } +} + +bool CNetClient::ChatHandler(CNetMessage *pMsg, CNetSession *pSession) +{ + CNetClient *pClient=(CNetClient *)pSession; + switch (pMsg->GetType()) + { + case NMT_ChatMessage: + { + CChatMessage *msg=(CChatMessage *)pMsg; + g_Console->ReceivedChatMessage(msg->m_Sender, msg->m_Message); + if (pClient->m_OnChat.Defined()) + { + CChatEvent evt(msg->m_Sender, msg->m_Message); + pClient->m_OnChat.DispatchEvent(pClient->GetScript(), &evt); + } + HANDLED(pMsg); + } + default: + UNHANDLED(pMsg); + } +}*/ + diff --git a/source/network/NetClient.h b/source/network/NetClient.h new file mode 100644 index 0000000000..bd7b87c182 --- /dev/null +++ b/source/network/NetClient.h @@ -0,0 +1,159 @@ +/** + *----------------------------------------------------------------------------- + * FILE : NetClient.h + * PROJECT : 0 A.D. + * DESCRIPTION : Network client class interface file + *----------------------------------------------------------------------------- + */ + +#ifndef NETCLIENT_H +#define NETCLIENT_H + +// INCLUDES +#include "NetSession.h" +#include "ps/CStr.h" +#include "simulation/TurnManager.h" +#include "simulation/ScriptObject.h" +#include "scripting/ScriptableObject.h" +#include "ps/scripting/JSMap.h" + +#include + +// DECLARATIONS +enum +{ + NCS_CONNECT = 200, + NCS_HANDSHAKE = 300, + NCS_AUTHENTICATE = 400, + NCS_PREGAME = 500, + NCS_INGAME = 600 +}; + +class CPlayerSlot; +class CPlayer; +class CGame; +class CGameAttributes; +class CServerPlayer; + +//typedef std::map< uint, CStr > PlayerMap; +typedef std::map< uint, CServerPlayer* > PlayerMap; + +/* + CLASS : CServerPlayer + DESCRIPTION : + NOTES : +*/ +class CServerPlayer : public CJSObject< CServerPlayer > +{ +public: + + CServerPlayer( uint sessionID, const CStr& nickname ); + ~CServerPlayer( void ); + + static void ScriptingInit( void ); + uint GetSessionID( void ) const { return m_SessionID; } + const CStr GetNickname( void ) const { return m_Nickname; } +protected: + +private: + + CServerPlayer( const CServerPlayer& ); + CServerPlayer& operator=( const CServerPlayer& ); + + uint m_SessionID; // Player session ID + CStr m_Nickname; // Player nickname +}; + +/* + CLASS : CNetClient + DESCRIPTION : + NOTES : +*/ + +class CNetClient: public CNetHost, + public CJSObject, + protected CTurnManager +{ +public: + + CNetClient( CGame* pGame, CGameAttributes* pGameAttributes ); + ~CNetClient( void ); + + bool CreateSession ( void ); + void OnPlayer ( uint ID, const CStr& name ); + void OnPlayerLeave ( uint ID ); + + /** + * Returns true indicating the host acts as a client + * + * @return Always true + */ + virtual bool IsClient( void ) const { return true; } + + // Get a pointer to our player + CPlayer* GetLocalPlayer(); + + CJSMap< PlayerMap > m_JsPlayers; + + CStr m_Nickname; + CStr m_Password; + //int m_SessionID; + + CPlayerSlot *m_pLocalPlayerSlot; + CGame *m_pGame; + CGameAttributes *m_pGameAttributes; + + // Are we currently in a locally-yet-unsimulated turn? + // This is set to true when we receive a command batch and cleared in NewTurn(). + // The server also ensures that it does not send a new turn until we ack one. + bool m_TurnPending; + + // Mutex for accessing batches + CMutex m_Mutex; + + // JS event scripts + CScriptObject m_OnStartGame; + CScriptObject m_OnChat; + CScriptObject m_OnConnectComplete; + CScriptObject m_OnDisconnect; + CScriptObject m_OnPlayerJoin; + CScriptObject m_OnPlayerLeave; + + static void ScriptingInit( void ); + int StartGame( void ); + +protected: + + virtual bool SetupSession ( CNetSession* pSession ); + virtual bool HandleConnect ( CNetSession* pSession ); + virtual bool HandleDisconnect ( CNetSession *pSession ); + + virtual void NewTurn ( void ); + virtual bool NewTurnReady ( void ) { return m_TurnPending; } + virtual void QueueLocalCommand ( CNetMessage* pMessage ); + +private: + + // Not implemented + CNetClient( const CNetClient& ); + CNetClient& operator=( const CNetClient& ); + + static bool OnError ( void* pContext, CFsmEvent* pEvent ); + static bool OnPlayerJoin ( void* pContext, CFsmEvent* pEvent ); + static bool OnHandshake ( void* pContext, CFsmEvent* pEvent ); + static bool OnAuthenticate ( void* pContext, CFsmEvent* pEvent ); + static bool OnPreGame ( void* pContext, CFsmEvent* pEvent ); + static bool OnInGame ( void* pContext, CFsmEvent* pEvent ); + static bool OnChat ( void* pContext, CFsmEvent* pEvent ); + static bool OnStartGame ( void* pContext, CFsmEvent* pEvent ); + + + bool SetupConnection( JSContext *cx, uintN argc, jsval *argv ); + + //CNetSession* m_Session; // Server session + PlayerMap m_Players; // List of online players +}; + +extern CNetClient *g_NetClient; + +#endif // NETCLIENT_H diff --git a/source/network/NetJsEvents.h b/source/network/NetJsEvents.h new file mode 100644 index 0000000000..d242d5637c --- /dev/null +++ b/source/network/NetJsEvents.h @@ -0,0 +1,147 @@ +/* + *----------------------------------------------------------------------------- + * FILE : NetJsEvents.h + * PROJECT : 0 A.D. + * DESCRIPTION : Definitions for JavaScript events used by the network + * system + *----------------------------------------------------------------------------- + */ + +#ifndef NETJSEVENTS_H +#define NETJSEVENTS_H + +// INCLUDES +#include "NetSession.h" +#include "scripting/DOMEvent.h" + +// DEFINES +enum NetJsEvents +{ + JS_EVENT_CLIENT_CONNECT, + JS_EVENT_CONNECT_COMPLETE, + JS_EVENT_CLIENT_DISCONNECT, + JS_EVENT_DISCONNECT, + JS_EVENT_START_GAME, + JS_EVENT_CHAT, + JS_EVENT_LAST +}; + +class CStartGameEvent: public CScriptEvent +{ +public: + CStartGameEvent(): + CScriptEvent(L"startGame", JS_EVENT_START_GAME, false) + {} +}; + +class CChatEvent: public CScriptEvent +{ + CStr m_Sender; + CStr m_Message; + +public: + CChatEvent(const CStr& sender, const CStr& message): + CScriptEvent(L"chat", JS_EVENT_CHAT, false ), + m_Sender(sender), + m_Message(message) + { + AddLocalProperty(L"sender", &m_Sender, true); + AddLocalProperty(L"message", &m_Message, true); + } +}; + +class CConnectCompleteEvent: public CScriptEvent +{ + CStr m_Message; + bool m_Success; + +public: + CConnectCompleteEvent(const CStr& message, bool success): + CScriptEvent(L"connectComplete", JS_EVENT_CONNECT_COMPLETE, false), + m_Message(message), + m_Success(success) + { + AddLocalProperty(L"message", &m_Message, true); + AddLocalProperty(L"success", &m_Success, true); + } +}; + +class CDisconnectEvent: public CScriptEvent +{ + CStrW m_Message; + +public: + CDisconnectEvent(const CStr& message): + CScriptEvent(L"disconnect", JS_EVENT_DISCONNECT, false), + m_Message(message) + { + AddLocalProperty(L"message", &m_Message, true); + } +}; + +class CClientConnectDisconnectCommon: public CScriptEvent +{ + uint m_SessionID; + CStr m_Name; + CNetSession *m_Session; + +public: + + CClientConnectDisconnectCommon(const wchar_t* UNUSED(eventName), int UNUSED(eventType), + int sessionID, const CStr& name, CNetSession* pSession) + : CScriptEvent(L"clientConnect", JS_EVENT_CLIENT_CONNECT, false), + m_SessionID(sessionID), + m_Name(name), + m_Session(pSession) + { + AddLocalProperty(L"id", &m_SessionID, true); + AddLocalProperty(L"name", &m_Name, true); + if (m_Session) + AddLocalProperty(L"session", &m_Session, true); + } +}; + +struct CClientConnectEvent: public CClientConnectDisconnectCommon +{ + CClientConnectEvent(int sessionID, const CStr& name): + CClientConnectDisconnectCommon( + L"clientConnect", + JS_EVENT_CLIENT_CONNECT, + sessionID, + name, + NULL) + {} + + CClientConnectEvent(CNetSession *pSession): + CClientConnectDisconnectCommon( + L"clientConnect", + JS_EVENT_CLIENT_CONNECT, + pSession->GetID(), + pSession->GetName(), + pSession) + {} +}; + +struct CClientDisconnectEvent: public CClientConnectDisconnectCommon +{ + CClientDisconnectEvent(int sessionID, const CStr& name): + CClientConnectDisconnectCommon( + L"clientDisconnect", + JS_EVENT_CLIENT_DISCONNECT, + sessionID, + name, + NULL) + {} + + CClientDisconnectEvent(CNetSession *pSession): + CClientConnectDisconnectCommon( + L"clientDisconnect", + JS_EVENT_CLIENT_DISCONNECT, + pSession->GetID(), + pSession->GetName(), + pSession) + {} +}; + +#endif // NETJSEVENTS_H + diff --git a/source/network/NetMessages.h b/source/network/NetMessages.h new file mode 100644 index 0000000000..eab8ef2110 --- /dev/null +++ b/source/network/NetMessages.h @@ -0,0 +1,247 @@ +/** + *----------------------------------------------------------------------------- + * FILE : NetMessages.h + * PROJECT : 0 A.D. + * DESCRIPTION : The list of messages used by the network subsystem + *----------------------------------------------------------------------------- + */ + +#ifndef NETMESSAGES_H +#define NETMESSAGES_H + +// INCLUDES +#include "ps/CStr.h" +#include "scripting/JSSerialization.h" +#include "simulation/EntityHandles.h" + +// DEFINES +#define PS_PROTOCOL_MAGIC 0x5073013f // 'P', 's', 0x01, '?' +#define PS_PROTOCOL_MAGIC_RESPONSE 0x50630121 // 'P', 'c', 0x01, '!' +#define PS_PROTOCOL_VERSION 0x01010002 // Arbitrary protocol +#define PS_DEFAULT_PORT 0x5073 // 'P', 's' + +// Defines the list of message types. The order of the list must not change +// The message types having a negative value are used internally and not sent +// over the network. The message types used for network communication have +// positive values. +enum NetMessageType +{ + NMT_ERROR = -256, // Delivery of error states + NMT_CONNECT_COMPLETE, // Connection is complete + NMT_CLOSE_REQUEST, // Close connection request + NMT_INVALID = 0, // Invalid message + NMT_SERVER_HANDSHAKE, // Handshake stage + NMT_CLIENT_HANDSHAKE, + NMT_SERVER_HANDSHAKE_RESPONSE, + NMT_AUTHENTICATE, // Authentication stage + NMT_AUTHENTICATE_RESULT, + NMT_CHAT, // Common chat message + NMT_PLAYER_JOIN, // Pre-game stage + NMT_PLAYER_LEAVE, + NMT_GAME_SETUP, + NMT_ASSIGN_PLAYER_SLOT, + NMT_PLAYER_CONFIG, + NMT_FILES_REQUIRED, + NMT_FILE_REQUEST, + NMT_FILE_CHUNK, + NMT_FILE_CHUNK_ACK, + NMT_FILE_PROGRESS, + NMT_GAME_START, + NMT_END_COMMAND_BATCH, // In-game stage + NMT_GOTO, + NMT_COMMAND_FIRST = NMT_GOTO, + NMT_PATROL, + NMT_ADD_WAYPOINT, + NMT_GENERIC, + NMT_PRODUCE, + NMT_PLACE_OBJECT, + NMT_RUN, + NMT_NOTIFY_REQUEST, + NMT_FORMATION_GOTO, + NMT_FORMATION_GENERIC, + NMT_COMMAND_LAST, + NMT_LAST // Last message in the list +}; + +// Authentication result codes +enum AuthenticateResultCode +{ + ARC_OK, + ARC_PASSWORD_INVALID, + ARC_NICK_TAKEN, + ARC_NICK_INVALID, +}; + +enum +{ + CHAT_RECIPIENT_FIRST = 0xFFFD, + CHAT_RECIPIENT_ENEMIES = 0xFFFD, + CHAT_RECIPIENT_ALLIES = 0xFFFE, + CHAT_RECIPIENT_ALL = 0xFFFF +}; + +enum +{ + ASSIGN_OPEN, + ASSIGN_CLOSED, + ASSIGN_AI, + ASSIGN_SESSION +}; + +#endif // NETMESSAGES_H + +#ifdef CREATING_NMT + +#define ALLNETMSGS_DONT_CREATE_NMTS +#define START_NMT_CLASS_(_nm, _message) START_NMT_CLASS(C##_nm##Message, _message) +#define DERIVE_NMT_CLASS_(_base, _nm, _message) START_NMT_CLASS_DERIVED(C ## _base ## Message, C ## _nm ## Message, _message) + +START_NMTS() + +START_NMT_CLASS_(SrvHandshake, NMT_SERVER_HANDSHAKE) + NMT_FIELD_INT(m_Magic, u32, 4) + NMT_FIELD_INT(m_ProtocolVersion, u32, 4) + NMT_FIELD_INT(m_SoftwareVersion, u32, 4) +END_NMT_CLASS() + +START_NMT_CLASS_(CliHandshake,NMT_CLIENT_HANDSHAKE) + NMT_FIELD_INT(m_MagicResponse, u32, 4) + NMT_FIELD_INT(m_ProtocolVersion, u32, 4) + NMT_FIELD_INT(m_SoftwareVersion, u32, 4) +END_NMT_CLASS() + +START_NMT_CLASS_(SrvHandshakeResponse, NMT_SERVER_HANDSHAKE_RESPONSE) + NMT_FIELD_INT(m_UseProtocolVersion, u32, 4) + NMT_FIELD_INT(m_Flags, u32, 4) + NMT_FIELD(CStrW, m_Message) +END_NMT_CLASS() + +START_NMT_CLASS_(Authenticate, NMT_AUTHENTICATE) + NMT_FIELD(CStrW, m_Name) + //NMT_FIELD(CPasswordHash, m_Password) + NMT_FIELD(CStrW, m_Password) +END_NMT_CLASS() + +START_NMT_CLASS_(AuthenticateResult, NMT_AUTHENTICATE_RESULT) + NMT_FIELD_INT(m_Code, u32, 4) + NMT_FIELD_INT(m_SessionID, u32, 2) + NMT_FIELD(CStrW, m_Message) +END_NMT_CLASS() + +START_NMT_CLASS_(Chat, NMT_CHAT) + NMT_FIELD(CStrW, m_Sender) + NMT_FIELD_INT(m_Recipient, u32, 2) + NMT_FIELD(CStrW, m_Message) +END_NMT_CLASS() + +START_NMT_CLASS_(PlayerJoin, NMT_PLAYER_JOIN) + NMT_START_ARRAY(m_Clients) + NMT_FIELD_INT(m_SessionID, u32, 2) + NMT_FIELD(CStr, m_Name) + NMT_END_ARRAY() +END_NMT_CLASS() + +START_NMT_CLASS_(PlayerLeave, NMT_PLAYER_LEAVE) + NMT_FIELD_INT(m_SessionID, u32, 2) +END_NMT_CLASS() + +START_NMT_CLASS_(GameSetup, NMT_GAME_SETUP) + NMT_START_ARRAY(m_Values) + NMT_FIELD(CStrW, m_Name) + NMT_FIELD(CStrW, m_Value) + NMT_END_ARRAY() +END_NMT_CLASS() + +START_NMT_CLASS_(AssignPlayerSlot, NMT_ASSIGN_PLAYER_SLOT) + NMT_FIELD_INT(m_SlotID, u32, 2) + NMT_FIELD_INT(m_Assignment, u32, 1) + NMT_FIELD_INT(m_SessionID, u32, 2) // Only applicable for PS_ASSIGN_SESSION +END_NMT_CLASS() + +START_NMT_CLASS_(PlayerConfig, NMT_PLAYER_CONFIG) + NMT_FIELD_INT(m_PlayerID, u32, 2) + NMT_START_ARRAY(m_Values) + NMT_FIELD(CStrW, m_Name) + NMT_FIELD(CStrW, m_Value) + NMT_END_ARRAY() +END_NMT_CLASS() + +START_NMT_CLASS_(GameStart, NMT_GAME_START) +END_NMT_CLASS() + +START_NMT_CLASS_(EndCommandBatch, NMT_END_COMMAND_BATCH) + NMT_FIELD_INT(m_TurnLength, u32, 2) +END_NMT_CLASS() + +START_NMT_CLASS_(Command, NMT_INVALID) + NMT_FIELD(CEntityList, m_Entities) + NMT_FIELD_INT(m_IsQueued, u32, 1) +END_NMT_CLASS() + +DERIVE_NMT_CLASS_(Command, Goto, NMT_GOTO) + NMT_FIELD_INT(m_TargetX, u32, 2) + NMT_FIELD_INT(m_TargetY, u32, 2) +END_NMT_CLASS() + +DERIVE_NMT_CLASS_(Command, Run, NMT_RUN) + NMT_FIELD_INT(m_TargetX, u32, 2) + NMT_FIELD_INT(m_TargetY, u32, 2) +END_NMT_CLASS() + +DERIVE_NMT_CLASS_(Command, Patrol, NMT_PATROL) + NMT_FIELD_INT(m_TargetX, u32, 2) + NMT_FIELD_INT(m_TargetY, u32, 2) +END_NMT_CLASS() + +DERIVE_NMT_CLASS_(Command, AddWaypoint, NMT_ADD_WAYPOINT) + NMT_FIELD_INT(m_TargetX, u32, 2) + NMT_FIELD_INT(m_TargetY, u32, 2) +END_NMT_CLASS() + +DERIVE_NMT_CLASS_(Command, Generic, NMT_GENERIC) + NMT_FIELD(HEntity, m_Target) + NMT_FIELD_INT(m_Action, u32, 4) +END_NMT_CLASS() + +DERIVE_NMT_CLASS_(Command, Produce, NMT_PRODUCE) + NMT_FIELD_INT(m_Type, u32, 4) + NMT_FIELD(CStrW, m_Name) +END_NMT_CLASS() + +DERIVE_NMT_CLASS_(Command, PlaceObject, NMT_PLACE_OBJECT) + NMT_FIELD(CStrW, m_Template) + NMT_FIELD_INT(m_X, u32, 4) + NMT_FIELD_INT(m_Y, u32, 4) + NMT_FIELD_INT(m_Z, u32, 4) + NMT_FIELD_INT(m_Angle, u32, 4) // Orientation angle +END_NMT_CLASS() + +DERIVE_NMT_CLASS_(Command, NotifyRequest, NMT_NOTIFY_REQUEST) + NMT_FIELD(HEntity, m_Target) + NMT_FIELD_INT(m_Action, u32, 4) +END_NMT_CLASS() + +DERIVE_NMT_CLASS_(Command, FormationGoto, NMT_FORMATION_GOTO) + NMT_FIELD_INT(m_TargetX, u32, 2) + NMT_FIELD_INT(m_TargetY, u32, 2) +END_NMT_CLASS() + +DERIVE_NMT_CLASS_(Command, FormationGeneric, NMT_FORMATION_GENERIC) + NMT_FIELD(HEntity, m_Target) + NMT_FIELD_INT(m_Action, u32, 4) +END_NMT_CLASS() + +END_NMTS() + +#else +#ifndef ALLNETMSGS_DONT_CREATE_NMTS + +# ifdef ALLNETMSGS_IMPLEMENT +# define NMT_CREATOR_IMPLEMENT +# endif + +# define NMT_CREATE_HEADER_NAME "NetMessages.h" +# include "NMTCreator.h" + +#endif // #ifndef ALLNETMSGS_DONT_CREATE_NMTS +#endif // #ifdef CREATING_NMT diff --git a/source/network/NetServer.cpp b/source/network/NetServer.cpp new file mode 100644 index 0000000000..e493ec0528 --- /dev/null +++ b/source/network/NetServer.cpp @@ -0,0 +1,1039 @@ +/** + *----------------------------------------------------------------------------- + * FILE : NetServer.cpp + * PROJECT : 0 A.D. + * DESCRIPTION : Network server class implementation + *----------------------------------------------------------------------------- + */ + +// INCLUDES +#include "precompiled.h" +#include "ps/CLogger.h" +#include "ps/CConsole.h" +#include "simulation/Simulation.h" +#include "NetJsEvents.h" +#include "NetSession.h" +#include "NetServer.h" + +// DECLARATIONS + +#pragma warning( disable : 4100 ) + +CNetServer* g_NetServer = NULL; + +//----------------------------------------------------------------------------- +// Name: CNetServer() +// Desc: Constructor +//----------------------------------------------------------------------------- +CNetServer::CNetServer( CGame *pGame, CGameAttributes *pGameAttributes ) +: m_JsSessions( &m_IDSessions ) +{ + m_Game = pGame; + m_GameAttributes = pGameAttributes; + m_MaxObservers = MAX_OBSERVERS; + //m_LastSessionID = 1; + m_Port = DEFAULT_HOST_PORT; + m_Name = DEFAULT_SERVER_NAME; + m_PlayerName = DEFAULT_PLAYER_NAME; + m_WelcomeMessage = DEFAULT_WELCOME_MESSAGE; + + // Set callbacks + m_GameAttributes->SetUpdateCallback( AttributeUpdate, this ); + m_GameAttributes->SetPlayerUpdateCallback( PlayerAttributeUpdate, this ); + m_GameAttributes->SetPlayerSlotAssignmentCallback( PlayerSlotAssignment, this ); + + // Set turn manager + CSimulation* pSimulation = m_Game->GetSimulation(); + if ( pSimulation ) pSimulation->SetTurnManager( this ); + + // Set an incredibly long turn length for debugging + // (e.g. less command batch spam that way) + for ( uint i = 0; i < 3; i++ ) + { + CTurnManager::SetTurnLength( i, 150 ); + } + + g_ScriptingHost.SetGlobal( "g_NetServer", OBJECT_TO_JSVAL( GetScript() ) ); +} + +//----------------------------------------------------------------------------- +// Name: ~CNetServer() +// Desc: Destructor +//----------------------------------------------------------------------------- +CNetServer::~CNetServer() +{ + m_IDSessions.clear(); + + g_ScriptingHost.SetGlobal( "g_NetServer", JSVAL_NULL ); +} + +//----------------------------------------------------------------------------- +// Name: ScriptingInit() +// Desc: +//----------------------------------------------------------------------------- +void CNetServer::ScriptingInit( void ) +{ + CJSMap< IDSessionMap >::ScriptingInit( "NetServer_SessionMap" ); + + AddMethod< bool, &CNetServer::Start >( "open", 0 ); + + AddProperty( L"sessions", &CNetServer::m_JsSessions ); + AddProperty( L"serverPlayerName", &CNetServer::m_PlayerName ); + AddProperty( L"serverName", &CNetServer::m_Name ); + AddProperty( L"welcomeMessage", &CNetServer::m_WelcomeMessage ); + AddProperty( L"port", &CNetServer::m_Port ); + AddProperty( L"onChat", &CNetServer::m_OnChat ); + AddProperty( L"onClientConnect", &CNetServer::m_ScriptConnect ); + AddProperty( L"onClientDisconnect", &CNetServer::m_OnClientDisconnect ); + + CJSObject< CNetServer >::ScriptingInit( "NetServer" ); +} + +//----------------------------------------------------------------------------- +// Name: Start() +// Desc: Initialize the server +//----------------------------------------------------------------------------- +bool CNetServer::Start( JSContext* pContext, uintN argc, jsval* argv ) +{ + // Setup initial state + m_State = SERVER_STATE_PREGAME; + + // Create new session + if ( !Create( m_Port, MAX_CLIENTS ) ) + { + LOG( CLogger::Error, LOG_CAT_NET, "CNetServer::Run(): Initialize failed" ); + + return false; + } + + return true; +} + +//----------------------------------------------------------------------------- +// Name: SetupSession() +// Desc: Setup new session +//----------------------------------------------------------------------------- +bool CNetServer::SetupSession( CNetSession* pSession ) +{ + // Validate parameters + if ( !pSession ) return false; + + FsmActionCtx* pContext = new FsmActionCtx; + if ( !pContext ) return false; + + pContext->pHost = this; + pContext->pSession = pSession; + + // Setup transitions for session + pSession->AddTransition( NSS_HANDSHAKE, ( uint )NMT_ERROR, NSS_HANDSHAKE, &OnError, pContext ); + pSession->AddTransition( NSS_HANDSHAKE, ( uint )NMT_CLIENT_HANDSHAKE, NSS_AUTHENTICATE, &OnHandshake, pContext ); + pSession->AddTransition( NSS_AUTHENTICATE, ( uint )NMT_ERROR, NSS_AUTHENTICATE, &OnError, pContext ); + pSession->AddTransition( NSS_AUTHENTICATE, ( uint )NMT_AUTHENTICATE, NSS_PREGAME, &OnAuthenticate, pContext ); + pSession->AddTransition( NSS_AUTHENTICATE, ( uint )NMT_APP_PREGAME, NSS_PREGAME, &OnPreGame, pContext ); + pSession->AddTransition( NSS_AUTHENTICATE, ( uint )NMT_APP_OBSERVER, NSS_INGAME, &OnChat, pContext ); + pSession->AddTransition( NSS_PREGAME, ( uint )NMT_CHAT, NSS_INGAME, &OnInGame, pContext ); + pSession->AddTransition( NSS_INGAME, ( uint )NMT_ERROR, NSS_INGAME, &OnError, pContext ); + pSession->AddTransition( NSS_INGAME, ( uint )NMT_CHAT, NSS_INGAME, &OnChat, pContext ); + pSession->AddTransition( NSS_INGAME, ( uint )NMT_GOTO, NSS_INGAME, &OnInGame, pContext ); + pSession->AddTransition( NSS_INGAME, ( uint )NMT_PATROL, NSS_INGAME, &OnInGame, pContext ); + pSession->AddTransition( NSS_INGAME, ( uint )NMT_ADD_WAYPOINT, NSS_INGAME, &OnInGame, pContext ); + pSession->AddTransition( NSS_INGAME, ( uint )NMT_GENERIC, NSS_INGAME, &OnInGame, pContext ); + pSession->AddTransition( NSS_INGAME, ( uint )NMT_PRODUCE, NSS_INGAME, &OnInGame, pContext ); + pSession->AddTransition( NSS_INGAME, ( uint )NMT_PLACE_OBJECT, NSS_INGAME, &OnInGame, pContext ); + pSession->AddTransition( NSS_INGAME, ( uint )NMT_RUN, NSS_INGAME, &OnInGame, pContext ); + pSession->AddTransition( NSS_INGAME, ( uint )NMT_NOTIFY_REQUEST, NSS_INGAME, &OnInGame, pContext ); + pSession->AddTransition( NSS_INGAME, ( uint )NMT_FORMATION_GOTO, NSS_INGAME, &OnInGame, pContext ); + pSession->AddTransition( NSS_INGAME, ( uint )NMT_FORMATION_GENERIC, NSS_INGAME, &OnInGame, pContext ); + pSession->AddTransition( NSS_INGAME, ( uint )NMT_END_COMMAND_BATCH, NSS_INGAME, &OnInGame, pContext ); + + // Set first state + pSession->SetFirstState( NSS_HANDSHAKE ); + + return true; +} + +//----------------------------------------------------------------------------- +// Name: HandleConnect() +// Desc: Called when a new player joins the current game +//----------------------------------------------------------------------------- +bool CNetServer::HandleConnect( CNetSession* pSession ) +{ + // Validate parameters + if ( !pSession ) return false; + + // Player joined the game, start authentication + CSrvHandshakeMessage handshake; + handshake.m_Magic = PS_PROTOCOL_MAGIC; + handshake.m_ProtocolVersion = PS_PROTOCOL_VERSION; + handshake.m_SoftwareVersion = PS_PROTOCOL_VERSION; + CNetHost::SendMessage( pSession, &handshake ); + + // Store new session into the map + m_IDSessions[ pSession->GetID() ] = pSession; + + // Script object defined? + if ( m_ScriptConnect.Defined() ) + { + // Dispatch a client connection event + CClientConnectEvent event( pSession ); + m_ScriptConnect.DispatchEvent( GetScript(), &event ); + } + + return true; +} + +//----------------------------------------------------------------------------- +// Name: HandleDisconnect() +// Desc: Called when a client has disconnected +//----------------------------------------------------------------------------- +bool CNetServer::HandleDisconnect( CNetSession* pSession ) +{ + // Validate parameters + if ( !pSession ) return false; + + OnPlayerLeave( pSession ); + + // Free session slot so it can be recycled + m_IDSessions[ pSession->GetID() ] = NULL; + + return true; +} + +//----------------------------------------------------------------------------- +// Name: SetupPlayer() +// Desc: Sends the necessary messages for the player setup +//----------------------------------------------------------------------------- +void CNetServer::SetupPlayer( CNetSession* pSession ) +{ + CGameSetupMessage gameSetup; + CPlayerConfigMessage playerConfig; + CAssignPlayerSlotMessage assignSlot; + CPlayerJoinMessage playerJoin; + + // Validate parameters + if ( !pSession ) return; + + assert( m_GameAttributes ); + + // Send a new config message to the connected client + BuildGameSetupMessage( &gameSetup ); + CNetHost::SendMessage( pSession, &gameSetup ); + + // Add information for already connected clients and the server + playerJoin.m_Clients.resize( GetSessionCount() ); + playerJoin.m_Clients[ 0 ].m_SessionID = SERVER_SESSIONID; + playerJoin.m_Clients[ 0 ].m_Name = GetPlayerName(); + + for ( uint i = 0; i < GetSessionCount(); i++ ) + { + CNetSession* pCurrSession = GetSession( i ); + + // Skip the player being setup + if ( !pCurrSession || pCurrSession == pSession ) continue; + + playerJoin.m_Clients[ i + 1 ].m_SessionID = pCurrSession->GetID(); + playerJoin.m_Clients[ i + 1 ].m_Name = pCurrSession->GetName(); + } + + CNetHost::SendMessage( pSession, &playerJoin ); + + // TODO: Handle observers + + // Send a message informing about the newly connected player + playerJoin.m_Clients.resize( 1 ); + playerJoin.m_Clients[ 0 ].m_SessionID = pSession->GetID(); + playerJoin.m_Clients[ 0 ].m_Name = pSession->GetName(); + + for ( uint i = 0; i < GetSessionCount(); i++ ) + { + CNetSession* pCurrSession = GetSession( i ); + + // Skip the player being setup + if ( !pCurrSession || pCurrSession == pSession ) continue; + + CNetHost::SendMessage( pCurrSession, &playerJoin ); + } + //Broadcast( &playerJoin ); + + // Sync player slot assignments and player attributes + for ( uint i = 0; i < m_GameAttributes->GetSlotCount(); i++ ) + { + CPlayerSlot* pCurrSlot = m_GameAttributes->GetSlot( i ); + if ( !pCurrSlot ) continue; + + assignSlot.m_SlotID = pCurrSlot->GetSlotID(); + assignSlot.m_SessionID = pCurrSlot->GetSessionID(); + switch ( pCurrSlot->GetAssignment() ) + { + case SLOT_CLOSED: + assignSlot.m_Assignment = ASSIGN_CLOSED; + break; + + case SLOT_OPEN: + assignSlot.m_Assignment = ASSIGN_OPEN; + break; + + case SLOT_SESSION: + assignSlot.m_Assignment = ASSIGN_SESSION; + break; + + case SLOT_AI: + break; + } + + CNetHost::SendMessage( pSession, &assignSlot ); + + if ( pCurrSlot->GetAssignment() == SLOT_SESSION ) + { + // Setup player + BuildPlayerConfigMessage( &playerConfig, pCurrSlot->GetPlayer() ); + CNetHost::SendMessage( pSession, &playerConfig ); + } + } +} + +//----------------------------------------------------------------------------- +// Name: OnClientDisconnect() +// Desc: Called when a player quits the game +//----------------------------------------------------------------------------- +void CNetServer::OnPlayerLeave( CNetSession* pSession ) +{ + // Validate parameters + if ( !pSession ) return; + + CPlayer* pPlayer = NULL; + CPlayerSlot* pPlayerSlot = NULL; + CPlayerLeaveMessage playerLeave; + + // Validate parameters + if ( !pSession ) return; + + pPlayer = pSession->GetPlayer(); + pPlayerSlot = pSession->GetPlayerSlot(); + + switch ( m_State ) + { + case SERVER_STATE_PREGAME: + + // Delete player's slot and sync client disconnection + if ( pPlayerSlot ) pPlayerSlot->AssignClosed(); + break; + + case SERVER_STATE_INGAME: + + // Revert player entities to Gaia control and + // wait for client reconnection + // TODO Reassign entities to Gaia control + // TODO Set everything up for re-connect and resume + if ( pPlayerSlot ) + { + SetClientPipe( pPlayerSlot->GetSlotID(), NULL ); + pPlayerSlot->AssignClosed(); + } + break; + + case SERVER_STATE_POSTGAME: + + // Synchronize disconnection + break; + } + + // Inform other clients about client disconnection + playerLeave.m_SessionID = pSession->GetID(); + + Broadcast( &playerLeave ); + + // Script object defined? + if ( m_ScriptDisconnect.Defined() ) + { + // Dispatch a client disconnect event + CClientDisconnectEvent event( pSession->GetID(), pSession->GetName() ); + m_ScriptDisconnect.DispatchEvent( GetScript(), &event ); + } +} + +//----------------------------------------------------------------------------- +// Name: OnError() +// Desc: +//----------------------------------------------------------------------------- +bool CNetServer::OnError( void* pContext, CFsmEvent* pEvent ) +{ + // Validate parameters + if ( !pContext || !pEvent ) return false; + + // Error event? + if ( pEvent->GetType() != NMT_ERROR ) return true; + + CNetServer* pServer = ( CNetServer* )( ( FsmActionCtx* )pContext )->pHost; + CNetSession* pSession = ( ( FsmActionCtx* )pContext )->pSession; + + CErrorMessage* pMessage = ( CErrorMessage* )pEvent->GetParamRef(); + if ( pMessage ) + { + if ( pMessage->m_State == SS_UNCONNECTED ) + { + //if ( pSession->GetID() != -1) + // pServer->RemoveSession( pSession ); + + //delete pSession; + } + else + { + // Weird stuff... + LOG( CLogger::Warning, LOG_CAT_NET, "NMT_ERROR: %s", pMessage->ToString().c_str() ); + } + } + + return true; +} + +//----------------------------------------------------------------------------- +// Name: OnHandshake() +// Desc: +//----------------------------------------------------------------------------- +bool CNetServer::OnHandshake( void* pContext, CFsmEvent* pEvent ) +{ + // Validate parameters + if ( !pEvent || !pContext ) return false; + + // Client handshake event? + if ( pEvent->GetType() != NMT_CLIENT_HANDSHAKE ) return true; + + CNetServer* pServer = ( CNetServer* )( ( FsmActionCtx* )pContext )->pHost; + CNetSession* pSession = ( ( FsmActionCtx* )pContext )->pSession; + + assert( pServer ); + assert( pSession ); + + CCliHandshakeMessage* pMessage = ( CCliHandshakeMessage* )pEvent->GetParamRef(); + if ( pMessage ) + { + if ( pMessage->m_ProtocolVersion != PS_PROTOCOL_VERSION ) + { + CCloseRequestMessage closeRequest; + ( ( CNetHost* )pServer )->SendMessage( pSession, &closeRequest ); + + CErrorMessage error( PS_OK, SS_UNCONNECTED ); + pSession->Update( ( uint )NMT_ERROR, &error ); + } + else + { + CSrvHandshakeResponseMessage handshakeResponse; + handshakeResponse.m_UseProtocolVersion = PS_PROTOCOL_VERSION; + handshakeResponse.m_Message = pServer->m_WelcomeMessage; + handshakeResponse.m_Flags = 0; + ( ( CNetHost* )pServer )->SendMessage( pSession, &handshakeResponse ); + } + } + + return true; +} + +//----------------------------------------------------------------------------- +// Name: OnAuthenticate() +// Desc: +//----------------------------------------------------------------------------- +bool CNetServer::OnAuthenticate( void* pContext, CFsmEvent* pEvent ) +{ + // Validate parameters + if ( !pContext || !pEvent ) return false; + + // Authenticate event? + if ( pEvent->GetType() != NMT_AUTHENTICATE ) return true; + + CNetServer* pServer = ( CNetServer* )( ( FsmActionCtx* )pContext )->pHost; + CNetSession* pSession = ( ( FsmActionCtx* )pContext )->pSession; + + CAuthenticateMessage* pMessage = ( CAuthenticateMessage* )pEvent->GetParamRef(); + if ( pMessage ) + { + if ( pMessage->m_Password == pServer->m_PlayerPassword ) + { + LOG( CLogger::Normal, LOG_CAT_NET, "Player authentication successful"); + + pSession->SetName( pMessage->m_Name ); + pSession->SetID( pServer->GetFreeSessionID() ); + + CAuthenticateResultMessage authenticateResult; + authenticateResult.m_Code = ARC_OK; + authenticateResult.m_SessionID = pSession->GetID(); + authenticateResult.m_Message = L"Logged in"; + ( ( CNetHost* )pServer )->SendMessage( pSession, &authenticateResult ); + + //pServer->AddSession( pSession ); + + //if ( pServer->GetServerState() == NSS_PreGame ) + //{ + pServer->SetupPlayer( pSession ); + //} + //else + //{ + // Chatter / observer + // SetupObserver( pSession ); + //} + } + else + { + LOG( CLogger::Warning, LOG_CAT_NET, "Player authentication failed" ); + + CAuthenticateResultMessage authenticateResult; + authenticateResult.m_Code = ARC_PASSWORD_INVALID; + authenticateResult.m_SessionID = 0; + authenticateResult.m_Message = L"Invalid Password"; + ( ( CNetHost* )pServer )->SendMessage( pSession, &authenticateResult ); + } + } + + return true; +} + +//----------------------------------------------------------------------------- +// Name: OnPreGame() +// Desc: +//----------------------------------------------------------------------------- +bool CNetServer::OnPreGame( void* pContext, CFsmEvent* pEvent ) +{ + // Validate parameters + if ( !pContext || !pEvent ) return false; + + return CNetServer::OnChat( pContext, pEvent ); +} + +//----------------------------------------------------------------------------- +// Name: OnInGame() +// Desc: +//----------------------------------------------------------------------------- +bool CNetServer::OnInGame( void* pContext, CFsmEvent* pEvent ) +{ + // Validate parameters + if ( !pContext || !pEvent ) return false; + + CNetServer* pServer = ( CNetServer* )( ( FsmActionCtx* )pContext )->pHost; + CNetSession* pSession = ( ( FsmActionCtx* )pContext )->pSession; + + CNetMessage* pMessage = ( CNetMessage* )pEvent->GetParamRef(); + if ( pMessage ) + { + if ( pMessage->GetType() >= NMT_COMMAND_FIRST && pMessage->GetType() < NMT_COMMAND_LAST ) + { + //pSession->m_pPlayer->ValidateCommand(pMsg); + pServer->QueueIncomingCommand( pMessage ); + + return true; + } + + if ( pMessage->GetType() == NMT_END_COMMAND_BATCH ) + { + // TODO Update client timing information and recalculate turn length + pSession->SetReadyForTurn( true ); + } + } + + return true; +} + +//----------------------------------------------------------------------------- +// Name: OnChat() +// Desc: +//----------------------------------------------------------------------------- +bool CNetServer::OnChat( void* pContext, CFsmEvent* pEvent ) +{ + // Validate parameters + if ( !pContext || !pEvent ) return false; + + // Chatting? + if ( pEvent->GetType() != NMT_CHAT ) return true; + + CNetServer* pServer = ( CNetServer* )( ( FsmActionCtx* )pContext )->pHost; + CNetSession* pSession = ( ( FsmActionCtx* )pContext )->pSession; + + assert( pSession ); + assert( pServer ); + + CChatMessage* pMessage = ( CChatMessage* )pEvent->GetParamRef(); + if ( pMessage ) + { + pMessage->m_Sender = pSession->GetName(); + + g_Console->ReceivedChatMessage( pSession->GetName(), pMessage->m_Message.c_str() ); + + if ( pServer->m_OnChat.Defined() ) + { + CChatEvent chatEvent( pMessage->m_Sender, pMessage->m_Message ); + pServer->m_OnChat.DispatchEvent( pServer->GetScript(), &chatEvent ); + } + + ( ( CNetHost*)pServer )->Broadcast( pMessage ); + } + + return true; +} + +//----------------------------------------------------------------------------- +// Name: GetSessionByID() +// Desc: Retrieve the session by its ID +//----------------------------------------------------------------------------- +CNetSession* CNetServer::GetSessionByID( uint sessionID ) +{ + // Lookup session + IDSessionMap::const_iterator it = m_IDSessions.find( sessionID ); + if ( it == m_IDSessions.end() ) return NULL; + + return it->second; +} + +//----------------------------------------------------------------------------- +// Name: AddSession() +// Desc: Adds a new session to the list of managed sessions +//----------------------------------------------------------------------------- +/*void CNetServer::AddSession( CNetSession* pSession ) +{ + // Validate parameter + if ( !pSession ) return; + + // Setup new session + //SetupNewSession(); + + // Broadcase a new message informing about the newly connected client + CPlayerJoinMessage playerJoin; + playerJoin.m_Clients.resize( 1 ); + playerJoin.m_Clients[ 0 ].m_SessionID = pSession->GetID(); + playerJoin.m_Clients[ 0 ].m_Name = pSession->GetName(); + + Broadcast( playerJoin ); + + // Store new session + m_IDSessions[ pSession->GetID() ] = pSession; +}*/ + +//----------------------------------------------------------------------------- +// Name: RemoveSession() +// Desc: Removes the specified session from the list of sessions +//----------------------------------------------------------------------------- +/*CNetSession* CNetServer::RemoveSession( CNetSession* pSession ) +{ + CPlayer* pPlayer = NULL; + CPlayerSlot* pPlayerSlot = NULL; + CPlayerLeaveMessage playerLeave; + uint sessionID; + + // Validate parameters + if ( !pSession ) return; + + pPlayer = pSession->GetPlayer(); + pPlayerSlot = pSession->GetPlayerSlot(); + sessionID = pSession->GetID(); + + switch ( m_State ) + { + case SERVER_STATE_PREGAME: + + // Delete player's slot and sync client disconnection + if ( pPlayerSlot ) pPlayerSlot->AssignClosed(); + + break; + + case SERVER_STATE_INGAME: + + // Revert player entities to Gaia control and + // wait for client reconnection + // TODO Reassign entities to Gaia control + // TODO Set everything up for re-connect and resume + if ( pPlayerSlot ) + { + SetClientPipe( pPlayerSlot->GetSlotID(), NULL ); + pPlayerSlot->AssignClosed(); + } + + break; + + case SERVER_STATE_POSTGAME: + + // Synchronize disconnection + + break; + } + + // Inform other clients about client disconnection + playerLeave.m_SessionID = sessionID; + + Broadcast( playerLeave ); + + // Free session slot from the list for later reuse + m_IDSessions[ sessionID ] = NULL; + + // TODO Handle observers +}*/ + +//----------------------------------------------------------------------------- +// Name: RemoveAllSessions() +// Desc: Removes all sessions from the list +//----------------------------------------------------------------------------- +/*void CNetServer::RemoveAllSessions( void ) +{ + SessionList::iterator it = m_Sessions.begin(); + for ( ; it != m_Sessions.end(); it++ ) + { + CNetSession* pCurrSession = it->second; + if ( !pCurrSession ) continue; + + RemoveSession( pCurrSession ); + } + + m_Sessions.clear(); +}*/ + +//----------------------------------------------------------------------------- +// Name: SetPlayerPassword() +// Desc: Sets player new password +//----------------------------------------------------------------------------- +void CNetServer::SetPlayerPassword( const CStr& password ) +{ + m_PlayerPassword = password; +} + +//----------------------------------------------------------------------------- +// Name: StartGame() +// Desc: +//----------------------------------------------------------------------------- +int CNetServer::StartGame( void ) +{ + uint i; + + assert( m_Game ); + assert( m_GameAttributes ); + + if ( m_Game->StartGame( m_GameAttributes ) != PSRETURN_OK ) return -1; + + CTurnManager::Initialize( m_GameAttributes->GetSlotCount() ); + + for ( i = 0; i < m_GameAttributes->GetSlotCount(); i++ ) + { + CPlayerSlot* pCurrSlot = m_GameAttributes->GetSlot( i ); + if ( !pCurrSlot ) continue; + + if ( pCurrSlot->GetAssignment() == SLOT_SESSION ) + CTurnManager::SetClientPipe( i, pCurrSlot->GetSession() ); + } + + m_State = SERVER_STATE_INGAME; + + for ( i = 0; i < GetSessionCount(); i++ ) + { + CNetSession* pCurrSession = GetSession( i ); + if ( !pCurrSession ) continue; + + pCurrSession->StartGame(); + } + + CGameStartMessage gameStart; + Broadcast( &gameStart ); + + // This is the signal for everyone to start their simulations. + SendBatch( 1 ); + + return 0; +} + +//----------------------------------------------------------------------------- +// Name: BuildGameSetupMessage() +// Desc: Loads the game properties into the specified message +//----------------------------------------------------------------------------- +void CNetServer::BuildGameSetupMessage( CGameSetupMessage* pMessage ) +{ + // Validate parameters + if ( !pMessage ) return; + + assert( m_GameAttributes ); + + // Iterate through game properties and load them into message + m_GameAttributes->IterateSynchedProperties( GameSetupMessageCallback, pMessage ); +} + +//----------------------------------------------------------------------------- +// Name: GameSetupMessageCallback() +// Desc: Callback called for each game attribute +//----------------------------------------------------------------------------- +void CNetServer::GameSetupMessageCallback( + const CStrW& name, + ISynchedJSProperty* pProperty, + void *pData ) +{ + // Validate parameters + if ( !pProperty || !pData ) return; + + CGameSetupMessage* pMessage = ( CGameSetupMessage* )pData; + + // Add new property to list + size_t valueCount = pMessage->m_Values.size(); + pMessage->m_Values.resize( valueCount + 1 ); + + // Store property into list + pMessage->m_Values[ valueCount ].m_Name = name; + pMessage->m_Values[ valueCount ].m_Value = pProperty->ToString(); +} + +//----------------------------------------------------------------------------- +// Name: BuildPlayerConfigMessage() +// Desc: Loads the player properties into the specified message +//----------------------------------------------------------------------------- +void CNetServer::BuildPlayerConfigMessage( + CPlayerConfigMessage* pMessage, + CPlayer* pPlayer ) +{ + // Validare parameters + if ( !pMessage || !pPlayer ) return; + + pMessage->m_PlayerID = pPlayer->GetPlayerID(); + + // Iterate through player properties and load them into message + pPlayer->IterateSynchedProperties( PlayerConfigMessageCallback, pMessage ); +} + +//----------------------------------------------------------------------------- +// Name: PlayerConfigMessageCallback() +// Desc: Callback called for each property of the player +//----------------------------------------------------------------------------- +void CNetServer::PlayerConfigMessageCallback( + const CStrW& name, + ISynchedJSProperty* pProperty, + void* pData ) +{ + // Validate parameters + if ( !pProperty || !pData ) return; + + CPlayerConfigMessage* pMessage = ( CPlayerConfigMessage* )pData; + + // Add new property to list + size_t valueCount = pMessage->m_Values.size(); + pMessage->m_Values.resize( valueCount + 1 ); + + // Store property into the list + pMessage->m_Values[ valueCount ].m_Name = name; + pMessage->m_Values[ valueCount ].m_Value = pProperty->ToString(); +} + +//----------------------------------------------------------------------------- +// Name: GetFreeSessionID() +// Desc: +//----------------------------------------------------------------------------- +uint CNetServer::GetFreeSessionID( void ) const +{ + uint sessionID = CLIENT_MIN_SESSIONID; + + // Loop through the list of sessions and return the first + // ID for which the associated session is NULL. If no such + // free slot is found, return a new session ID which is higher + // than the last session ID from the list. + IDSessionMap::const_iterator it = m_IDSessions.begin(); + for ( ; it != m_IDSessions.end(); it++ ) + { + CNetSession* pCurrSession = it->second; + if ( !pCurrSession ) return it->first; + + sessionID = pCurrSession->GetID() + 1; + } + + return sessionID; +} + +//----------------------------------------------------------------------------- +// Name: AttributeUpdate() +// Desc: +//----------------------------------------------------------------------------- +void CNetServer::AttributeUpdate( + const CStrW& name, + const CStrW& newValue, + void* pData ) +{ + // Validate parameters + if ( !pData ) return; + + CNetServer* pServer = ( CNetServer* )pData; + + g_Console->InsertMessage( L"AttributeUpdate: %ls = \"%ls\"", name.c_str(), newValue.c_str() ); + + CGameSetupMessage gameSetup; + gameSetup.m_Values.resize( 1 ); + gameSetup.m_Values[ 0 ].m_Name = name; + gameSetup.m_Values[ 0 ].m_Value = newValue; + pServer->Broadcast( &gameSetup ); +} + +//----------------------------------------------------------------------------- +// Name: PlayerAttributeUpdate() +// Desc: +//----------------------------------------------------------------------------- +void CNetServer::PlayerAttributeUpdate( + const CStrW& name, + const CStrW& newValue, + CPlayer* pPlayer, + void* pData ) +{ + // Validate parameters + if ( !pData ) return; + + CNetServer* pServer = ( CNetServer* )pData; + + g_Console->InsertMessage( L"PlayerAttributeUpdate(%d): %ls = \"%ls\"", pPlayer->GetPlayerID(), name.c_str(), newValue.c_str() ); + + CPlayerConfigMessage* pNewMessage = new CPlayerConfigMessage; + if ( !pNewMessage ) return; + + pNewMessage->m_PlayerID = pPlayer->GetPlayerID(); + pNewMessage->m_Values.resize( 1 ); + pNewMessage->m_Values[ 0 ].m_Name = name; + pNewMessage->m_Values[ 0 ].m_Value = newValue; + + pServer->Broadcast( pNewMessage ); +} + +//----------------------------------------------------------------------------- +// Name: PlayerSlotAssignment() +// Desc: +//----------------------------------------------------------------------------- +void CNetServer::PlayerSlotAssignment( + void* pData, + CPlayerSlot* pPlayerSlot ) +{ + // Validate parameters + if ( !pData || !pPlayerSlot ) return; + + CNetServer* pServer = ( CNetServer* )pData; + + if ( pPlayerSlot->GetAssignment() == SLOT_SESSION ) + pPlayerSlot->GetSession()->SetPlayerSlot( pPlayerSlot ); + + CAssignPlayerSlotMessage* pMessage = new CAssignPlayerSlotMessage; + if ( !pMessage ) return; + + pServer->BuildPlayerSlotAssignmentMessage( pMessage, pPlayerSlot ); + + g_Console->InsertMessage( L"Player Slot Assignment: %hs\n", pMessage->ToString().c_str() ); + + pServer->Broadcast( pMessage ); +} + +//----------------------------------------------------------------------------- +// Name: AllowObserver() +// Desc: +//----------------------------------------------------------------------------- +bool CNetServer::AllowObserver( CNetSession* UNUSED( pSession ) ) +{ + return m_Observers.size() < m_MaxObservers; +} + +//----------------------------------------------------------------------------- +// Name: NewTurnReady() +// Desc: +//----------------------------------------------------------------------------- +bool CNetServer::NewTurnReady() +{ +// return true; + + // Check whether all sessions are ready for the next turn + for ( uint i = 0; i < GetSessionCount(); i++ ) + { + CNetSession* pCurrSession = GetSession( i ); + if ( !pCurrSession ) continue; + + if ( !pCurrSession->IsReadyForTurn() ) + return false; + } + + return true; +} + +//----------------------------------------------------------------------------- +// Name: NewTurn() +// Desc: +//----------------------------------------------------------------------------- +void CNetServer::NewTurn() +{ +// return; + + CScopeLock lock(m_Mutex); + + // Reset session ready for next turn flag + for ( uint i = 0; i < GetSessionCount(); i++ ) + { + CNetSession* pCurrSession = GetSession( i ); + if ( !pCurrSession ) continue; + + pCurrSession->SetReadyForTurn( false ); + } + + RecordBatch( 2 ); + RotateBatches(); + ClearBatch( 2 ); + IterateBatch( 1, CSimulation::GetMessageMask, m_Game->GetSimulation() ); + SendBatch( 1 ); + //IterateBatch( 1, SendToObservers, this ); +} + +//----------------------------------------------------------------------------- +// Name: QueueLocalCommand() +// Desc: +//----------------------------------------------------------------------------- +void CNetServer::QueueLocalCommand( CNetMessage *pMessage ) +{ + QueueIncomingCommand( pMessage ); +} + +//----------------------------------------------------------------------------- +// Name: QueueIncomingCommand() +// Desc: +//----------------------------------------------------------------------------- +void CNetServer::QueueIncomingCommand( CNetMessage* pMessage ) +{ + // Validate parameters + if ( !pMessage ) return; + + //LOG( NORMAL, LOG_CAT_NET, "CNetServer::QueueIncomingCommand(): %s.", pMessage->ToString().c_str() ); + + QueueMessage( 2, pMessage ); +} + +//----------------------------------------------------------------------------- +// Name: OnChat() +// Desc: +//----------------------------------------------------------------------------- +void CNetServer::OnPlayerChat( const CStrW& from, const CStrW& message ) +{ + if ( m_OnChat.Defined() ) + { + CChatEvent event( from, message ); + m_OnChat.DispatchEvent( GetScript(), &event ); + } +} + +//----------------------------------------------------------------------------- +// Name: BuildPlayerSlotAssignementMessage() +// Desc: +//----------------------------------------------------------------------------- +void CNetServer::BuildPlayerSlotAssignmentMessage( + CAssignPlayerSlotMessage* pMessage, + CPlayerSlot* pPlayerSlot ) +{ + // Validate parameters + if ( !pMessage || !pPlayerSlot ) return; + + pMessage->m_SlotID = pPlayerSlot->GetSlotID(); + pMessage->m_SessionID = pPlayerSlot->GetSessionID(); + + switch ( pPlayerSlot->GetAssignment() ) + { + case SLOT_CLOSED: + pMessage->m_Assignment = ASSIGN_CLOSED; + break; + + case SLOT_OPEN: + pMessage->m_Assignment = ASSIGN_OPEN; + break; + + case SLOT_SESSION: + pMessage->m_Assignment = ASSIGN_SESSION; + break; + } +} + diff --git a/source/network/NetServer.h b/source/network/NetServer.h new file mode 100644 index 0000000000..607f5ef8a6 --- /dev/null +++ b/source/network/NetServer.h @@ -0,0 +1,343 @@ +/** + *----------------------------------------------------------------------------- + * FILE : NetServer.h + * PROJECT : 0 A.D. + * DESCRIPTION : Network server class interface file + *----------------------------------------------------------------------------- + */ + +#ifndef NETSERVER_H +#define NETSERVER_H + +// INCLUDES +#include "Network.h" +#include "NetSession.h" +#include "simulation/TurnManager.h" +#include "scripting/ScriptableObject.h" +#include "ps/GameAttributes.h" +#include "ps/scripting/JSMap.h" +#include "ps/Player.h" +#include "ps/Game.h" +#include "simulation/ScriptObject.h" + +#include +#include + +// DECLARATIONS +#define SERVER_SESSIONID 10 +#define CLIENT_MIN_SESSIONID 100 +#define MAX_CLIENTS 8 +#define MAX_OBSERVERS 5 +#define DEFAULT_SERVER_SESSION_ID 1 +#define DEFAULT_SERVER_NAME L"Noname Server" +#define DEFAULT_PLAYER_NAME L"Noname Player" +#define DEFAULT_WELCOME_MESSAGE L"Noname Server Welcome Message" +#define DEFAULT_HOST_PORT 0x5073 + +enum NetServerState +{ + // We haven't opened the port yet, we're just setting some stuff up. + // This is probably equivalent to the first "Start Network Game" screen + SERVER_STATE_PREBIND, + + // The server is open and accepting connections. This is the screen where + // rules are set up by the operator and where players join and select civs + // and stuff. + SERVER_STATE_PREGAME, + + // The one with all the killing ;-) + SERVER_STATE_INGAME, + + // The game is over and someone has won. Players might linger to chat or + // download the replay log. + SERVER_STATE_POSTGAME +}; + +enum +{ + NSS_HANDSHAKE = 1300, + NSS_AUTHENTICATE = 1400, + NSS_PREGAME = 1500, + NSS_INGAME = 1600 +}; + +enum +{ + NMT_APP_PLAYER_LEAVE = NMT_LAST + 100, + NMT_APP_PREGAME = NMT_LAST + 200, + NMT_APP_OBSERVER = NMT_LAST + 300 +}; + +typedef std::map< uint, CNetSession* > IDSessionMap; +typedef std::vector< CNetSession* > SessionList; + +/* + CLASS : CNetServer + DESCRIPTION : CNetServer implements a network server for the game. + It receives data and connection requests from clients. + Under the hood, it uses ENet library to manage connected + peers and bandwidth among these. + NOTES : +*/ + +class CNetServer : public CNetHost, + public CJSObject, + public CTurnManager +{ +public: + + CNetServer( CGame* pGame, CGameAttributes* pGameAttributes ); + virtual ~CNetServer( void ); + + bool Start ( JSContext *pContext, uintN argc, jsval *argv ); +// void Shutdown ( void ); + + /** + * Returns true indicating the host acts as a server + * + * @return Always true + */ + virtual bool IsServer( void ) const { return true; } + + /** + * Adds a new session to the list of sessions + * + * @param pSession New session to add + */ + void AddSession( CNetSession* pSession ); + + /** + * Removes the specified session from the list of sessions. If the session + * isn't found it returns NULL otherwise it returns the session object found. + * + * @param pSession Session to remove + * @return The session object if found, NULL otherwise + */ + CNetSession* RemoveSession( CNetSession* pSession ); + + /** + * Removes all the sessions managed by the network server + * + */ + //void RemoveAllSessions( void ); + + /** + * Returns the number of session the server manages + * + * @return The number of sessions + */ + //uint GetSessionCount( void ) const; + + /** + * Returns the session object for the specified ID + * + * @param sessionID The session ID + * @return A pointer to session for the specified ID or + * NULL if not found + */ + CNetSession* GetSessionByID( uint sessionID ); + + static void ScriptingInit( void ); + +protected: + + virtual bool SetupSession ( CNetSession* pSession ); + virtual bool HandleConnect ( CNetSession* pSession ); + virtual bool HandleDisconnect ( CNetSession *pSession ); + +private: + + // Not implemented + CNetServer( const CNetServer& ); + CNetServer& operator=( const CNetServer& ); + + //void ClientConnect ( ENetPeer* pPeer ); + //void ClientDisconnect ( ENetPeer* pPeer ); + //void ClientReceive ( ENetPeer* pPeer, ENetPacket* pPacket ); + + /** + * Returns the session associated with the specified ENet peer + * + * @param pPeer ENet peer + * @return The session object if found or NULL + */ + //CNetSession* GetSessionByPeer( const ENetPeer* pPeer ); + + /** + * Setup client game by sending the apropiate network messages. It also + * inform the client about the other connected clients as well as player + * slot assignment and attributes. + * + */ + //void SetupNewSession( CNetSession* pSession ); + + /** + * Loads the player properties into the specified message + * + * @param pMessage Message where to load player properties + * @param pPlayer Player for which we load the properties + */ + void BuildPlayerConfigMessage( + CPlayerConfigMessage* pMessage, + CPlayer* pPlayer ); + + /** + * Callback function used by the BuildPlayerSetupMessage to iterate over + * the player properties. It will be called for each property of the player + * + * @param name Property name + * @param pProperty Pointer to player property + * @param pData Context pointer passed on iteration startup + */ + static void PlayerConfigMessageCallback( + const CStrW& name, + ISynchedJSProperty* pProperty, + void* pData ); + + /** + * Loads game properties into the specified message + * + * @param pMessage Message where to load game properties + */ + void BuildGameSetupMessage( CGameSetupMessage* pMessage ); + + /** + * Loads player slot properties into the specified message + * + * @param pMessage Message where to load player properties + * @param pPlayerSlot Player slot properties + */ + void BuildPlayerSlotAssignmentMessage( + CAssignPlayerSlotMessage* pMessage, + CPlayerSlot* pPlayerSlot ); + + /** + * Callback function used by the BuildGameSetupMessage to iterate over the + * game properties. It will be called for each property of the game + * + * @param name Property name + * @param pProperty Pointer to game property + * @param pData Context pointer passed on iteration startup + */ + // IterateCB GameSetupMessageCallbak; + static void GameSetupMessageCallback( + const CStrW& name, + ISynchedJSProperty *pProperty, + void *pData ); + + /** + * Retrieves a free session ID from the recycled sessions list + * + * @return Free session ID + */ + uint GetFreeSessionID( void ) const; + + IDSessionMap m_IDSessions; // List of connected ID and session pairs + CScriptObject m_ScriptConnect; // Script client connect dispatch + CScriptObject m_ScriptDisconnect; // Script client disconnect dispatch + CPlayer* m_Player; // Server player + + +public: + + /** + * + * + * @param addr Address where to bind + * @return PS_OK if bind successfully, error code otherwise + */ + //PS_RESULT Bind( const CSocketAddress& addr ); + void SetPlayerPassword ( const CStr& password ); + CStrW GetPlayerName ( void ) const { return m_PlayerName; } + NetServerState GetState ( void ) const { return m_State; } + int StartGame ( void ); + +protected: + + // Assign a session ID to the session. Do this just before calling AddSession + void AssignSessionID( CNetSession* pSession ); + + // Add the session. This will be called after the session passes the + // handshake and authentication stages. AssignSessionID should've been called + // on the session prior to calling this method. + //void AddSession( CNetServerSession* pSession ); + + // Remove the session from the server + //void RemoveSession( CNetServerSession* pSession ); + + // Queue a command coming in from the wire. The command has been validated + // by the caller. + void QueueIncomingCommand( CNetMessage* pMessage ); + + // Call the JS callback for incoming events + void OnPlayerChat ( const CStrW& from, const CStrW& message ); + void OnPlayerLeave ( CNetSession* pSession ); + void SetupPlayer ( CNetSession* pSession ); + + //static bool OnPlayerJoin ( void* pContext, CFsmEvent* pEvent ); + static bool OnError ( void* pContext, CFsmEvent* pEvent ); + static bool OnHandshake ( void* pContext, CFsmEvent* pEvent ); + static bool OnAuthenticate ( void* pContext, CFsmEvent* pEvent ); + static bool OnPreGame ( void* pContext, CFsmEvent* pEvent ); + static bool OnInGame ( void* pContext, CFsmEvent* pEvent ); + static bool OnChat ( void* pContext, CFsmEvent* pEvent ); + + // OVERRIDES FROM CServerSocket + //virtual void OnAccept( const CSocketAddress& address ); + + // OVERRIDES FROM CTurnManager + virtual void NewTurn( void ); + virtual bool NewTurnReady( void ); + virtual void QueueLocalCommand( CNetMessage* pMessage ); + + // Will only be called from the Network Thread, by the OnAccept handler + //virtual CNetServerSession* CreateSession( CSocketInternal* pSocketInternal); + + // Ask the server if the session is allowed to start observing. + // + // Returns: + // true if the session should be made an observer + // false otherwise + virtual bool AllowObserver( CNetSession* pSession ); + +private: + + CGameAttributes* m_GameAttributes; // Stores game attributes + //int m_LastSessionID; // Stores the last session ID + //SessionMap m_Sessions; // Managed sessions + CJSMap< IDSessionMap > m_JsSessions; + CMutex m_Mutex; // Synchronization object for batches + + /* + All sessions that have observer status (observer as in watcher - simple + chatters don't have an entry here, only in m_Sessions). + Sessions are added here after they have successfully requested observer + status. + */ + SessionList m_Observers; + uint m_MaxObservers; // Maximum number of observers + CGame* m_Game; // Pointer to actual game + NetServerState m_State; // Holds server state + CStrW m_Name; // Server name + CStrW m_WelcomeMessage; // Nice welcome message + //CPlayer* m_Player; // Pointer to 'server' player + CStrW m_PlayerName; // Player name + CStrW m_PlayerPassword; // Player password + int m_Port; // The listening port + CScriptObject m_OnChat; + CScriptObject m_OnClientConnect; + CScriptObject m_OnClientDisconnect; + +// static CGameAttributes::UpdateCallback AttributeUpdate; +// static CPlayer::UpdateCallback PlayerAttributeUpdate; +// static PlayerSlotAssignmentCB PlayerSlotAssignmentCallback; + + static void AttributeUpdate ( const CStrW& name, const CStrW& newValue, void* pData); + static void PlayerAttributeUpdate ( const CStrW& name, const CStrW& value, CPlayer* pPlayer, void* pData ); + static void PlayerSlotAssignment ( void* pData, CPlayerSlot* pPlayerSlot ); +}; + +extern CNetServer *g_NetServer; + +#endif // NETSERVER_H + diff --git a/source/network/NetSession.cpp b/source/network/NetSession.cpp new file mode 100644 index 0000000000..3da1bef57d --- /dev/null +++ b/source/network/NetSession.cpp @@ -0,0 +1,895 @@ +/** + *----------------------------------------------------------------------------- + * FILE : NetSession.cpp + * PROJECT : 0 A.D. + * DESCRIPTION : Network session class implementation + *----------------------------------------------------------------------------- + */ + +// INCLUDES +#include "precompiled.h" +//#include "SessionManager.h" +#include "NetSession.h" +//#include "NetServer.h" +#include "NetLog.h" + +#pragma warning( disable : 4100 ) + +// DECLARATIONS + +//----------------------------------------------------------------------------- +// Name: CNetHost() +// Desc: Constructor +//----------------------------------------------------------------------------- +CNetHost::CNetHost( void ) +{ + m_Host = NULL; + m_Buffer = NULL; + m_BufferSize = 0; + +// m_WorkerID = 0; +// m_StopWorker = NULL; +} + +//----------------------------------------------------------------------------- +// Name: ~CNetHost() +// Desc: Destructor +//----------------------------------------------------------------------------- +CNetHost::~CNetHost( void ) +{ + // Release host + if ( m_Host ) enet_host_destroy( m_Host ); + if ( m_Buffer ) delete [] m_Buffer; + + // Release running semaphore +// if ( m_StopWorker ) sem_close( m_StopWorker ); + + m_Host = NULL; + m_Buffer = NULL; + m_BufferSize = 0; +// m_WorkerID = 0; +// m_StopWorker = NULL; +} + +//----------------------------------------------------------------------------- +// Name: Create() +// Desc: Creates a client host +//----------------------------------------------------------------------------- +bool CNetHost::Create( void ) +{ + // Create ENet host + m_Host = enet_host_create( NULL, 1, 0, 0 ); + if ( !m_Host ) return false; + + return true; +} + +//----------------------------------------------------------------------------- +// Name: Create() +// Desc: Creates a server host +//----------------------------------------------------------------------------- +bool CNetHost::Create( uint port, uint maxPeers ) +{ + ENetAddress addr; + + // Bind to default host + addr.host = ENET_HOST_ANY; + addr.port = port; + + // Create ENet server + m_Host = enet_host_create( &addr, maxPeers, 0, 0 ); + if ( !m_Host ) return false; + + return true; +} + +//----------------------------------------------------------------------------- +// Name: Shutdown() +// Desc: Shuts down network server and releases any resources +//----------------------------------------------------------------------------- +void CNetHost::Shutdown( void ) +{ + // Destroy server + if ( m_Host ) enet_host_destroy( m_Host ); + + // Stop worker thread +// sem_post( m_StopWorker ); +// if ( m_WorkerID ) pthread_join( m_WorkerID, NULL ); + + // Disconnect and release each peer + PeerSessionList::iterator it = m_PeerSessions.begin(); + for ( ; it != m_PeerSessions.end(); it++ ) + { + if ( !it->pSession ) continue; + + Disconnect( it->pSession ); + + delete it->pSession; + } + + m_PeerSessions.clear(); + + m_Host = NULL; +// m_WorkerID = 0; +} + +//----------------------------------------------------------------------------- +// Name: Connect() +// Desc: Connects to the specified remote host +// Note: Only clients use this method for connection to server +//----------------------------------------------------------------------------- +bool CNetHost::Connect( const CStr& host, uint port ) +{ + ENetEvent event; + ENetAddress addr; + PeerSession item; + + assert( m_Host ); + + // Bind to specified host + addr.port = port; + if ( enet_address_set_host( &addr, host.c_str() ) < 0 ) return false; + + // Initiate connection, allocate one channel + ENetPeer* pPeer = enet_host_connect( m_Host, &addr, 1 ); + if ( !pPeer ) return false; + + // Wait 3 seconds for the connection to succeed + if ( enet_host_service( m_Host, &event, 5000 ) > 0 && + event.type == ENET_EVENT_TYPE_CONNECT ) + { + // Connection succeeded + CNetSession* pNewSession = new CNetSession( this, event.peer ); + if ( !pNewSession ) return false; + + if ( !SetupSession( pNewSession ) ) return false; + + // Successfully handled? + if ( !HandleConnect( pNewSession ) ) + { + delete pNewSession; + + return false; + } + + NET_LOG3( "Successfully connected to server %s:%d succeeded", host.c_str(), port ); + + // Store the only server session + item.pPeer = event.peer; + item.pSession = pNewSession; + m_PeerSessions.push_back( item ); + + return true; + } + + NET_LOG3( "Connection to server %s:%d failed", host.c_str(), port ); + + // 3 seconds are up or a host was disconnected + enet_peer_reset( pPeer ); + + return false; +} + +//----------------------------------------------------------------------------- +// Name: Disconnect() +// Desc: Disconnects the specified session from the host +//----------------------------------------------------------------------------- +bool CNetHost::Disconnect( CNetSession* pSession ) +{ + ENetEvent event; + + // Validate parameters + if ( !pSession ) return false; + + assert( m_Host ); + assert( pSession->m_Peer ); + + // Disconnect peer + enet_peer_disconnect( pSession->m_Peer ); + + // Allow up to 3 seconds for the disconnect to succeed + while ( enet_host_service( m_Host, &event, 5000 ) > 0 ) + { + switch ( event.type ) + { + case ENET_EVENT_TYPE_RECEIVE: + + // Drop any received packets + enet_packet_destroy( event.packet ); + break; + + case ENET_EVENT_TYPE_DISCONNECT: + + // Disconnect received for peer + if ( !HandleDisconnect( pSession ) ) return false; + break; + } + } + + // Disconnect attempt didn't succeed, force connection down + enet_peer_reset( pSession->m_Peer ); + + return true; +} + +//----------------------------------------------------------------------------- +// Name: Run() +// Desc: +//----------------------------------------------------------------------------- +/*bool CNetHost::Run( void ) +{ + assert( m_Host ); + + // Host created? + if ( !m_Host ) return false; + + // Already running? + if ( m_WorkerID != 0 ) return true; + + // Create run semaphore + m_StopWorker = sem_open( "//WFG_HostWorkerRun", O_CREAT | O_EXCL, 0700, 0 ); + if ( !m_StopWorker ) return false; + + // Create worker thread + if ( pthread_create( &m_WorkerID, 0, &WorkerFunc, this ) < 0 ) return false; + + return true; +} + +//----------------------------------------------------------------------------- +// Name: WorkerFunc() +// Desc: Worker thread function +//----------------------------------------------------------------------------- +void* CNetHost::WorkerFunc( void* pData ) +{ + ENetEvent event; + CNetSession* pSession = NULL; + PeerSession item; + PeerSessionList::iterator it; + + // Validate parameters + if ( !pData ) return NULL; + + CNetHost* pHost = ( CNetHost* )pData; + + // Poll host for events + while ( true ) + { + // Decide whether to stop or not + if ( !sem_timedwait( pHost->m_StopWorker, NULL ) ) break; + + int retval = enet_host_service( pHost->m_Host, &event, 100 ); + + // Any event? + if ( !retval ) continue; + + // Any error? + if ( !retval ) break; + + // Handle occured event + switch( event.type ) + { + case ENET_EVENT_TYPE_CONNECT: + + // A new client has connected, handle it + pSession = new CNetSession( pHost, event.peer ); + if ( !pSession ) return NULL; + + // Successfully handled? + if ( !pHost->HandleConnect( pSession ) ) return NULL; + + // Add new item to internal list + item.pPeer = event.peer; + item.pSession = pSession; + pHost->m_PeerSessions.push_back( item ); + + break; + + case ENET_EVENT_TYPE_DISCONNECT: + + // Client has disconnected, handle it + it = pHost->m_PeerSessions.begin();; + for ( ; it != pHost->m_PeerSessions.end(); it++ ) + { + // Is this our session? + if ( it->pPeer == event.peer ) + { + // Successfully handled? + if ( !pHost->HandleDisconnect( it->pSession ) ) return NULL; + + pHost->m_PeerSessions.erase( it ); + } + } + + break; + + case ENET_EVENT_TYPE_RECEIVE: + + // A new data packet was received from client, handle message + it = pHost->m_PeerSessions.begin(); + for ( ; it != pHost->m_PeerSessions.end(); it++ ) + { + // Is this our session? + if ( it->pPeer == event.peer ) + { + // Create message from raw data + CNetMessage* pNewMessage = CNetMessageFactory::CreateMessage( event.packet->data, event.packet->dataLength ); + if ( !pNewMessage ) return NULL; + + // Successfully handled? + if ( !pHost->HandleMessageReceive( pNewMessage, it->pSession ) ) return NULL; + } + } + + break; + } + } + + return NULL; +}*/ + +//----------------------------------------------------------------------------- +// Name: ProcessEvents() +// Desc: Wait for events and shuttles packets between the host and its peers +//----------------------------------------------------------------------------- +bool CNetHost::Poll( void ) +{ + ENetEvent event; + CNetSession* pSession = NULL; + PeerSession item; + PeerSessionList::iterator it; + + assert( m_Host ); + + // Poll host for events + while ( enet_host_service( m_Host, &event, 0 ) > 0 ) + { + // Handle occured event + switch( event.type ) + { + case ENET_EVENT_TYPE_CONNECT: + + // A new client has connected, handle it + pSession = new CNetSession( this, event.peer ); + if ( !pSession ) return false; + + // Setup new session + if ( !SetupSession( pSession ) ) return false; + + // Successfully handled? + if ( !HandleConnect( pSession ) ) return false; + + NET_LOG3( "A new client connected from %x:%u", event.peer->address.host, event.peer->address.port ); + event.peer->data = pSession; + + // Add new item to internal list + item.pPeer = event.peer; + item.pSession = pSession; + m_PeerSessions.push_back( item ); + + break; + + case ENET_EVENT_TYPE_DISCONNECT: + + // Client has disconnected, handle it + it = m_PeerSessions.begin();; + for ( ; it != m_PeerSessions.end(); it++ ) + { + // Is this our session? + if ( it->pPeer == event.peer ) + { + // Successfully handled? + if ( !HandleDisconnect( it->pSession ) ) return false; + + m_PeerSessions.erase( it ); + + NET_LOG2( "%x disconnected", event.peer->data ); + + return true; + } + } + + break; + + case ENET_EVENT_TYPE_RECEIVE: + + // A new data packet was received from client, handle message + it = m_PeerSessions.begin(); + for ( ; it != m_PeerSessions.end(); it++ ) + { + // Is this our session? + if ( it->pPeer == event.peer ) + { + // Create message from raw data + CNetMessage* pNewMessage = CNetMessageFactory::CreateMessage( event.packet->data, event.packet->dataLength ); + if ( !pNewMessage ) return false; + + // Successfully handled? + if ( !HandleMessageReceive( pNewMessage, it->pSession ) ) return false; + + NET_LOG4( "Message %s of size %u was received from %x", pNewMessage->ToString().c_str(), pNewMessage->GetSerializedLength(), event.peer->data ); + + // Done using the packet + enet_packet_destroy( event.packet ); + } + } + + break; + } + } + + return true; +} + +//----------------------------------------------------------------------------- +// Name: Broadcast() +// Desc: Broadcast the specified message to connected clients +// Note: Reference counting for a sending message requires multithreading +// locking mechanisms so a clone of the message is made and sent out +//----------------------------------------------------------------------------- +void CNetHost::Broadcast( const CNetMessage* pMessage ) +{ + // Validate parameters + if ( !pMessage ) return; + + // Loop through the list of sessions and send the message to each + for ( uint i = 0; i < GetSessionCount(); i++ ) + { + CNetSession* pCurrSession = GetSession( i ); + if ( !pCurrSession ) continue; + + SendMessage( pCurrSession, pMessage ); + } +} + +//----------------------------------------------------------------------------- +// Name: ResizeBuffer() +// Desc: Resizes the internal buffer +//----------------------------------------------------------------------------- +void CNetHost::ResizeBuffer( uint size ) +{ + // Already enough space? + if ( size <= m_BufferSize ) return; + + // Allocate enough space for the new buffer + u8* pBuffer = new u8[ ALIGN_BLOCK( m_BufferSize + size ) ]; + if ( !pBuffer ) return; + + // Any old data? + if ( m_Buffer ) + { + // Copy old data + memcpy( pBuffer, m_Buffer, m_BufferSize ); + + delete [] m_Buffer; + } + + // Store new buffer + m_Buffer = pBuffer; + + // Store new buffer size + m_BufferSize = ALIGN_BLOCK( m_BufferSize + size ); +} + +//----------------------------------------------------------------------------- +// Name: SendMessage() +// Desc: Sends the specified message to peer +//----------------------------------------------------------------------------- +bool CNetHost::SendMessage( + const CNetSession* pSession, + const CNetMessage* pMessage ) +{ + // Validate parameters + if ( !pMessage || !pSession ) return false; + + assert( pSession->m_Peer ); + assert( m_Host ); + + uint size = pMessage->GetSerializedLength(); + + // Adjust buffer for message + ResizeBuffer( size ); + + // Save message to internal buffer + pMessage->Serialize( m_Buffer ); + + // Create a reliable packet + ENetPacket* pPacket = enet_packet_create( m_Buffer, size, ENET_PACKET_FLAG_RELIABLE ); + if ( !pPacket ) return false; + + // Let ENet send the message to peer + if ( enet_peer_send( pSession->m_Peer, ENET_DEFAULT_CHANNEL, pPacket ) < 0 ) + { + // ENet failed to send the packet + NET_LOG( "Failed to send ENet packet to peer" ); + + return false; + } + else + { + NET_LOG( "Successfully sent ENet packet to peer" ); + } + + enet_host_flush( m_Host ); + + return true; +} + +//----------------------------------------------------------------------------- +// Name: ReceiveMessage() +// Desc: Receives a message from client if incoming packets are available +//----------------------------------------------------------------------------- +CNetMessage* CNetHost::ReceiveMessage( const CNetSession* pSession ) +{ + // Validate parameters + if ( !pSession ) return NULL; + + assert( pSession->m_Peer ); + + // Let ENet receive a message from peer + ENetPacket* pPacket = enet_peer_receive( pSession->m_Peer, ENET_DEFAULT_CHANNEL ); + if ( !pPacket ) return NULL; + + // Create new message + return CNetMessageFactory::CreateMessage( pPacket->data, pPacket->dataLength ); +} + +//----------------------------------------------------------------------------- +// Name: SetupSession() +// Desc: Setup new session upon creation +//----------------------------------------------------------------------------- +bool CNetHost::SetupSession( CNetSession* pSession ) +{ + return true; +} + +//----------------------------------------------------------------------------- +// Name: HandleConnect() +// Desc: Allow application to handle client connect +//----------------------------------------------------------------------------- +bool CNetHost::HandleConnect( CNetSession* pSession ) +{ + return true; +} + +//----------------------------------------------------------------------------- +// Name: HandleDisconnect() +// Desc: Allow application to handle client disconnect +//----------------------------------------------------------------------------- +bool CNetHost::HandleDisconnect( CNetSession* pSession ) +{ + return true; +} + +//----------------------------------------------------------------------------- +// Name: HandleMessageReceive() +// Desc: Allow application to handle message recive +//----------------------------------------------------------------------------- +bool CNetHost::HandleMessageReceive( + CNetMessage* pMessage, + CNetSession* pSession ) +{ + // Validate parameters + if ( !pSession || !pMessage ) return false; + + // Update FSM + return pSession->Update( pMessage->GetType(), pMessage ); +} + +//----------------------------------------------------------------------------- +// Name: GetSessionCount() +// Desc: Returns the number of sessions the host manages +//----------------------------------------------------------------------------- +uint CNetHost::GetSessionCount( void ) const +{ + return ( uint )m_PeerSessions.size(); +} + +//----------------------------------------------------------------------------- +// Name: GetSession() +// Desc: Rteurns the session for the index +//----------------------------------------------------------------------------- +CNetSession* CNetHost::GetSession( uint index ) +{ + // Validate parameter + if ( index >= GetSessionCount() ) return NULL; + + return m_PeerSessions[ index ].pSession; +}; + +//----------------------------------------------------------------------------- +// Name: CNetSession() +// Desc: Constructor +//----------------------------------------------------------------------------- +CNetSession::CNetSession( CNetHost* pHost, ENetPeer* pPeer ) +{ + m_Host = pHost; + m_Peer = pPeer; + m_ID = INVALID_SESSION; + m_PlayerSlot = NULL; + m_ReadyForTurn = false; + + // Register the network session + //g_SessionManager.Register( this ); +} + +//----------------------------------------------------------------------------- +// Name: ~CNetSession() +// Desc: Destructor +//----------------------------------------------------------------------------- +CNetSession::~CNetSession( void ) +{ + // Release any resources + //if ( m_Host ) enet_host_destroy( m_Host ); + + //m_Host = NULL; + m_Peer = NULL; + + // Unregister the network session + //g_SessionManager.Unregister( this ); +} + +//----------------------------------------------------------------------------- +// Name: SetName() +// Desc: Set a new name for the session +//----------------------------------------------------------------------------- +void CNetSession::SetName( const CStr& name ) +{ + m_Name = name; +} + +//----------------------------------------------------------------------------- +// Name: SetID() +// Desc: Set new ID for this session +//----------------------------------------------------------------------------- +void CNetSession::SetID( uint ID ) +{ + m_ID = ID; +} + +//----------------------------------------------------------------------------- +// Name: SetPlayer() +// Desc: Set the player for this session +//----------------------------------------------------------------------------- +void CNetSession::SetPlayer( CPlayer* pPlayer ) +{ + m_Player = pPlayer; +} + +//----------------------------------------------------------------------------- +// Name: SetPlayerSlot() +// Desc: Set the player slot for this session +//----------------------------------------------------------------------------- +void CNetSession::SetPlayerSlot( CPlayerSlot* pPlayerSlot ) +{ + m_PlayerSlot = pPlayerSlot; +} + +//----------------------------------------------------------------------------- +// Name: StartGame() +// Desc: Called by server after informing all clients about starting the game +//----------------------------------------------------------------------------- +void CNetSession::StartGame( void ) +{ +} + +//----------------------------------------------------------------------------- +// Name: Push() +// Desc: Sends a message through ENet +//----------------------------------------------------------------------------- +void CNetSession::Push( CNetMessage* pMessage ) +{ + // Validate parameters + if ( !pMessage ) return; + + assert( m_Host ); + + m_Host->SendMessage( this, pMessage ); +} + +//----------------------------------------------------------------------------- +// Name: TryPop() +// Desc: Receives a message through ENet +//----------------------------------------------------------------------------- +CNetMessage* CNetSession::TryPop( void ) +{ + assert( m_Host ); + + return m_Host->ReceiveMessage( this ); +} + +//----------------------------------------------------------------------------- +// Name: ScriptingInit() +// Desc: +//----------------------------------------------------------------------------- +void CNetSession::ScriptingInit( void ) +{ + AddProperty( L"id", &CNetSession::m_ID ); + AddProperty( L"name", &CNetSession::m_Name ); + AddMethod( "close", 0 ); + + CJSObject::ScriptingInit( "NetSession" ); +} + +//----------------------------------------------------------------------------- +// Name: JSI_Close() +// Desc: +//----------------------------------------------------------------------------- +bool CNetSession::JSI_Close( JSContext* cx, uintN argc, jsval* argv ) +{ + return false; +} + +//----------------------------------------------------------------------------- +// Name: HandleMessage() +// Desc: +//----------------------------------------------------------------------------- +//bool CNetSession::HandleMessage( CNetMessage* pMessage ) +//{ +// return true; +//} + +/* +//----------------------------------------------------------------------------- +// Name: CNetServerSession() +// Desc: Constructor +//----------------------------------------------------------------------------- +CNetServerSession::CNetSession( + CNetServer* pServer, + NetMessageHandler* pHandler ) +: CNetSession( pHandler ) +{ + m_Server = pServer; + m_Player = NULL; + m_PlayerSlot = NULL; + m_IsObserver = false; +} + +//----------------------------------------------------------------------------- +// Name: CNetServerSession() +// Desc: Constructor +//----------------------------------------------------------------------------- +CNetServerSession::CNetSession( + CNetServer* pServer, + CSocketInternal* pSocketInternal, + NetMessageHandler* pHandler ) +: CNetSession( pSocketInternal, pHandler ) +{ + m_Server = pServer; + m_Player = NULL; + m_PlayerSlot = NULL; + m_IsObserver = false; +} + +//----------------------------------------------------------------------------- +// Name: ~CNetServerSession() +// Desc: Destructor +//----------------------------------------------------------------------------- +CNetServerSession::~CNetServerSession( void ) +{ +} + +//----------------------------------------------------------------------------- +// Name: StartGame() +// Desc: Called by server after informing all clients about starting the game +//----------------------------------------------------------------------------- +void CNetServerSession::StartGame( void ) +{ +} + +//----------------------------------------------------------------------------- +// Name: SetPlayer() +// Desc: Set the player for this session +//----------------------------------------------------------------------------- +void CNetServerSession::SetPlayer( CPlayer* pPlayer ) +{ + m_Player = pPlayer; +} + +//----------------------------------------------------------------------------- +// Name: SetPlayerSlot() +// Desc: Set the player slot for this session +//----------------------------------------------------------------------------- +void CNetServerSession::SetPlayerSlot( CPlayerSlot* pPlayerSlot ) +{ + m_PlayerSlot = pPlayerSlot; +} + +//----------------------------------------------------------------------------- +// Name: SetID() +// Desc: Set new session ID +//----------------------------------------------------------------------------- +void CNetServerSession::SetID( uint ID ) +{ + m_ID = ID; +} + +//----------------------------------------------------------------------------- +// Name: BaseHandler() +// Desc: +//----------------------------------------------------------------------------- +bool CNetServerSession::BaseHandler( + CNetMessage* pMessage, + CNetSession* pSession ) +{ + assert( pMessage ); + assert( pSession ); + + // Validate parameters + if ( !pMessage || !pSession ) return false; + + CNetServerSession* pSrvSession = dynamic_cast< CNetSession* >( pSession ); + if ( pSrvSession ) return false; + + // Handler NMT_ERROR message only + if ( pMessage->GetType() != NMT_ERROR ) return false; + + CNetErrorMessage* pErrMessage = dynamic_cast< CNetErrorMessage* >( pMessage ); + if ( !pErrMessage ) return false; + + if ( pErrMessage->GetState() == SERVER_STATE_DISCONNECTED ) + { + if ( pSrvSession->m_ID != -1 ) + { + pSrvSession->m_Server->RemoveSession( pSrvSession ); + } + + delete pSrvSession; + } + else + { + // Not disconnected? Weired... + LOG( WARNING, LOG_CAT_NET, "CNetServerSession::BaseHandler() NMT_ERROR: %s", pErrMessage->ToString().c_str() ); + } + + delete pMessage; + + return true; +} + +//----------------------------------------------------------------------------- +// Name: HandshakeHandler() +// Desc: +//----------------------------------------------------------------------------- +bool CNetServerSession::HandshakeHandler( + CNetMessage* pMessage, + CNetSession* pSession ) +{ + assert( pMessage ); + assert( pSession ); + assert( m_Server ); + + // Validate parameters + if ( !pMessage || !pSession ) return false; + + CNetServerSession* pSrvSession = dynamic_cast< CNetServerSession* >( pSession ); + if ( !pSrvSession ) return false; + + LOG( NORMAL, LOG_CAT_NET, "CNetServerSession::HandshakeHandler() %s", pMessage->ToString().c_str() ); + + // Call base handler if other message thant NMT_ClientHandshake + if ( pMessage->GetType() != NMT_ClientHandshake ) BaseHandler( pMessage, pSession ); + + CClientHandshake* pHandshakeMessage = dynamic_cast< CClientHandshake* >( pMessage ); + if ( !pHandshakeMessage ) return false; + + if ( pHandshakeMessage->m_ProtocolVersion != PS_PROTOCOL_VERSION ) + { + pSrvSession->Push( new CCloseRequestMessage() ); + BaseHandler( new CNetErrorMessage( PS_OK, SERVER_STATE_DISCONNECTED ), pSrvSession ); + } + + //??? (else) + CServerHandshakeResponse* pNewMessage = new CServerHandshakeResponse(); + pNewMessage->m_UseProtocolVersion = PS_PROTOCOL_VERSION; + pNewMessage->m_Flags = 0; + pNewMessage->m_Message = pSrvSession->m_Server->m_WelcomeMessage; + + pSrvSession->Push( pNewMessage ); + + pSrvSession->m_MessageHandler = AuthHandler; + + delete pMessage; + + return true; +}*/ diff --git a/source/network/NetSession.h b/source/network/NetSession.h new file mode 100644 index 0000000000..d58fcb6f09 --- /dev/null +++ b/source/network/NetSession.h @@ -0,0 +1,341 @@ +/** + *----------------------------------------------------------------------------- + * FILE : NetSession.h + * PROJECT : 0 A.D. + * DESCRIPTION : Network session class interface file + *----------------------------------------------------------------------------- + */ + +#ifndef NETSESSION_H +#define NETSESSION_H + +// INCLUDES +#include "Network.h" +#include "ps/Singleton.h" +#include "ps/GameAttributes.h" +#include "ps/Player.h" +#include "fsm.h" +#include "enet/enet.h" + +#include + +// DECLARATIONS +#define INVALID_SESSION ( uint )( ~0 ) +#define ENET_DEFAULT_CHANNEL 0 + +class CNetSession; +class CNetHost; + +typedef struct +{ + ENetPeer* pPeer; + CNetSession* pSession; + +} PeerSession; + +typedef struct +{ + CNetHost* pHost; + CNetSession* pSession; + +} FsmActionCtx; + +typedef std::vector< PeerSession > PeerSessionList; + +/* + CLASS : CNetHost + DESCRIPTION : CNetHost is a wrapper around ENet host conecept + NOTES : +*/ + +class CNetHost : public Singleton< CNetHost > +{ +public: + + CNetHost( void ); + virtual ~CNetHost( void ); + + bool Create( void ); + bool Create( uint port, uint maxPeers ); + void Shutdown( void ); + + /** + * Indicates whether the host is currently a server + * + * @return Boolean indicating whether the host is a server + */ + virtual bool IsServer( void ) const { return false; } + + /** + * Indicates whether the host is currently a client + * + * @return Boolean indicating whether the host is a client + */ + virtual bool IsClient( void ) const { return false; } + + /** + * Returns the number of sessions for the host + * + * @return The number of sessions + */ + uint GetSessionCount( void ) const; + + /** + * Returns the session object for the specified index + * + * @param index Index for session + * @return Session object for index or NULL if not found + */ + CNetSession* GetSession( uint index ); + + /** + * Connects to foreign host + * + * @param host Foreign host name + * @param port Port on which the foreign host listens + * @return true on success, false on failure + */ + bool Connect( const CStr& host, uint port ); + + /** + * Disconnects session from host + * + * @param pSession Session representing peer + * @return true on success, false otherwise + */ + bool Disconnect( CNetSession* pSession ); + + /** + * Listens for incoming connections and dispatches host events + * + * @return true on exit, false dispatch failure + */ + //bool Run( void ); + bool Poll( void ); + + /** + * Broadcast the specified message to connected clients + * + * @param pMessage Message to broadcast + */ + void Broadcast( const CNetMessage* pMessage ); + + /** + * Send the specified message to client + * + * @param pMessage The message to send + */ + virtual bool SendMessage( + const CNetSession* pSession, + const CNetMessage* pMessage ); + + /** + * Receive a message from client if available + * + */ + virtual CNetMessage* ReceiveMessage( const CNetSession* pSession ); + +protected: + + /** + * Attempts to resize the internal buffer to the size indicated by the + * passed parameter. + * + * @param size The new size for the buffer + */ + void ResizeBuffer( uint size ); + + // Allow application to handle new client connect + virtual bool SetupSession ( CNetSession* pSession ); + virtual bool HandleConnect ( CNetSession* pSession ); + virtual bool HandleDisconnect ( CNetSession* pSession ); + virtual bool HandleMessageReceive ( + CNetMessage* pMessage, + CNetSession* pSession ); + + /** + * Worker thread function + * + * @pData Argument specified on thread creation + * @return NULL + */ + //static void* WorkerFunc( void* pData ); + +private: + + // Not implemented + CNetHost( const CNetHost& ); + CNetHost& operator=( const CNetHost& ); + + u8* m_Buffer; // Serialize out messages buffer + uint m_BufferSize; // Output buffer size + ENetHost* m_Host; // Represents this host + PeerSessionList m_PeerSessions; // Session list of connected peers + //pthread_t m_WorkerID; // Worker thread + //sem_t* m_StopWorker; // Worker thread stop semaphore +}; + +/* + CLASS : CNetSession + DESCRIPTION : CNetSession is a wrapper class around ENet peer concept + which represents a peer from a network connection. A + network session is spawned by CNetServer each time a + client connects and destroyed when it disconnects. When a + new message is received fom a client, its representing + session object's message handler is called for processing + that message. + CNetSession is also a state machine. All client requests + are delegated to the current state. The current + CNetSessionState object's methods will change the current + state as appropriate. + NOTES : +*/ + +class CNetSession : public CFsm, + public CJSObject< CNetSession >, + public IMessagePipeEnd +{ + friend class CNetHost; + +public: + + virtual ~CNetSession( void ); + + /** + * Retrieves the name of the session + * + * @return Session name + */ + const CStrW& GetName( void ) const { return m_Name; } + + /** + * Set the new name for the session + * + * @param name The session new name + */ + void SetName( const CStr& name ); + + /** + * Retrieves the ID of the session + * + * @return Session ID + */ + uint GetID( void ) const { return m_ID; } + + /** + * Set the ID for this session + * + * @param New session ID + */ + void SetID( uint ID ); + + /** + * Allows both client and server to set a callback handler + * + * @param pCallbackHandler Callback handler + */ + //void SetCallbackHandler( ISessionCallback* pCallbackHandler ); + + /** + * Disconnects the client from remote host + * + */ + void Reset( void ); + + void SetPlayer( CPlayer* pPlayer ); + CPlayer* GetPlayer( void ) { return m_Player; } + void SetPlayerSlot( CPlayerSlot* pPlayerSlot ); + CPlayerSlot* GetPlayerSlot( void ) { return m_PlayerSlot; } + void StartGame( void ); + virtual void Push( CNetMessage* pMessage ); + virtual CNetMessage* TryPop( void ); + bool IsReadyForTurn( void ) const { return m_ReadyForTurn; } + void SetReadyForTurn( bool newValue ) { m_ReadyForTurn = newValue; } + bool JSI_Close( JSContext *cx, uintN argc, jsval *argv ); + static void ScriptingInit( void ); + + //bool HandleMessage( CNetMessage* pMessage ); + +protected: + + /** + * Process the message passed as parameter + * + * @param message The message to process + * @return true if the message was handler + * successufully, false otherwise + */ + //bool ProcessMessage( const CNetMessage& message ); + +private: + + // Only the hosts can create sessions + CNetSession( CNetHost* pHost, ENetPeer* pPeer ); + + // Not implemented + CNetSession( void ); + CNetSession( const CNetSession& ); + CNetSession& operator=( const CNetSession& ); + + CNetHost* m_Host; // The associated local host + ENetPeer* m_Peer; // Represents the peer host + uint m_ID; // Session ID + CStrW m_Name; // Session name + CPlayer* m_Player; + CPlayerSlot* m_PlayerSlot; + bool m_ReadyForTurn; // Next turn ready flag +}; + +/* + CLASS : CNetServerSession + DESCRIPTION : + NOTES : +*/ + +/*class CNetServerSession : public CNetSession, + public CJSObject< CNetServerSession > +{ +public: + + bool IsObserver ( void ) const { return m_IsObserver; } + CPlayer* GetPlayer ( void ) const { return m_Player; } + CPlayerSlot* GetPlayerSlot ( void ) const { return m_PlayerSlot; } + void StartGame ( void ); + void SetPlayer ( CPlayer* pPlayer ); + void SetPlayerSlot ( CPlayerSlot* pPlayerSlot ); + +protected: + + CNetServerSession( + CNetServer* pServer, + NetMessageHandler* pHandler = m_HandshakeHandler ); + CNetServerSession( + CNetServer* pServer, + CSocketInternal* pSocketInternal, + NetMessageHandler* pHandler = m_HandshakeHandler ); + virtual ~CNetServerSession( void ); + +private: + + static void ScriptingInit ( void ); + bool JSI_Close ( + JSContext* pContext, + uintN argc, + jsval* argv ); + + CNetServer* m_Server; + CPlayer* m_Player; + CPlayerSlot* m_PlayerSlot; + bool m_IsObserver; + + static bool HandshakeHandler( CNetMessage* pMessage, CNetSession* pSession ); + static bool ObserverHandler ( CNetMessage* pMessage, CNetSession* pSession ); + static bool BaseHandler ( CNetMessage* pMessage, CNetSession* pSession ); + static bool AuthHandler ( CNetMessage* pMessage, CNetSession* pSession ); + static bool PreGameHandler ( CNetMessage* pMessage, CNetSession* pSession ); + static bool InGameHandler ( CNetMessage* pMessage, CNetSession* pSession ); + static bool ChatHandler ( CNetMessage* pMessage, CNetSession* pSession ); +};*/ + +#endif // NETSESSION_H + diff --git a/source/network/Server.cpp b/source/network/Server.cpp deleted file mode 100644 index cf4ba168e6..0000000000 --- a/source/network/Server.cpp +++ /dev/null @@ -1,454 +0,0 @@ -#include "precompiled.h" - -#include "Server.h" -#include "ServerSession.h" -#include "Network.h" -#include "JSEvents.h" - -#include "scripting/ScriptableObject.h" - -#include "ps/Game.h" -#include "simulation/Simulation.h" -#include "ps/Player.h" -#include "ps/CLogger.h" -#include "ps/CConsole.h" -#include "ps/ThreadUtil.h" - -#define LOG_CAT_NET "net" - -CNetServer *g_NetServer=NULL; - -using namespace std; - -// NOTE: Called in network thread -CNetServerSession *CNetServer::CreateSession(CSocketInternal *pInt) -{ - CNetServerSession *pRet=new CNetServerSession(this, pInt); - - CServerHandshake *pMsg=new CServerHandshake(); - pMsg->m_Magic=PS_PROTOCOL_MAGIC; - pMsg->m_ProtocolVersion=PS_PROTOCOL_VERSION; - pMsg->m_SoftwareVersion=PS_PROTOCOL_VERSION; - pRet->Push(pMsg); - - return pRet; -} - -// NOTE: Called in network thread -void CNetServer::OnAccept(const CSocketAddress &addr) -{ - LOG(CLogger::Normal, LOG_CAT_NET, "CNetServer::OnAccept(): Accepted connection from %s port %d", addr.GetString().c_str(), addr.GetPort()); - - CSocketInternal *pInt=Accept(); - CNetServerSession *pSession=CreateSession(pInt); - UNUSED2(pSession); -} - -CNetServer::CNetServer(CGame *pGame, CGameAttributes *pGameAttribs): - m_JSI_Sessions(&m_Sessions), - m_pGame(pGame), - m_pGameAttributes(pGameAttribs), - m_MaxObservers(5), - m_LastSessionID(1), - m_ServerPlayerName(L"Noname Server Player"), - m_ServerName(L"Noname Server"), - m_WelcomeMessage(L"Noname Server Welcome Message"), - m_Port(-1) -{ - m_pGameAttributes->SetUpdateCallback(AttributeUpdate, this); - m_pGameAttributes->SetPlayerUpdateCallback(PlayerAttributeUpdate, this); - m_pGameAttributes->SetPlayerSlotAssignmentCallback(PlayerSlotAssignmentCallback, this); - - m_pGame->GetSimulation()->SetTurnManager(this); - - // Set an incredibly long turn length for debugging - less command batch spam that way - for (int i=0; i<3; i++) - CTurnManager::SetTurnLength(i, CTurnManager::DEFAULT_TURN_LENGTH); - - g_ScriptingHost.SetGlobal("g_NetServer", OBJECT_TO_JSVAL(GetScript())); -} - -CNetServer::~CNetServer() -{ - g_ScriptingHost.SetGlobal("g_NetServer", JSVAL_NULL); - - while (m_Sessions.size() > 0) - { - SessionMap::iterator it=m_Sessions.begin(); - delete it->second; - m_Sessions.erase(it); - } -} - -void CNetServer::ScriptingInit() -{ - CJSMap::ScriptingInit("NetServer_SessionMap"); - - AddMethod("open", 0); - - AddProperty(L"sessions", &CNetServer::m_JSI_Sessions); - - AddProperty(L"serverPlayerName", &CNetServer::m_ServerPlayerName); - AddProperty(L"serverName", &CNetServer::m_ServerName); - AddProperty(L"welcomeMessage", &CNetServer::m_WelcomeMessage); - - AddProperty(L"port", &CNetServer::m_Port); - - AddProperty(L"onChat", &CNetServer::m_OnChat); - AddProperty(L"onClientConnect", &CNetServer::m_OnClientConnect); - AddProperty(L"onClientDisconnect", &CNetServer::m_OnClientDisconnect); - - CJSObject::ScriptingInit("NetServer"); -} - -bool CNetServer::JSI_Open(JSContext* UNUSED(cx), uintN UNUSED(argc), jsval* UNUSED(argv)) -{ - CSocketAddress addr; - if (m_Port == -1) - GetDefaultListenAddress(addr); - else - addr=CSocketAddress(m_Port, /* m_UseIPv6 ? IPv6 : */ IPv4); - - PS_RESULT res=Bind(addr); - if (res != PS_OK) - { - LOG(CLogger::Error, LOG_CAT_NET, "CNetServer::JSI_Open(): Bind error: %s", res); - return false; - } - - return true; -} - -PS_RESULT CNetServer::Bind(const CSocketAddress &address) -{ - PS_RESULT res=CServerSocket::Bind(address); - if (res==PS_OK) - m_ServerState=NSS_PreGame; - return res; -} - -void FillSetGameConfigCB(const CStrW& name, ISynchedJSProperty *prop, void *userdata) -{ - CSetGameConfig *pMsg=(CSetGameConfig *)userdata; - size_t size=pMsg->m_Values.size(); - pMsg->m_Values.resize(size+1); - pMsg->m_Values[size].m_Name=name; - pMsg->m_Values[size].m_Value=prop->ToString(); -} - -void CNetServer::FillSetGameConfig(CSetGameConfig *pMsg) -{ - m_pGameAttributes->IterateSynchedProperties(FillSetGameConfigCB, pMsg); -} - -void FillSetPlayerConfigCB(const CStrW& name, ISynchedJSProperty *prop, void *userdata) -{ - CSetPlayerConfig *pMsg=(CSetPlayerConfig *)userdata; - size_t size=pMsg->m_Values.size(); - pMsg->m_Values.resize(size+1); - pMsg->m_Values[size].m_Name=name; - pMsg->m_Values[size].m_Value=prop->ToString(); -} - -void CNetServer::FillSetPlayerConfig(CSetPlayerConfig *pMsg, CPlayer *pPlayer) -{ - pMsg->m_PlayerID=(u32)pPlayer->GetPlayerID(); - pPlayer->IterateSynchedProperties(FillSetPlayerConfigCB, pMsg); -} - -void CNetServer::AssignSessionID(CNetServerSession *pSession) -{ - int newID=++m_LastSessionID; - pSession->SetID(newID); - m_Sessions[newID]=pSession; -} - -void CNetServer::AddSession(CNetServerSession *pSession) -{ - { - CSetGameConfig *pMsg=new CSetGameConfig(); - FillSetGameConfig(pMsg); - pSession->Push(pMsg); - } - - // Broadcast a message for the newly added player session - CClientConnect *pMsg=new CClientConnect(); - pMsg->m_Clients.resize(1); - pMsg->m_Clients[0].m_SessionID=pSession->GetID(); - pMsg->m_Clients[0].m_Name=pSession->GetName(); - Broadcast(pMsg); - - pMsg=new CClientConnect(); - - // Server "client" - pMsg->m_Clients.resize(1); - pMsg->m_Clients.back().m_SessionID=1; // Server is always 1 - pMsg->m_Clients.back().m_Name=m_ServerPlayerName; - - // All the other clients - SessionMap::iterator it=m_Sessions.begin(); - for (;it!=m_Sessions.end();++it) - { - pMsg->m_Clients.push_back(CClientConnect::S_m_Clients()); - pMsg->m_Clients.back().m_SessionID=it->second->GetID(); - pMsg->m_Clients.back().m_Name=it->second->GetName(); - } - pSession->Push(pMsg); - - // Sync player slot assignments and player attributes - for (size_t i=0;iGetSlotCount();i++) - { - CPlayerSlot *pSlot=m_pGameAttributes->GetSlot(i); - - pSession->Push(CreatePlayerSlotAssignmentMessage(pSlot)); - - if (pSlot->GetAssignment() == SLOT_SESSION) - { - CSetPlayerConfig *pMsg=new CSetPlayerConfig(); - FillSetPlayerConfig(pMsg, pSlot->GetPlayer()); - pSession->Push(pMsg); - } - } - - OnClientConnect(pSession); -} - -void CNetServer::AttributeUpdate(const CStrW& name, const CStrW& newValue, void *userdata) -{ - CNetServer *pServer=(CNetServer *)userdata; - g_Console->InsertMessage(L"AttributeUpdate: %ls = \"%ls\"", name.c_str(), newValue.c_str()); - - CSetGameConfig *pMsg=new CSetGameConfig; - pMsg->m_Values.resize(1); - pMsg->m_Values[0].m_Name=name; - pMsg->m_Values[0].m_Value=newValue; - - pServer->Broadcast(pMsg); -} - -void CNetServer::PlayerAttributeUpdate(const CStrW& name, const CStrW& newValue, CPlayer *pPlayer, void *userdata) -{ - CNetServer *pServer=(CNetServer *)userdata; - g_Console->InsertMessage(L"PlayerAttributeUpdate(%d): %ls = \"%ls\"", pPlayer->GetPlayerID(), name.c_str(), newValue.c_str()); - - CSetPlayerConfig *pMsg=new CSetPlayerConfig; - pMsg->m_PlayerID=(u32)pPlayer->GetPlayerID(); - pMsg->m_Values.resize(1); - pMsg->m_Values[0].m_Name=name; - pMsg->m_Values[0].m_Value=newValue; - - pServer->Broadcast(pMsg); -} - -CNetMessage *CNetServer::CreatePlayerSlotAssignmentMessage(CPlayerSlot *pSlot) -{ - CAssignPlayerSlot *pMsg=new CAssignPlayerSlot(); - pMsg->m_SlotID=(u32)pSlot->GetSlotID(); - pMsg->m_SessionID=pSlot->GetSessionID(); - switch (pSlot->GetAssignment()) - { -#define CASE(_a, _b) case _a: pMsg->m_Assignment=_b; break; - CASE(SLOT_CLOSED, PS_ASSIGN_CLOSED) - CASE(SLOT_OPEN, PS_ASSIGN_OPEN) - CASE(SLOT_SESSION, PS_ASSIGN_SESSION) - //CASE(SLOT_AI, PS_ASSIGN_AI) - } - return pMsg; -} - -void CNetServer::PlayerSlotAssignmentCallback(void *userdata, CPlayerSlot *pSlot) -{ - CNetServer *pInstance=(CNetServer *)userdata; - if (pSlot->GetAssignment() == SLOT_SESSION) - pSlot->GetSession()->SetPlayerSlot(pSlot); - CNetMessage *pMsg=CreatePlayerSlotAssignmentMessage(pSlot); - g_Console->InsertMessage(L"Player Slot Assignment: %hs\n", pMsg->GetString().c_str()); - pInstance->Broadcast(pMsg); -} - -bool CNetServer::AllowObserver(CNetServerSession* UNUSED(pSession)) -{ - return m_Observers.size() < m_MaxObservers; -} - -void CNetServer::RemoveSession(CNetServerSession *pSession) -{ - SessionMap::iterator it=m_Sessions.find(pSession->GetID()); - if (it != m_Sessions.end()) - m_Sessions.erase(it); - - /* - * Player sessions require some extra care: - * - * Pre-Game: dissociate the slot that was used by the session and - * synchronize the disconnection of the client. - * - * In-Game: Revert all player's entities to Gaia control, awaiting the - * client's reconnect attempts [if/when we implement that] - * - * Post-Game: Just sync disconnection - we don't have any players anymore - * and all is fine. - * - * After this is done, call the JS callback if it's been set. - */ - if (pSession->GetPlayer()) - { - if (m_ServerState == NSS_PreGame) - { - pSession->GetPlayerSlot()->AssignClosed(); - } - else if (m_ServerState == NSS_InGame) - { - // TODO Reassign entities to Gaia control - // TODO Set everything up for re-connect and resume - SetClientPipe(pSession->GetPlayerSlot()->GetSlotID(), NULL); - pSession->GetPlayerSlot()->AssignClosed(); - } - } - - CClientDisconnect *pMsg=new CClientDisconnect(); - pMsg->m_SessionID=pSession->GetID(); - Broadcast(pMsg); - - OnClientDisconnect(pSession); - - // TODO Correct handling of observers -} - -// Unfortunately, the message queueing model is made so that each message has -// to be copied once for each socket its sent over, messages are deleted when -// sent by CMessageSocket. We could ref-count, but that requires a lot of -// thread safety stuff => hard work -void CNetServer::Broadcast(CNetMessage *pMsg) -{ - if (m_Sessions.empty()) - { - delete pMsg; - return; - } - - SessionMap::iterator it=m_Sessions.begin(); - // Skip one session - ++it; - // Send a *copy* to all remaining sessions - for (;it != m_Sessions.end();++it) - { - it->second->Push(pMsg->Copy()); - } - // Now send to the first session, *not* copying the message - m_Sessions.begin()->second->Push(pMsg); -} - -int CNetServer::StartGame() -{ - if (m_pGame->StartGame(m_pGameAttributes) != PSRETURN_OK) - { - return -1; - } - else - { - CTurnManager::Initialize(m_pGameAttributes->GetSlotCount()); - for (size_t i=0;iGetSlotCount();i++) - { - CPlayerSlot *pSlot=m_pGameAttributes->GetSlot(i); - if (pSlot->GetAssignment() == SLOT_SESSION) - CTurnManager::SetClientPipe(i, pSlot->GetSession()); - } - m_ServerState=NSS_InGame; - - SessionMap::iterator it=m_Sessions.begin(); - while (it != m_Sessions.end()) - { - it->second->StartGame(); - ++it; - } - - debug_printf("Server StartGame\n"); - Broadcast(new CStartGame()); - - // This is the signal for everyone to start their simulations. - //SendBatch(1); - - return 0; - } -} - -void CNetServer::GetDefaultListenAddress(CSocketAddress &address) -{ - address=CSocketAddress(PS_DEFAULT_PORT, IPv4); -} - -bool CNetServer::NewTurnReady() -{ - // Wait for all clients to check in - SessionMap::iterator it = m_Sessions.begin(); - for (; it != m_Sessions.end(); ++it) - { - if (!it->second->IsReadyForTurn()) - return false; - } - return true; -} - -void CNetServer::NewTurn() -{ - CScopeLock lock(m_Mutex); - - // Clear ready flags on clients - SessionMap::iterator it = m_Sessions.begin(); - for (; it != m_Sessions.end(); ++it) - { - it->second->SetReadyForTurn(false); - } - - RecordBatch(2); - - RotateBatches(); - ClearBatch(2); - - IterateBatch(1, CSimulation::GetMessageMask, m_pGame->GetSimulation()); - //debug_printf("In NewTurn - sending batch\n"); - SendBatch(1); - //IterateBatch(1, SendToObservers, this); -} - -void CNetServer::QueueLocalCommand(CNetMessage *pMsg) -{ - //debug_printf("Queueing command from server\n"); - QueueIncomingCommand(pMsg); -} - -void CNetServer::QueueIncomingCommand(CNetMessage *pMsg) -{ - CScopeLock lock(m_Mutex); - LOG(CLogger::Normal, LOG_CAT_NET, "CNetServer::QueueIncomingCommand(): %s.", pMsg->GetString().c_str()); - debug_printf("Got a command! queueing it to 2 turns from now\n"); - QueueMessage(2, pMsg); -} - -void CNetServer::OnChat(const CStrW& from, const CStrW& message) -{ - if (m_OnChat.Defined()) - { - CChatEvent evt(from, message); - m_OnChat.DispatchEvent(GetScript(), &evt); - } -} - -void CNetServer::OnClientConnect(CNetServerSession *pSession) -{ - if (m_OnClientConnect.Defined()) - { - CClientConnectEvent evt(pSession); - m_OnClientConnect.DispatchEvent(GetScript(), &evt); - } -} - -void CNetServer::OnClientDisconnect(CNetServerSession *pSession) -{ - if (m_OnClientDisconnect.Defined()) - { - CClientDisconnectEvent evt(pSession->GetID(), pSession->GetName()); - m_OnClientDisconnect.DispatchEvent(GetScript(), &evt); - } -} diff --git a/source/network/Server.h b/source/network/Server.h deleted file mode 100644 index 360a64216b..0000000000 --- a/source/network/Server.h +++ /dev/null @@ -1,153 +0,0 @@ -#ifndef INCLUDED_NETWORK_SERVER -#define INCLUDED_NETWORK_SERVER - -#include "Session.h" -#include "ps/GameAttributes.h" -#include "simulation/TurnManager.h" -#include "ps/scripting/JSMap.h" -#include "simulation/ScriptObject.h" - -class CGame; - -enum ENetServerState -{ - // We haven't opened the port yet, we're just setting some stuff up. - // This is probably equivalent to the first "Start Network Game" screen - NSS_PreBind, - // The server is open and accepting connections. This is the screen where - // rules are set up by the operator and where players join and select civs - // and stuff. - NSS_PreGame, - // In-Game state: the one with all the killing ;-) - NSS_InGame, - // The game is over and someone has won. Players might linger to chat or - // download the replay log. - NSS_PostGame -}; - -class CNetServerSession; - -class CNetServer: - protected CServerSocket, - protected CTurnManager, - public CJSObject -{ -private: - typedef std::map SessionMap; - - /* - Every connected session is in m_Sessions as soon as the Handshake and - Authentication stages are complete. - */ - SessionMap m_Sessions; - CJSMap m_JSI_Sessions; - /* - All sessions that have observer status (observer as in watcher - simple - chatters don't have an entry here, only in m_Sessions). - Sessions are added here after they have successfully requested observer - status. - */ - std::vector m_Observers; - - ENetServerState m_ServerState; - - CGame *m_pGame; - CGameAttributes *m_pGameAttributes; - - size_t m_MaxObservers; - int m_LastSessionID; - - CPlayer *m_pServerPlayer; - - CStrW m_Password; - CStrW m_ServerPlayerName; - CStrW m_ServerName; - CStrW m_WelcomeMessage; - - int m_Port; - - CScriptObject m_OnChat; - CScriptObject m_OnClientConnect; - CScriptObject m_OnClientDisconnect; - - static CGameAttributes::UpdateCallback AttributeUpdate; - static CPlayer::UpdateCallback PlayerAttributeUpdate; - static PlayerSlotAssignmentCB PlayerSlotAssignmentCallback; - - void FillSetGameConfig(CSetGameConfig *pMsg); - void FillSetPlayerConfig(CSetPlayerConfig *pMsg, CPlayer *pPlayer); - static CNetMessage *CreatePlayerSlotAssignmentMessage(CPlayerSlot *slot); - - // JS Interface Methods - bool JSI_Open(JSContext *cx, uintN argc, jsval *argv); - - // Synchronization object for batches - CMutex m_Mutex; - -protected: - friend class CNetServerSession; - - // Assign a session ID to the session. Do this just before calling AddSession - void AssignSessionID(CNetServerSession *pSession); - // Add the session. This will be called after the session passes the - // handshake and authentication stages. AssignSessionID should've been called - // on the session prior to calling this method. - void AddSession(CNetServerSession *pSession); - - // Remove the session from the server - void RemoveSession(CNetServerSession *pSession); - - // Queue a command coming in from the wire. The command has been validated - // by the caller. - void QueueIncomingCommand(CNetMessage *pMsg); - - // Call the JS callback for incoming events - void OnChat(const CStrW& from, const CStrW& message); - void OnClientConnect(CNetServerSession *pSession); - void OnClientDisconnect(CNetServerSession *pSession); - - // OVERRIDES FROM CServerSocket - virtual void OnAccept(const CSocketAddress &); - - // OVERRIDES FROM CTurnManager - virtual bool NewTurnReady(); - virtual void NewTurn(); - virtual void QueueLocalCommand(CNetMessage *pMsg); - -// OVERRIDABLES - // Will only be called from the Network Thread, by the OnAccept handler - virtual CNetServerSession *CreateSession(CSocketInternal *pInt); - - // Ask the server if the session is allowed to start observing. - // - // Returns: - // true if the session should be made an observer - // false otherwise - virtual bool AllowObserver(CNetServerSession *pSession); - -public: - CNetServer(CGame *pGame, CGameAttributes *pGameAttribs); - virtual ~CNetServer(); - - static void GetDefaultListenAddress(CSocketAddress &address); - PS_RESULT Bind(const CSocketAddress &address); - - inline void SetPassword(const CStr& password) - { m_Password=password; } - - inline const CStrW& GetServerPlayerName() - { return m_ServerPlayerName; } - - inline ENetServerState GetServerState() - { return m_ServerState; } - - int StartGame(); - - void Broadcast(CNetMessage *); - - static void ScriptingInit(); -}; - -extern CNetServer *g_NetServer; - -#endif // INCLUDED_NETWORK_SERVER diff --git a/source/network/ServerSession.cpp b/source/network/ServerSession.cpp deleted file mode 100644 index 5e514db500..0000000000 --- a/source/network/ServerSession.cpp +++ /dev/null @@ -1,214 +0,0 @@ -#include "precompiled.h" - -#include "ServerSession.h" -#include "Server.h" -#include "ps/CLogger.h" -#include "ps/CConsole.h" - -CNetServerSession::CNetServerSession(CNetServer *pServer, CSocketInternal *pInt, - MessageHandler *pMsgHandler): - CNetSession(pInt, pMsgHandler), - m_pServer(pServer), - m_pPlayer(NULL), - m_pPlayerSlot(NULL), - m_IsObserver(false), - m_ID(-1), - m_ReadyForTurn(false) -{ -} - -CNetServerSession::~CNetServerSession() -{ -} - -void CNetServerSession::StartGame() -{ - if (m_pMessageHandler==PreGameHandler) - m_pMessageHandler=InGameHandler; -} - -#define UNHANDLED(_pMsg) return false; -#define HANDLED(_pMsg) delete _pMsg; return true; -#define TAKEN(_pMsg) return true; - -bool CNetServerSession::BaseHandler(CNetMessage *pMsg, CNetSession *pNetSession) -{ - CNetServerSession *pSession=(CNetServerSession *)pNetSession; - switch (pMsg->GetType()) - { - case NMT_ERROR: - { - CNetErrorMessage *msg=(CNetErrorMessage *)pMsg; - if (msg->m_State == SS_UNCONNECTED) - { - if (pSession->m_ID != -1) - pSession->m_pServer->RemoveSession(pSession); - delete pSession; - } - else // error, but not disconnected? something weird is up... - LOG(CLogger::Warning, LOG_CAT_NET, "CNetServerSession::BaseHandler(): NMT_ERROR: %s", msg->GetString().c_str()); - HANDLED(pMsg); - } - - default: - UNHANDLED(pMsg); - } -} - -bool CNetServerSession::HandshakeHandler(CNetMessage *pMsg, CNetSession *pNetSession) -{ - CNetServerSession *pSession=(CNetServerSession *)pNetSession; - LOG(CLogger::Normal, LOG_CAT_NET, "CNetServerSession::HandshakeHandler(): %s.", pMsg->GetString().c_str()); - switch (pMsg->GetType()) - { - case NMT_ClientHandshake: - { - CClientHandshake *msg=(CClientHandshake *)pMsg; - - if (msg->m_ProtocolVersion != PS_PROTOCOL_VERSION) - { - pSession->Push(new CCloseRequestMessage()); - BaseHandler(new CNetErrorMessage(PS_OK, SS_UNCONNECTED), pSession); - } - - CServerHandshakeResponse *retmsg=new CServerHandshakeResponse(); - retmsg->m_UseProtocolVersion=PS_PROTOCOL_VERSION; - retmsg->m_Flags=0; - retmsg->m_Message=pSession->m_pServer->m_WelcomeMessage; - pSession->Push(retmsg); - - pSession->m_pMessageHandler=AuthenticateHandler; - - HANDLED(pMsg); - } - - default: - return BaseHandler(pMsg, pNetSession); - } -} - -bool CNetServerSession::AuthenticateHandler(CNetMessage *pMsg, CNetSession *pNetSession) -{ - CNetServerSession *pSession=(CNetServerSession *)pNetSession; - CNetServer *pServer=pSession->m_pServer; - LOG(CLogger::Normal, LOG_CAT_NET, "CNetServerSession::AuthenticateHandler(): %s.", pMsg->GetString().c_str()); - if (pMsg->GetType() == NMT_Authenticate) - { - CAuthenticate *msg=(CAuthenticate *)pMsg; - - if (msg->m_Password == pSession->m_pServer->m_Password) - { - LOG(CLogger::Normal, LOG_CAT_NET, "CNetServerSession::AuthenticateHandler(): Login Successful"); - pSession->m_Name=msg->m_Name; - - pServer->AssignSessionID(pSession); - - CAuthenticationResult *msg=new CAuthenticationResult(); - msg->m_Code=NRC_OK; - msg->m_SessionID=pSession->m_ID; - msg->m_Message=L"Logged in"; - pSession->Push(msg); - - pServer->AddSession(pSession); - if (pServer->GetServerState() == NSS_PreGame) - { - pSession->m_pMessageHandler=PreGameHandler; - } - else // We're not in pre-game. The session becomes a chatter/observer here. - { - pSession->m_pMessageHandler=ObserverHandler; - } - } - else - { - LOG(CLogger::Warning, LOG_CAT_NET, "CNetServerSession::AuthenticateHandler(): Login Failed"); - CAuthenticationResult *msg=new CAuthenticationResult(); - msg->m_Code=NRC_PasswordInvalid; - msg->m_SessionID=0; - msg->m_Message=L"Invalid Password"; - pSession->Push(msg); - } - - HANDLED(pMsg); - } - return BaseHandler(pMsg, pNetSession); -} - -bool CNetServerSession::PreGameHandler(CNetMessage *pMsg, CNetSession *pNetSession) -{ - return ChatHandler(pMsg, pNetSession); -} - -bool CNetServerSession::ObserverHandler(CNetMessage *pMsg, CNetSession *pNetSession) -{ - printf("CNetServerSession::ObserverHandler(): %s.\n", pMsg->GetString().c_str()); - - // TODO Implement observers and chatter => observer promotion - /* - if (pMsg->GetType() == NMT_RequestObserve) - */ - - return ChatHandler(pMsg, pNetSession); -} - -bool CNetServerSession::ChatHandler(CNetMessage *pMsg, CNetSession *pNetSession) -{ - CNetServerSession *pSession=(CNetServerSession *)pNetSession; - if (pMsg->GetType() == NMT_ChatMessage) - { - CChatMessage *msg=(CChatMessage *)pMsg; - msg->m_Sender=pSession->m_Name; - g_Console->ReceivedChatMessage(pSession->GetName().c_str(), msg->m_Message.c_str()); - pSession->m_pServer->OnChat(msg->m_Sender, msg->m_Message); - pSession->m_pServer->Broadcast(msg); - - TAKEN(pMsg); - } - - return BaseHandler(pMsg, pNetSession); -} - -bool CNetServerSession::InGameHandler(CNetMessage *pMsg, CNetSession *pNetSession) -{ - CNetServerSession *pSession=(CNetServerSession *)pNetSession; - if (pMsg->GetType() != NMT_EndCommandBatch) - LOG(CLogger::Normal, LOG_CAT_NET, "CNetServerSession::InGameHandler(): %s.", pMsg->GetString().c_str()); - - if (BaseHandler(pMsg, pNetSession)) - return true; - - if (ChatHandler(pMsg, pNetSession)) - return true; - - if (pMsg->GetType() >= NMT_COMMAND_FIRST && pMsg->GetType() < NMT_COMMAND_LAST) - { - // All Command Messages (i.e. simulation turn synchronized messages) - //pSession->m_pPlayer->ValidateCommand(pMsg); - pSession->m_pServer->QueueIncomingCommand(pMsg); - TAKEN(pMsg); - } - - switch (pMsg->GetType()) - { - case NMT_EndCommandBatch: - pSession->m_ReadyForTurn = true; - HANDLED(pMsg); - - default: - UNHANDLED(pMsg); - } -} - -void CNetServerSession::ScriptingInit() -{ - AddProperty( L"id", &CNetServerSession::m_ID ); - AddProperty( L"name", (CStrW CNetServerSession::*)&CNetServerSession::m_Name ); - AddMethod("close", 0); - - CJSObject::ScriptingInit("NetSession"); -} - -bool CNetServerSession::JSI_Close(JSContext* UNUSED(cx), uintN UNUSED(argc), jsval* UNUSED(argv)) -{ - return false; -} diff --git a/source/network/ServerSession.h b/source/network/ServerSession.h deleted file mode 100644 index a4e6f60cfb..0000000000 --- a/source/network/ServerSession.h +++ /dev/null @@ -1,74 +0,0 @@ -/* - CNetServerSession - the server's representation of a connected client - - DESCRIPTION: - -*/ - -#ifndef INCLUDED_NETWORK_SERVERSESSION -#define INCLUDED_NETWORK_SERVERSESSION - -#include "Session.h" -#include "scripting/ScriptableObject.h" - -class CNetServer; -class CPlayer; -class CPlayerSlot; - -class CNetServerSession: public CNetSession, public CJSObject -{ - CNetServer *m_pServer; - CPlayer *m_pPlayer; - CPlayerSlot *m_pPlayerSlot; - bool m_IsObserver; - int m_ID; - bool m_ReadyForTurn; // Is the last turn acknowledged? - - // JS INTERFACE - bool JSI_Close(JSContext *cx, uintN argc, jsval *argv); - -protected: - friend class CNetServer; - - inline void SetPlayer(CPlayer *pPlayer) - { m_pPlayer=pPlayer; } - - inline void SetPlayerSlot(CPlayerSlot *pPlayerSlot) - { m_pPlayerSlot=pPlayerSlot; } - - inline void SetID(int id) - { m_ID=id; } - -public: - CNetServerSession(CNetServer *pServer, CSocketInternal *pInt, MessageHandler *pMsgHandler=HandshakeHandler); - virtual ~CNetServerSession(); - - static void ScriptingInit(); - - inline bool IsObserver() - { return m_IsObserver; } - inline CPlayer *GetPlayer() - { return m_pPlayer; } - inline CPlayerSlot *GetPlayerSlot() - { return m_pPlayerSlot; } - inline int GetID() - { return m_ID; } - inline bool IsReadyForTurn() - { return m_ReadyForTurn; } - inline void SetReadyForTurn(bool value) - { m_ReadyForTurn = value; } - - // Called by server when starting the game, after sending NMT_StartGame to - // all connected clients. - void StartGame(); - - static MessageHandler BaseHandler; - static MessageHandler HandshakeHandler; - static MessageHandler AuthenticateHandler; - static MessageHandler PreGameHandler; - static MessageHandler ObserverHandler; - static MessageHandler ChatHandler; - static MessageHandler InGameHandler; -}; - -#endif diff --git a/source/network/Session.cpp b/source/network/Session.cpp deleted file mode 100644 index 50769fb724..0000000000 --- a/source/network/Session.cpp +++ /dev/null @@ -1,8 +0,0 @@ -#include "precompiled.h" - -#include "Session.h" - -CNetSession::~CNetSession() -{ - g_SessionManager.Deregister(this); -} diff --git a/source/network/Session.h b/source/network/Session.h deleted file mode 100644 index 7b94ea72ed..0000000000 --- a/source/network/Session.h +++ /dev/null @@ -1,71 +0,0 @@ -#ifndef INCLUDED_NETWORK_SESSION -#define INCLUDED_NETWORK_SESSION - -#include "Network.h" -#include "SessionManager.h" - -/* - CNetSession - - DESCRIPTION: - The representation of a network session (on both the client and server - side) -*/ -class CNetSession: public CMessageSocket -{ - friend class CNetClient; -protected: - /* - The MessageHandler callback follows the contract of HandleMessage, see - the documentation for that function for more information. - */ - typedef bool (MessageHandler)(CNetMessage *, CNetSession *); - MessageHandler *m_pMessageHandler; - - CStrW m_Name; - -public: - inline CNetSession(MessageHandler *pMsgHandler=NULL): - m_pMessageHandler(pMsgHandler) - { - g_SessionManager.Register(this); - } - inline CNetSession(CSocketInternal *pInt, MessageHandler *pMsgHandler=NULL): - CMessageSocket(pInt), - m_pMessageHandler(pMsgHandler) - { - g_SessionManager.Register(this); - } - - virtual ~CNetSession(); - - /* - Handle an incoming message. - - THREADS: - When used with the session manager, this method will only be - called from the main thread. - - ARGUMENTS: - pMsg The incoming message - pSocket The socket the message came from - - RETURNS: - TRUE if the message was handled by this class or another protocol - that this class knows of. FALSE if the message was not handled. - */ - inline bool HandleMessage(CNetMessage *pMsg) - { - if (m_pMessageHandler) - return (m_pMessageHandler)(pMsg, this); - else - return false; - } - - inline const CStrW& GetName() - { - return m_Name; - } -}; - -#endif //INCLUDED_NETWORK_SESSION diff --git a/source/network/SessionManager.cpp b/source/network/SessionManager.cpp deleted file mode 100644 index e0eb08dc0c..0000000000 --- a/source/network/SessionManager.cpp +++ /dev/null @@ -1,86 +0,0 @@ -#include "precompiled.h" - -#include "Session.h" -#include "Network.h" -#include "ps/CLogger.h" - -using namespace std; - -void CSessionManager::Register(CNetSession *pSession) -{ - CScopeLock scopeLock(m_Mutex); - SessionMap::iterator it; - - // The total number of references to this session should be 1 and only 1 - // Either there's one in m_Sessions or there's one in m_AddQueue. There - // shall be no references in the remove queue. - // m_Sessions is Read-Only - - if ((it = m_RemoveQueue.find(pSession)) != m_RemoveQueue.end()) - m_RemoveQueue.erase(it); - if (m_Sessions.find(pSession) == m_Sessions.end()) - m_AddQueue[pSession]=pSession; -} - -void CSessionManager::Deregister(CNetSession *pSession) -{ - CScopeLock scopeLock(m_Mutex); - SessionMap::iterator it; - - // The total number of references to this session should be 2 or 0: - // One in m_Sessions and one in the remove queue or 0 in both. None in - // m_AddQueue. - // m_Sessions is Read-Only - - if ((it = m_AddQueue.find(pSession)) != m_AddQueue.end()) - m_AddQueue.erase(it); - if (m_Sessions.find(pSession) != m_Sessions.end()) - m_RemoveQueue.insert(make_pair(pSession, pSession)); -} - -void CSessionManager::Poll() -{ - m_Mutex.Lock(); - SessionMap::iterator it; - - // De/Register should've made sure that it doesn't matter which order we - // process the two queues. - for (it=m_AddQueue.begin();it != m_AddQueue.end();++it) - { - m_Sessions.insert(*it); - } - m_AddQueue.clear(); - for (it=m_RemoveQueue.begin();it != m_RemoveQueue.end();++it) - { - m_Sessions.erase(it->second); - } - m_RemoveQueue.clear(); - - it=m_Sessions.begin(); - while (it != m_Sessions.end()) - { - CNetMessage *pMsg; - - // Extraneous? well, sessions are perfectly able to delete other - // sessions (or cause them to be deleted) in their message handler - if (m_RemoveQueue.find(it->second) != m_RemoveQueue.end()) - continue; - - while ((pMsg = it->second->TryPop()) != NULL) - { - CNetSession *pSess=it->second; - m_Mutex.Unlock(); - if (!pSess->HandleMessage(pMsg)) - { - LOG(CLogger::Warning, LOG_CAT_NET, "CSessionManager::Poll(): Unhandled message %s.", pMsg->GetString().c_str()); - delete pMsg; - } - m_Mutex.Lock(); - - if (m_RemoveQueue.find(it->second) != m_RemoveQueue.end()) - break; - } - ++it; - } - m_Mutex.Unlock(); -} diff --git a/source/network/SessionManager.h b/source/network/SessionManager.h deleted file mode 100644 index 2bb7e1b5ef..0000000000 --- a/source/network/SessionManager.h +++ /dev/null @@ -1,55 +0,0 @@ -#ifndef INCLUDED_NETWORK_SESSIONMANAGER -#define INCLUDED_NETWORK_SESSIONMANAGER - -#include "ps/ThreadUtil.h" - -class CNetSession; - -/* - NAME: CSessionManager - - The central nexus of network message handling. Contains the entry point - called from the main thread. - CNetSession's are registered and when the Poll method finds that the session - has a pending message, the session object's HandleMessage method is called - to handle it. Any unhandled messages (HandleMessage returns false) are - logged to the system log. -*/ -class CSessionManager: public Singleton -{ - typedef std::map SessionMap; - - SessionMap m_Sessions; - SessionMap m_AddQueue; - SessionMap m_RemoveQueue; - CMutex m_Mutex; - -public: - - /* - Poll all registered sessions and pass all messages to their - message handlers. - - THREADS: Call from Main Thread only - */ - void Poll(); - - /* - Register a network session with the session manager. Future calls to - Poll() will poll this session's socket and pass any messages to - its message handler function. - - THREADS: Safe from all threads - */ - void Register(CNetSession *pSession); - - /* - Delete the protocol context associated with the specified socket. - - THREADS: Safe from all threads - */ - void Deregister(CNetSession *pSession); -}; -#define g_SessionManager (CSessionManager::GetSingleton()) - -#endif diff --git a/source/network/fsm.cpp b/source/network/fsm.cpp new file mode 100644 index 0000000000..4c3db8be25 --- /dev/null +++ b/source/network/fsm.cpp @@ -0,0 +1,445 @@ +/** + *----------------------------------------------------------------------------- + * FILE : fsm.cpp + * PROJECT : 0 A.D. + * DESCRIPTION : Finite state machine class implementation + *----------------------------------------------------------------------------- + */ + +// INCLUDES +#include "precompiled.h" +#include "fsm.h" + +// DECLARATIONS + +//----------------------------------------------------------------------------- +// Name: CFsmEvent() +// Desc: Constructor +//----------------------------------------------------------------------------- +CFsmEvent::CFsmEvent( unsigned int type ) +{ + m_Type = type; + m_Param = NULL; +} + +//----------------------------------------------------------------------------- +// Name; ~CFsmEvent() +// Desc: Destructor +//----------------------------------------------------------------------------- +CFsmEvent::~CFsmEvent( void ) +{ + m_Param = NULL; +} + +//----------------------------------------------------------------------------- +// Name: SetParamRef() +// Desc: Sets the parameter for the event +//----------------------------------------------------------------------------- +void CFsmEvent::SetParamRef( void* pParam ) +{ + m_Param = pParam; +} + +//----------------------------------------------------------------------------- +// Name: CFsmTransition() +// Desc: Constructor +//----------------------------------------------------------------------------- +CFsmTransition::CFsmTransition( unsigned int state ) +{ + m_CurrState = state; +} + +//----------------------------------------------------------------------------- +// Name: ~CFsmTransition() +// Desc: Destructor +//----------------------------------------------------------------------------- +CFsmTransition::~CFsmTransition( void ) +{ + m_Actions.clear(); + m_Conditions.clear(); +} + +//----------------------------------------------------------------------------- +// Name: RegisterAction() +// Desc: Adds action that will be executed when the transition will occur +//----------------------------------------------------------------------------- +void CFsmTransition::RegisterAction( void* pAction, void* pContext ) +{ + CallbackFunction callback; + + // Add action at the end of actions list + callback.pFunction = pAction; + callback.pContext = pContext; + + m_Actions.push_back( callback ); +} + +//----------------------------------------------------------------------------- +// Name: AddCondition() +// Desc: Adds condition which will be evaluated when the transition will occurs +//----------------------------------------------------------------------------- +void CFsmTransition::RegisterCondition( void* pCondition, void* pContext ) +{ + CallbackFunction callback; + + // Add condition at the end of conditions list + callback.pFunction = pCondition; + callback.pContext = pContext; + + m_Conditions.push_back( callback ); +} + +//----------------------------------------------------------------------------- +// Name: SetEvent() +// Desc: Set event for which transition will occur +//----------------------------------------------------------------------------- +void CFsmTransition::SetEvent( CFsmEvent* pEvent ) +{ + m_Event = pEvent; +} + +//----------------------------------------------------------------------------- +// Name: SetNextState() +// Desc: Set next state the transition will switch the system to +//----------------------------------------------------------------------------- +void CFsmTransition::SetNextState( unsigned int nextState ) +{ + m_NextState = nextState; +} + +//----------------------------------------------------------------------------- +// Name: ApplyConditions() +// Desc: Evaluate conditions for the transition +// Note: If there are no conditions, assume true +//----------------------------------------------------------------------------- +bool CFsmTransition::ApplyConditions( void ) const +{ + bool eval = true; + + CallbackList::const_iterator it = m_Conditions.begin(); + for( ; it != m_Conditions.end(); it++ ) + { + if ( it->pFunction ) + { + // Evaluate condition + CONDITION Condition = ( CONDITION )it->pFunction; + eval &= Condition( it->pContext ); + } + } + + return eval; +} + +//----------------------------------------------------------------------------- +// Name: RunActions() +// Desc: Execur actions for the transition +// Note: If there are no actions, assume true +//----------------------------------------------------------------------------- +bool CFsmTransition::RunActions( void ) const +{ + bool result = true; + + CallbackList::const_iterator it = m_Actions.begin(); + for( ; it != m_Actions.end(); it++ ) + { + if ( it->pFunction ) + { + // Run action + ACTION Action = ( ACTION )it->pFunction; + result &= Action( it->pContext, m_Event ); + } + } + + return result; +} + +//----------------------------------------------------------------------------- +// Name: CFsm() +// Desc: Constructor +//----------------------------------------------------------------------------- +CFsm::CFsm( void ) +{ + m_Done = false; + m_FirstState = ( unsigned int )FSM_INVALID_STATE; + m_CurrState = ( unsigned int )FSM_INVALID_STATE; +} + +//----------------------------------------------------------------------------- +// Name: ~CFsm() +// Desc: Destructor +//----------------------------------------------------------------------------- +CFsm::~CFsm( void ) +{ + Shutdown(); +} + +//----------------------------------------------------------------------------- +// Name: Setup() +// Desc: Setup events, actions and state transitions +//----------------------------------------------------------------------------- +void CFsm::Setup( void ) +{ + // Does nothing by default +} + +//----------------------------------------------------------------------------- +// Name: Reset() +// Desc: Shuts down the state machine and releases any resources +//----------------------------------------------------------------------------- +void CFsm::Shutdown( void ) +{ + // Release transitions + TransitionList::iterator itTransition = m_Transitions.begin(); + for ( ; itTransition < m_Transitions.end(); itTransition++ ) + { + CFsmTransition* pCurrTransition = *itTransition; + if ( !pCurrTransition ) continue; + + delete pCurrTransition; + } + + // Release events + EventMap::iterator itEvent = m_Events.begin(); + for( ; itEvent != m_Events.end(); itEvent++ ) + { + CFsmEvent* pCurrEvent = itEvent->second; + if ( !pCurrEvent ) continue; + + delete pCurrEvent; + } + + m_States.clear(); + m_Events.clear(); + m_Transitions.clear(); + + m_Done = false; + m_FirstState = ( unsigned int )FSM_INVALID_STATE; + m_CurrState = ( unsigned int )FSM_INVALID_STATE; +} + +//----------------------------------------------------------------------------- +// Name: AddState() +// Desc: Adds the specified state to the internal list of states +// Note: If a state with the specified ID exists, the state is not added +//----------------------------------------------------------------------------- +void CFsm::AddState( unsigned int state ) +{ + m_States.insert( state ); +} + +//----------------------------------------------------------------------------- +// Name: AddEvent() +// Desc: Adds the specified event to the internal list of events +// Note: If an eveny with the specified ID exists, the event is not added +//----------------------------------------------------------------------------- +CFsmEvent* CFsm::AddEvent( unsigned int eventType ) +{ + CFsmEvent* pEvent = NULL; + + // Lookup event by type + EventMap::iterator it = m_Events.find( eventType ); + if ( it != m_Events.end() ) + { + pEvent = it->second; + } + else + { + pEvent = new CFsmEvent( eventType ); + if ( !pEvent ) return NULL; + + // Store new event into internal map + m_Events[ eventType ] = pEvent; + } + + return pEvent; +} + +//----------------------------------------------------------------------------- +// Name: AddTransition() +// Desc: Adds a new transistion to the state machine +//----------------------------------------------------------------------------- +CFsmTransition* CFsm::AddTransition( + unsigned int state, + unsigned int eventType, + unsigned int nextState ) +{ + // Make sure we store the current state + AddState( state ); + + // Make sure we store the next state + AddState( nextState ); + + // Make sure we store the event + CFsmEvent* pEvent = AddEvent( eventType ); + if ( !pEvent ) return NULL; + + // Create new transition + CFsmTransition* pNewTransition = new CFsmTransition( state ); + if ( !pNewTransition ) return NULL; + + // Setup new transition + pNewTransition->SetEvent( pEvent ); + pNewTransition->SetNextState( nextState ); + + // Store new transition + m_Transitions.push_back( pNewTransition ); + + return pNewTransition; +} + +//----------------------------------------------------------------------------- +// Name: AddTransition() +// Desc: Adds a new transistion to the state machine +//----------------------------------------------------------------------------- +CFsmTransition* CFsm::AddTransition( + unsigned int state, + unsigned int eventType, + unsigned int nextState, + void* pAction, + void* pContext ) +{ + CFsmTransition* pTransition = AddTransition( state, eventType, nextState ); + if ( !pTransition ) return NULL; + + // If action specified, register it + if ( pAction ) + pTransition->RegisterAction( pAction, pContext ); + + return pTransition; +} + +//----------------------------------------------------------------------------- +// Name: GetTransition() +// Desc: Lookup transition given the state, event and next state to transition +//----------------------------------------------------------------------------- +CFsmTransition* CFsm::GetTransition( + unsigned int state, + unsigned int eventType ) const +{ + // Valid state? + if ( !IsValidState( state ) ) return NULL; + + // Valid event? + if ( !IsValidEvent( eventType ) ) return NULL; + + // Loop through the list of transitions + TransitionList::const_iterator it = m_Transitions.begin(); + for ( ; it != m_Transitions.end(); it++ ) + { + CFsmTransition* pCurrTransition = *it; + if ( !pCurrTransition ) continue; + + CFsmEvent* pCurrEvent = pCurrTransition->GetEvent(); + if ( !pCurrEvent ) continue; + + // Is it our transition? + if ( pCurrTransition->GetCurrState() == state && + pCurrEvent->GetType() == eventType ) + { + return pCurrTransition; + } + } + + // No transition found + return NULL; +} + +//----------------------------------------------------------------------------- +// Name: SetFirstState() +// Desc: Set initial state for FSM +//----------------------------------------------------------------------------- +void CFsm::SetFirstState( unsigned int firstState ) +{ + m_FirstState = firstState; +} + +//----------------------------------------------------------------------------- +// Name: SetCurrState() +// Desc: Set current state and update last state to current state +//----------------------------------------------------------------------------- +void CFsm::SetCurrState( unsigned int state ) +{ + m_CurrState = state; +} + +//----------------------------------------------------------------------------- +// Name: IsFirstTime() +// Desc: Verifies if the state machine has been already updated +//----------------------------------------------------------------------------- +bool CFsm::IsFirstTime( void ) const +{ + return ( m_CurrState == FSM_INVALID_STATE ); +} + +//----------------------------------------------------------------------------- +// Name: Update() +// Desc: Updates state machine and retrieves next state +//----------------------------------------------------------------------------- +bool CFsm::Update( unsigned int eventType, void* pEventParam ) +{ + // Valid event? + if ( !IsValidEvent( eventType ) ) + return false; + + // First time update? + if ( IsFirstTime() ) + m_CurrState = m_FirstState; + + // Lookup transition + CFsmTransition* pTransition = GetTransition( m_CurrState, eventType ); + if ( !pTransition ) return false; + + // Setup event parameter + EventMap::iterator it = m_Events.find( eventType ); + if ( it != m_Events.end() ) + { + CFsmEvent* pEvent = it->second; + if ( pEvent ) pEvent->SetParamRef( pEventParam ); + } + + // Valid transition? + if ( !pTransition->ApplyConditions() ) return false; + + // Run transition actions + if ( !pTransition->RunActions() ) return false; + + // Switch state + SetCurrState( pTransition->GetNextState() ); + + return true; +} + +//----------------------------------------------------------------------------- +// Name: IsDone() +// Desc: Tests whether the state machine has finished its work +// Note: This is state machine specific +//----------------------------------------------------------------------------- +bool CFsm::IsDone( void ) const +{ + // By default the internal flag m_Done is tested + return m_Done; +} + +//----------------------------------------------------------------------------- +// Name: IsValidState() +// Desc: Verifies whether the specified state is managed by FSM +//----------------------------------------------------------------------------- +bool CFsm::IsValidState( unsigned int state ) const +{ + StateSet::const_iterator it = m_States.find( state ); + if ( it == m_States.end() ) return false; + + return true; +} + +//----------------------------------------------------------------------------- +// Name: IsValidEvent() +// Desc: Verifies whether the specified event is managed by FSM +//----------------------------------------------------------------------------- +bool CFsm::IsValidEvent( unsigned int eventType ) const +{ + EventMap::const_iterator it = m_Events.find( eventType ); + if ( it == m_Events.end() ) return false; + + return true; +} diff --git a/source/network/fsm.h b/source/network/fsm.h new file mode 100644 index 0000000000..f52da49a88 --- /dev/null +++ b/source/network/fsm.h @@ -0,0 +1,193 @@ +/** + *----------------------------------------------------------------------------- + * FILE : fsm.h + * PROJECT : 0 A.D. + * DESCRIPTION : Finite state machine class definitions + *----------------------------------------------------------------------------- + */ + +#ifndef FSM_H +#define FSM_H + +// INCLUDES +#include +#include + +// DECLARATIONS +#define FSM_INVALID_STATE ( unsigned int )( ~0 ) + +class CFsmEvent; +class CFsmTransition; +class CFsm; + +typedef bool ( *CONDITION ) ( void* pContext ); +typedef bool ( *ACTION ) ( void* pContext, const CFsmEvent* pEvent ); + +typedef struct +{ + void* pFunction; + void* pContext; + +} CallbackFunction; + +typedef std::set< unsigned int > StateSet; +typedef std::map< unsigned int, CFsmEvent* > EventMap; +typedef std::vector< CFsmTransition* > TransitionList; +typedef std::vector< CallbackFunction > CallbackList; + +/* + CLASS : CFsmEvent + DESCRIPTION : CFsmEvent class represents a signal in the state machine + that a change has occured. + NOTES : The CFsmEvent objects are under the control of CFsm so + they are created and deleted via CFsm. +*/ + +class CFsmEvent +{ +public: + + CFsmEvent( unsigned int type ); + ~CFsmEvent( void ); + + unsigned int GetType ( void ) const { return m_Type; } + void* GetParamRef ( void ) { return m_Param; } + void SetParamRef ( void* pParam ); + +protected: + +private: + + // Not implemented + CFsmEvent( const CFsmEvent& ); + CFsmEvent& operator=( const CFsmEvent& ); + + unsigned int m_Type; // Event type + void* m_Param; // Event paramater +}; + + +/* + CLASS : CFsmTransition + DESCRIPTION : The CFsmTransition class is an association of event, condition, + action and next state. + NOTES : +*/ + +class CFsmTransition +{ +public: + + CFsmTransition( unsigned int state ); + ~CFsmTransition( void ); + + void RegisterAction ( + void* pAction, + void* pContext ); + void RegisterCondition ( + void* pCondition, + void* pContext ); + void SetEvent ( CFsmEvent* pEvent ); + CFsmEvent* GetEvent ( void ) const { return m_Event; } + void SetNextState ( unsigned int nextState ); + unsigned int GetNextState ( void ) const { return m_NextState; } + unsigned int GetCurrState ( void ) const { return m_CurrState; } + const CallbackList& GetActions ( void ) const { return m_Actions; } + const CallbackList& GetConditions ( void ) const { return m_Conditions; } + bool ApplyConditions ( void ) const; + bool RunActions ( void ) const; + +protected: + +private: + + // Not implemented + CFsmTransition( const CFsmTransition& ); + CFsmTransition& operator=( const CFsmTransition& ); + + unsigned int m_CurrState; // Current state + unsigned int m_NextState; // Next state + CFsmEvent* m_Event; // Transition event + CallbackList m_Actions; // List of actions for transition + CallbackList m_Conditions; // List of conditions for transition +}; + +/* + CLASS : CFsm + DESCRIPTION : CFsm manages states, events, actions and transitions + between states. It provides an interface for advertising + events and track the current state. The implementation is + a Mealy state machine, so the system respond to events + and execute some action. + NOTES : A Mealy state machine has behaviour associated with state + transitions; Mealy machines are event driven where an + event triggers a state transition +*/ + +class CFsm +{ +public: + + CFsm( void ); + ~CFsm( void ); + + /** + * Constructs the state machine. This method must be overriden so that + * connections are constructed for the particular state machine implemented + * + */ + virtual void Setup( void ); + + /** + * Clear event, action and condition lists and reset state machine + * + */ + void Shutdown( void ); + + void AddState ( unsigned int state ); + CFsmEvent* AddEvent ( unsigned int eventType ); + CFsmTransition* AddTransition ( + unsigned int state, + unsigned int eventType, + unsigned int nextState ); + CFsmTransition* AddTransition ( + unsigned int state, + unsigned int eventType, + unsigned int nextState, + void* pAction, + void* pContext ); + CFsmTransition* GetTransition ( + unsigned int state, + unsigned int eventType ) const; + CFsmTransition* GetEventTransition ( unsigned int eventType ) const; + void SetFirstState ( unsigned int firstState ); + unsigned int GetCurrState ( void ) const { return m_CurrState; } + const StateSet& GetStates ( void ) const { return m_States; } + const EventMap& GetEvents ( void ) const { return m_Events; } + const TransitionList& GetTransitions ( void ) const { return m_Transitions; } + bool Update ( unsigned int eventType, void* pEventData ); + bool IsValidState ( unsigned int state ) const; + bool IsValidEvent ( unsigned int eventType ) const; + virtual bool IsDone ( void ) const; + +protected: + +private: + + // Not implemented + CFsm( const CFsm& ); + CFsm& operator=( const CFsm& ); + + void SetCurrState ( unsigned int state ); + bool IsFirstTime ( void ) const; + + bool m_Done; // FSM work is done + unsigned int m_FirstState; // Initial state + unsigned int m_CurrState; // Current state + StateSet m_States; // List of states + EventMap m_Events; // List of events + TransitionList m_Transitions; // State transitions +}; + +#endif // FSM_H +