forked from 0ad/0ad
janwas
c0ed950657
this snowballed into a massive search+destroy of the hodgepodge of mostly equivalent types we had in use (int, uint, unsigned, unsigned int, i32, u32, ulong, uintN). it is more efficient to use 64-bit types in 64-bit mode, so the preferred default is size_t (for anything remotely resembling a size or index). tile coordinates are ssize_t to allow more efficient conversion to/from floating point. flags are int because we almost never need more than 15 distinct bits, bit test/set is not slower and int is fastest to type. finally, some data that is pretty much directly passed to OpenGL is now typed accordingly. after several hours, the code now requires fewer casts and less guesswork. other changes: - unit and player IDs now have an "invalid id" constant in the respective class to avoid casting and -1 - fix some endian/64-bit bugs in the map (un)packing. added a convenience function to write/read a size_t. - ia32: change CPUID interface to allow passing in ecx (required for cache topology detection, which I need at work). remove some unneeded functions from asm, replace with intrinsics where possible. This was SVN commit r5942.
484 lines
12 KiB
C++
484 lines
12 KiB
C++
#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<CServerSession>::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<bool, &CNetClient::JSI_BeginConnect>("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<CStrW>(L"playerName", &CNetClient::m_Name);
|
|
AddProperty(L"sessionId", &CNetClient::m_SessionID);
|
|
|
|
AddProperty(L"sessions", &CNetClient::m_JSI_ServerSessions);
|
|
CJSMap<SessionMap>::ScriptingInit("NetClient_SessionMap");
|
|
CJSObject<CNetClient>::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<int>(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::<X>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;i<msg->m_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;i<msg->m_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;i<msg->m_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);
|
|
}
|