#include "precompiled.h" #include #include "Network/Server.h" #include "Network/ServerSession.h" #include "Network/Network.h" #include "Network/JSEvents.h" #include "scripting/ScriptableObject.h" #include "Game.h" #include "Player.h" #include "CLogger.h" #include "CConsole.h" #define LOG_CAT_NET "net" extern CConsole *g_Console; 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(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) { ONCE( ScriptingInit(); ); 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 - less command batch spam that way for (int i=0;i<3;i++) CTurnManager::SetTurnLength(i, 3000); 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(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(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(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=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 (uint 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(CStrW name, 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(CStrW name, 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=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=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() { Broadcast(new CStartGame()); if (m_pGame->StartGame(m_pGameAttributes) != PSRETURN_OK) return -1; else { CTurnManager::Initialize(m_pGameAttributes->GetSlotCount()); for (uint 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; } // 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); } void CNetServer::NewTurn() { RecordBatch(2); RotateBatches(); ClearBatch(2); IterateBatch(1, CSimulation::GetMessageMask, m_pGame->GetSimulation()); SendBatch(1); //IterateBatch(1, SendToObservers, this); } void CNetServer::QueueLocalCommand(CNetMessage *pMsg) { QueueIncomingCommand(pMsg); } void CNetServer::QueueIncomingCommand(CNetMessage *pMsg) { LOG(NORMAL, LOG_CAT_NET, "CNetServer::QueueIncomingCommand(): %s.", pMsg->GetString().c_str()); QueueMessage(2, pMsg); } void CNetServer::OnChat(CStrW from, 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); } }