# Basic experimental multiplayer integration with new simulation system.

Add new turn manager that should be more correct and potentially more
efficient.
Remove evil CNetServer/CNetClient multiple inheritance of CTurnManager.
Add multiplayer autostart.
Various minor cleanups.

This was SVN commit r7551.
This commit is contained in:
Ykkrosh 2010-05-20 00:59:01 +00:00
parent 8c1a3f7c8a
commit c684c211a2
41 changed files with 1330 additions and 463 deletions

View File

@ -143,7 +143,6 @@ Watch for updates or get involved in the development: http://wildfiregames.com/0
sprite_over="pgMultiPlayerOver"
sprite_disabled="pgMultiPlayerDisabled"
tooltip="Tired of playing with yourself? Fight against one or more human players in a multiplayer game."
enabled="false"
>
<action on="Press"><![CDATA[
// Open Multiplayer connection window.

View File

@ -76,6 +76,11 @@ CGUIManager::~CGUIManager()
{
}
bool CGUIManager::HasPages()
{
return !m_PageStack.empty();
}
void CGUIManager::SwitchPage(const CStrW& pageName, CScriptVal initData)
{
m_PageStack.clear();

View File

@ -49,16 +49,27 @@ public:
ScriptInterface& GetScriptInterface() { return m_ScriptInterface; }
// Load a new GUI page and make it active. All current pages will be destroyed.
/**
* Returns whether there are any current pages.
*/
bool HasPages();
/**
* Load a new GUI page and make it active. All current pages will be destroyed.
*/
void SwitchPage(const CStrW& name, CScriptVal initData);
// Load a new GUI page and make it active. All current pages will be retained,
// and will still be drawn and receive tick events, but will not receive
// user inputs.
/**
* Load a new GUI page and make it active. All current pages will be retained,
* and will still be drawn and receive tick events, but will not receive
* user inputs.
*/
void PushPage(const CStrW& name, CScriptVal initData);
// Unload the currently active GUI page, and make the previous page active.
// (There must be at least two pages when you call this.)
/**
* Unload the currently active GUI page, and make the previous page active.
* (There must be at least two pages when you call this.)
*/
void PopPage();
/**

View File

@ -103,16 +103,9 @@ void PostNetworkCommand(void* cbdata, CScriptVal cmd)
if (queue.null())
return;
int player = -1;
if (g_Game && g_Game->GetLocalPlayer())
player = g_Game->GetLocalPlayer()->GetPlayerID();
jsval cmd2 = sim->GetScriptInterface().CloneValueFromOtherContext(guiManager->GetScriptInterface(), cmd.get());
queue->PushClientCommand(player, cmd2);
// TODO: This shouldn't call Push, it should send the message to the network layer
// (which should propagate it across the network and eventually call Push on the
// appropriate turn)
queue->PostNetworkCommand(cmd2);
}
std::vector<entity_id_t> PickEntitiesAtPoint(void* UNUSED(cbdata), int x, int y)

View File

@ -257,10 +257,15 @@ static void Frame()
PROFILE_START("input");
MICROLOG(L"input");
PumpEvents();
//g_SessionManager.Poll();
if ( CNetHost::IsInitialised() )
CNetHost::GetSingleton().Poll();
PROFILE_END("input");
//g_SessionManager.Poll();
PROFILE_START("network poll");
if (g_NetServer)
g_NetServer->Poll();
if (g_NetClient)
g_NetClient->Poll();
PROFILE_END("network poll");
ogl_WarnIfError();

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2009 Wildfire Games.
/* Copyright (C) 2010 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -218,14 +218,6 @@ u8 *_nm::Serialize(u8 *buffer) const \
#define START_NMT_CLASS(_nm, _tp) \
START_NMT_CLASS_DERIVED(CNetMessage, _nm, _tp)
#define START_NMT_CLASS_DERIVED(_base, _nm, _tp) \
CNetMessage *Deserialize##_nm(const u8 *buffer, size_t length) \
{ \
_nm *ret=new _nm(); \
if (ret->Deserialize(buffer, buffer+length)) \
return ret; \
else \
{ delete ret; return NULL; } \
} \
const u8 *_nm::Deserialize(const u8 *pos, const u8 *end) \
{ \
pos=_base::Deserialize(pos, end); \
@ -262,46 +254,7 @@ const u8 *_nm::Deserialize(const u8 *pos, const u8 *end) \
#undef NMT_CREATOR_PASS_DESERIALIZE
/*************************************************************************/
// Pass 5, Deserializer Registration
#define NMT_CREATOR_PASS_REGISTRATION
//#define START_NMTS() SNetMessageDeserializerRegistration g_DeserializerRegistrations[] = {
//#define END_NMTS() { NMT_INVALID, NULL } };
#define START_NMTS()
#define END_NMTS()
#define START_NMT_CLASS(_nm, _tp) \
START_NMT_CLASS_DERIVED(CNetMessage, _nm, _tp)
/*
#define START_NMT_CLASS_DERIVED(_base, _nm, _tp) \
{ _tp, Deserialize##_nm },
*/
#define START_NMT_CLASS_DERIVED(_base, _nm, _tp)
#define NMT_START_ARRAY(_nm)
#define NMT_END_ARRAY()
#define NMT_FIELD_INT(_nm, _hosttp, _netsz)
#define NMT_FIELD(_tp, _nm)
#define END_NMT_CLASS()
#include "NMTCreator.h"
#undef NMT_CREATOR_PASS_REGISTRATION
/*************************************************************************/
// Pass 6, String Representation
#define _T(s) s
// PT: I'm not sure whether this is correct - it appears that this code
// relied on CStr.h leaving _T defined in ASCII mode, so I'm not sure
// why the macro is actually used at all...
// Pass 5, String Representation
#define START_NMTS()
#define END_NMTS()
@ -309,8 +262,8 @@ const u8 *_nm::Deserialize(const u8 *pos, const u8 *end) \
#define START_NMT_CLASS(_nm, _tp) \
CStr _nm::ToString() const \
{ \
CStr ret=#_nm _T(" { "); \
return ret + ToStringRaw() + _T(" }"); \
CStr ret=#_nm " { "; \
return ret + ToStringRaw() + " }"; \
} \
CStr _nm::ToStringRaw() const \
{ \
@ -321,39 +274,39 @@ CStr _nm::ToStringRaw() const \
#define START_NMT_CLASS_DERIVED(_base, _nm, _tp) \
CStr _nm::ToString() const \
{ \
CStr ret=#_nm _T(" { "); \
return ret + ToStringRaw() + _T(" }"); \
CStr ret=#_nm " { "; \
return ret + ToStringRaw() + " }"; \
} \
CStr _nm::ToStringRaw() const \
{ \
CStr ret=_base::ToStringRaw() + _T(", "); \
CStr ret=_base::ToStringRaw() + ", "; \
const _nm *thiz=this;\
UNUSED2(thiz); // preempt any "unused" warning
#define NMT_START_ARRAY(_nm) \
ret+=#_nm _T(": { "); \
ret+=#_nm ": { "; \
std::vector < ARRAY_STRUCT_PREFIX(_nm) >::const_iterator it=_nm.begin(); \
while (it != _nm.end()) \
{ \
ret+=_T(" { "); \
ret+=" { "; \
const ARRAY_STRUCT_PREFIX(_nm) *thiz=&*it;\
UNUSED2(thiz); // preempt any "unused" warning
#define NMT_END_ARRAY() \
++it; \
ret=ret.substr(0, ret.length()-2)+_T(" }, "); \
ret=ret.substr(0, ret.length()-2)+" }, "; \
} \
ret=ret.substr(0, ret.length()-2)+_T(" }, ");
ret=ret.substr(0, ret.length()-2)+" }, ";
#define NMT_FIELD_INT(_nm, _hosttp, _netsz) \
ret += #_nm _T(": "); \
ret += #_nm ": "; \
ret += NetMessageStringConvert(thiz->_nm); \
ret += _T(", ");
ret += ", ";
#define NMT_FIELD(_tp, _nm) \
ret += #_nm _T(": "); \
ret += #_nm ": "; \
ret += NetMessageStringConvert(thiz->_nm); \
ret += _T(", ");
ret += ", ";
#define END_NMT_CLASS() \
return ret.substr(0, ret.length()-2); \

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2009 Wildfire Games.
/* Copyright (C) 2010 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -39,13 +39,24 @@
#include "ps/Globals.h"
#include "ps/GameAttributes.h"
#include "simulation/Simulation.h"
#include "simulation2/Simulation2.h"
// DECLARATIONS
#define LOG_CATEGORY L"net"
CNetClient *g_NetClient = NULL;
extern int fps;
class CClientTurnManager : public CTurnManager
{
public:
CClientTurnManager(CNetClient& client) : m_Client(client) { }
virtual void QueueLocalCommand(CNetMessage* pMessage);
virtual void NewTurn();
virtual bool NewTurnReady() { return m_Client.m_TurnPending; }
private:
CNetClient& m_Client;
};
//-----------------------------------------------------------------------------
// Name: CServerPlayer()
@ -84,6 +95,9 @@ void CServerPlayer::ScriptingInit( void )
CNetClient::CNetClient( CGame* pGame, CGameAttributes* pGameAttribs )
: m_JsPlayers( &m_Players )
{
m_TurnManager = new CClientTurnManager(*this);
m_ClientTurnManager = NULL;
m_pLocalPlayerSlot = NULL;
m_pGame = pGame;
m_pGameAttributes = pGameAttribs;
@ -91,7 +105,8 @@ CNetClient::CNetClient( CGame* pGame, CGameAttributes* pGameAttribs )
//ONCE( ScriptingInit(); );
m_pGame->GetSimulation()->SetTurnManager(this);
if (!g_UseSimulation2)
m_pGame->GetSimulation()->SetTurnManager(m_TurnManager);
g_ScriptingHost.SetGlobal("g_NetClient", OBJECT_TO_JSVAL(GetScript()));
}
@ -173,7 +188,7 @@ bool CNetClient::SetupSession( CNetSession* pSession )
// Validate parameters
if ( !pSession ) return false;
FsmActionCtx* pContext = new FsmActionCtx;
FsmActionCtx* pContext = new FsmActionCtx; // XXX: this gets leaked
if ( !pContext ) return false;
pContext->pHost = this;
@ -193,7 +208,7 @@ bool CNetClient::SetupSession( CNetSession* pSession )
pSession->AddTransition( NCS_PREGAME, ( uint )NMT_ASSIGN_PLAYER_SLOT, NCS_PREGAME, (void*)&OnPreGame, pContext );
pSession->AddTransition( NCS_PREGAME, ( uint )NMT_PLAYER_CONFIG, NCS_PREGAME, (void*)&OnPreGame, pContext );
pSession->AddTransition( NCS_PREGAME, ( uint )NMT_PLAYER_JOIN, NCS_PREGAME, (void*)&OnPlayerJoin, pContext );
pSession->AddTransition( NCS_PREGAME, ( uint )NMT_GAME_START, NCS_INGAME, (void*)&OnStartGame, pContext );
pSession->AddTransition( NCS_PREGAME, ( uint )NMT_GAME_START, NCS_INGAME, (void*)&OnStartGame_, pContext );
pSession->AddTransition( NCS_INGAME, ( uint )NMT_CHAT, NCS_INGAME, (void*)&OnChat, pContext );
pSession->AddTransition( NCS_INGAME, ( uint )NMT_GOTO, NCS_INGAME, (void*)&OnInGame, pContext );
@ -208,6 +223,7 @@ bool CNetClient::SetupSession( CNetSession* pSession )
pSession->AddTransition( NCS_INGAME, ( uint )NMT_NOTIFY_REQUEST, NCS_INGAME, (void*)&OnInGame, pContext );
pSession->AddTransition( NCS_INGAME, ( uint )NMT_FORMATION_GOTO, NCS_INGAME, (void*)&OnInGame, pContext );
pSession->AddTransition( NCS_INGAME, ( uint )NMT_FORMATION_CONTACT_ACTION, NCS_INGAME, (void*)&OnInGame, pContext );
pSession->AddTransition( NCS_INGAME, ( uint )NMT_SIMULATION_COMMAND, NCS_INGAME, (void*)&OnInGame, pContext );
pSession->AddTransition( NCS_INGAME, ( uint )NMT_END_COMMAND_BATCH, NCS_INGAME, (void*)&OnInGame, pContext );
// Set first state
@ -253,7 +269,7 @@ bool CNetClient::OnError( void* pContext, CFsmEvent* pEvent )
if ( pEvent->GetType() != (uint)NMT_ERROR ) return true;
CNetClient* pClient = ( CNetClient* )( ( FsmActionCtx* )pContext )->pHost;
assert( pClient );
debug_assert( pClient );
CErrorMessage* pMessage = ( CErrorMessage* )pEvent->GetParamRef();
if ( pMessage )
@ -283,7 +299,7 @@ bool CNetClient::OnPlayerJoin( void* pContext, CFsmEvent* pEvent )
if ( pEvent->GetType() != NMT_PLAYER_JOIN ) return true;
CNetClient* pClient = ( CNetClient* )( ( FsmActionCtx* )pContext )->pHost;
assert( pClient );
debug_assert( pClient );
CPlayerJoinMessage* pMessage = ( CPlayerJoinMessage* )pEvent->GetParamRef();
if ( pMessage )
@ -293,16 +309,25 @@ bool CNetClient::OnPlayerJoin( void* pContext, CFsmEvent* pEvent )
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 );
}
pClient->OnConnectComplete();
}
return true;
}
//-----------------------------------------------------------------------------
// Name: OnConnectComplete()
// Desc:
//-----------------------------------------------------------------------------
void CNetClient::OnConnectComplete( )
{
if ( m_OnConnectComplete.Defined() )
{
CConnectCompleteEvent connectComplete( ( CStrW )PS_OK, true );
m_OnConnectComplete.DispatchEvent( GetScript(), &connectComplete );
}
}
//-----------------------------------------------------------------------------
// Name: OnHandshake()
// Desc:
@ -315,8 +340,8 @@ bool CNetClient::OnHandshake( void* pContext, CFsmEvent* pEvent )
CNetClient* pClient = ( CNetClient* )( ( FsmActionCtx* )pContext )->pHost;
CNetSession* pSession = ( ( FsmActionCtx* )pContext )->pSession;
assert( pClient );
assert( pSession );
debug_assert( pClient );
debug_assert( pSession );
switch ( pEvent->GetType() )
{
@ -331,7 +356,7 @@ bool CNetClient::OnHandshake( void* pContext, CFsmEvent* pEvent )
handshake.m_MagicResponse = PS_PROTOCOL_MAGIC_RESPONSE;
handshake.m_ProtocolVersion = PS_PROTOCOL_VERSION;
handshake.m_SoftwareVersion = PS_PROTOCOL_VERSION;
( ( CNetHost* )pClient )->SendMessage( pSession, &handshake );
pClient->SendMessage( pSession, &handshake );
}
break;
@ -340,7 +365,7 @@ bool CNetClient::OnHandshake( void* pContext, CFsmEvent* pEvent )
CAuthenticateMessage authenticate;
authenticate.m_Name = pClient->m_Nickname;
authenticate.m_Password = pClient->m_Password;
( ( CNetHost* )pClient )->SendMessage( pSession, &authenticate );
pClient->SendMessage( pSession, &authenticate );
}
break;
}
@ -361,8 +386,8 @@ bool CNetClient::OnAuthenticate( void* pContext, CFsmEvent* pEvent )
UNUSED2(pClient);
CNetSession* pSession = ( ( FsmActionCtx* )pContext )->pSession;
assert( pClient );
assert( pSession );
debug_assert( pClient );
debug_assert( pSession );
if ( pEvent->GetType() == (uint)NMT_ERROR )
{
@ -395,8 +420,8 @@ bool CNetClient::OnPreGame( void* pContext, CFsmEvent* pEvent )
CNetClient* pClient = ( CNetClient* )( ( FsmActionCtx* )pContext )->pHost;
CNetSession* pSession = ( CNetSession* )( ( FsmActionCtx* )pContext )->pSession;
assert( pClient );
assert( pSession );
debug_assert( pClient );
debug_assert( pSession );
switch ( pEvent->GetType() )
{
@ -499,6 +524,13 @@ bool CNetClient::OnInGame( void *pContext, CFsmEvent* pEvent )
CNetMessage* pMessage = ( CNetMessage* )pEvent->GetParamRef();
if ( pMessage )
{
if (pMessage->GetType() == NMT_SIMULATION_COMMAND)
{
CSimulationMessage* simMessage = static_cast<CSimulationMessage*> (pMessage);
pClient->m_ClientTurnManager->OnSimulationMessage(simMessage);
return true;
}
if ( pMessage->GetType() >= NMT_COMMAND_FIRST && pMessage->GetType() < NMT_COMMAND_LAST )
{
pClient->QueueIncomingMessage( pMessage );
@ -511,7 +543,13 @@ bool CNetClient::OnInGame( void *pContext, CFsmEvent* pEvent )
CEndCommandBatchMessage* pMessage = ( CEndCommandBatchMessage* )pEvent->GetParamRef();
if ( !pMessage ) return false;
pClient->SetTurnLength( 1, pMessage->m_TurnLength );
if (g_UseSimulation2)
{
CEndCommandBatchMessage* endMessage = static_cast<CEndCommandBatchMessage*> (pMessage);
pClient->m_ClientTurnManager->FinishedAllCommands(endMessage->m_Turn);
}
pClient->m_TurnManager->SetTurnLength( 1, pMessage->m_TurnLength );
pClient->m_TurnPending = true;
@ -563,19 +601,35 @@ bool CNetClient::OnChat( void* pContext, CFsmEvent* pEvent )
// Name: OnStartGame()
// Desc:
//-----------------------------------------------------------------------------
bool CNetClient::OnStartGame( void* pContext, CFsmEvent* pEvent )
void CNetClient::OnStartGame( void )
{
if ( m_OnStartGame.Defined() )
{
CStartGameEvent event;
m_OnStartGame.DispatchEvent( GetScript(), &event );
}
}
//-----------------------------------------------------------------------------
// Name: OnStartGame_()
// Desc:
//-----------------------------------------------------------------------------
bool CNetClient::OnStartGame_( void* pContext, CFsmEvent* pEvent )
{
// Validate parameters
if ( !pEvent || !pContext ) return false;
CNetClient* pClient = ( CNetClient* )( ( FsmActionCtx* )pContext )->pHost;
CNetSession* pSession = ( ( FsmActionCtx* )pContext )->pSession;
if ( pClient->m_OnStartGame.Defined() )
if (g_UseSimulation2)
{
CStartGameEvent event;
pClient->m_OnStartGame.DispatchEvent( pClient->GetScript(), &event );
pClient->m_ClientTurnManager = new CNetClientTurnManager(*pClient->m_pGame->GetSimulation2(), *pClient, pClient->GetLocalPlayer()->GetPlayerID(), pSession->GetID());
pClient->m_pGame->SetTurnManager(pClient->m_ClientTurnManager);
}
pClient->OnStartGame();
return true;
}
@ -635,12 +689,15 @@ int CNetClient::StartGame( void )
if ( m_pGame->StartGame( m_pGameAttributes ) != PSRETURN_OK ) return -1;
if (!g_UseSimulation2)
{
// 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 );
SendMessage( pSession, &endBatch );
}
return 0;
}
@ -658,33 +715,33 @@ CPlayer* CNetClient::GetLocalPlayer()
// Name: NewTurn()
// Desc:
//-----------------------------------------------------------------------------
void CNetClient::NewTurn()
void CClientTurnManager::NewTurn()
{
CScopeLock lock(m_Mutex);
CScopeLock lock(m_Client.m_Mutex);
RotateBatches();
ClearBatch(2);
m_TurnPending = false;
m_Client.m_TurnPending = false;
//debug_printf(L"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 );
CNetSession* pSession = m_Client.GetSession( 0 );
m_Client.SendMessage( pSession, pMsg );
}
//-----------------------------------------------------------------------------
// Name: QueueLocalCommand()
// Desc:
//-----------------------------------------------------------------------------
void CNetClient::QueueLocalCommand( CNetMessage* pMessage )
void CClientTurnManager::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 );
CNetSession* pSession = m_Client.GetSession( 0 );
m_Client.SendMessage( pSession, pMessage );
}
//-----------------------------------------------------------------------------
@ -695,5 +752,5 @@ void CNetClient::QueueIncomingMessage( CNetMessage* pMessage )
{
CScopeLock lock( m_Mutex );
QueueMessage( 2, pMessage );
m_TurnManager->QueueMessage( 2, pMessage );
}

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2009 Wildfire Games.
/* Copyright (C) 2010 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -28,6 +28,7 @@
// INCLUDES
#include "NetSession.h"
#include "NetTurnManager.h"
#include "ps/CStr.h"
#include "simulation/TurnManager.h"
#include "simulation/ScriptObject.h"
@ -88,9 +89,10 @@ private:
*/
class CNetClient: public CNetHost,
public CJSObject<CNetClient>,
protected CTurnManager
public CJSObject<CNetClient>
{
NONCOPYABLE(CNetClient);
public:
CNetClient( CGame* pGame, CGameAttributes* pGameAttributes );
@ -139,22 +141,20 @@ public:
static void ScriptingInit( void );
int StartGame( void );
CNetTurnManager* GetTurnManager() { debug_assert(m_ClientTurnManager); return m_ClientTurnManager; }
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 );
void QueueIncomingMessage ( CNetMessage* pMessage );
private:
virtual void OnConnectComplete ( void );
virtual void OnStartGame ( void );
// Not implemented
CNetClient( const CNetClient& );
CNetClient& operator=( const CNetClient& );
private:
static bool OnError ( void* pContext, CFsmEvent* pEvent );
static bool OnPlayerJoin ( void* pContext, CFsmEvent* pEvent );
@ -163,13 +163,16 @@ private:
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 );
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
CTurnManager* m_TurnManager;
CNetClientTurnManager* m_ClientTurnManager;
};
extern CNetClient *g_NetClient;

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2009 Wildfire Games.
/* Copyright (C) 2010 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -31,6 +31,9 @@
#include "Network.h"
#include "NetMessage.h"
#include "ps/Game.h"
#include "simulation2/Simulation2.h"
#undef ALLNETMSGS_DONT_CREATE_NMTS
#define ALLNETMSGS_IMPLEMENT
#include "NetMessages.h"
@ -41,10 +44,6 @@
// DEFINES
#define LOG_CATEGORY L"net"
// Please don't modify the deserializer map outside the ONCE-block in DeserializeMessage
//typedef std::map< NetMessageType, NetMessageDeserializer > MessageDeserializerMap;
//MessageDeserializerMap g_DeserializerMap;
//-----------------------------------------------------------------------------
// Name: CNetMessage()
// Desc: Constructor
@ -110,7 +109,7 @@ const u8* CNetMessage::Deserialize( const u8* pStart, const u8* pEnd )
m_Type = *( ( NetMessageType* )pStart );
uint size = *( ( uint* )( pStart + sizeof( NetMessageType ) ) );
assert( pStart + size == pEnd );
debug_assert( pStart + size == pEnd );
return pStart + sizeof( NetMessageType ) + sizeof( size );
}
@ -1054,6 +1053,10 @@ CNetMessage* CNetMessageFactory::CreateMessage(const void* pData,
pNewMessage = new CFormationContactActionMessage;
break;
case NMT_SIMULATION_COMMAND:
pNewMessage = new CSimulationMessage(g_Game->GetSimulation2()->GetScriptInterface());
break;
default:
LOG(CLogger::Error, LOG_CATEGORY, L"CNetMessageFactory::CreateMessage(): Unknown message received" );
break;

View File

@ -47,6 +47,8 @@
class CNetMessage : public ISerializable
{
NONCOPYABLE(CNetMessage);
friend class CNetSession;
public:
@ -132,18 +134,10 @@ public:
* @return The message as a string
*/
virtual CStr ToString( void ) const;
// virtual CStr GetString( void ) const;
// operator CStr() const { return GetString(); }
private:
// Not implemented
CNetMessage( const CNetMessage& );
CNetMessage& operator=( const CNetMessage& );
bool m_Dirty; // Message has been modified
NetMessageType m_Type; // Message type
//u16 m_SerializeSize; // Serialized message size in bytes
public:
@ -214,6 +208,28 @@ private:
CNetMessageFactory& operator=( const CNetMessageFactory& );
};
/**
* Special message type for simulation commands.
* These commands are exposed as arbitrary JS objects, associated with a specific player.
*/
class CSimulationMessage : public CNetMessage
{
public:
CSimulationMessage(ScriptInterface& scriptInterface);
CSimulationMessage(ScriptInterface& scriptInterface, u32 client, i32 player, u32 turn, jsval data);
virtual u8* Serialize(u8* pBuffer) const;
virtual const u8* Deserialize(const u8* pStart, const u8* pEnd);
virtual size_t GetSerializedLength() const;
virtual CStr ToString() const;
u32 m_Client;
i32 m_Player;
u32 m_Turn;
CScriptValRooted m_Data;
private:
ScriptInterface& m_ScriptInterface;
};
// This time, the classes are created
#include "NetMessages.h"

View File

@ -0,0 +1,126 @@
/* Copyright (C) 2010 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
*/
#include "NetMessage.h"
#include "scriptinterface/ScriptInterface.h"
#include "simulation2/serialization/BinarySerializer.h"
#include "simulation2/serialization/StdDeserializer.h"
/**
* Serializer instance that writes directly to a buffer (which must be long enough).
*/
class CBufferBinarySerializer : public CBinarySerializer
{
public:
CBufferBinarySerializer(ScriptInterface& scriptInterface, u8* buffer) :
CBinarySerializer(scriptInterface), m_Buffer(buffer)
{
}
void Put(const char* UNUSED(name), const u8* data, size_t len)
{
memcpy(m_Buffer, data, len);
m_Buffer += len;
}
u8* m_Buffer;
};
/**
* Serializer instance that simply counts how many bytes would be written.
*/
class CLengthBinarySerializer : public CBinarySerializer
{
public:
CLengthBinarySerializer(ScriptInterface& scriptInterface) :
CBinarySerializer(scriptInterface), m_Length(0)
{
}
void Put(const char* UNUSED(name), const u8* UNUSED(data), size_t len)
{
m_Length += len;
}
size_t m_Length;
};
CSimulationMessage::CSimulationMessage(ScriptInterface& scriptInterface) :
CNetMessage(NMT_SIMULATION_COMMAND), m_ScriptInterface(scriptInterface)
{
}
CSimulationMessage::CSimulationMessage(ScriptInterface& scriptInterface, u32 client, i32 player, u32 turn, jsval data) :
CNetMessage(NMT_SIMULATION_COMMAND), m_ScriptInterface(scriptInterface),
m_Client(client), m_Player(player), m_Turn(turn), m_Data(scriptInterface.GetContext(), data)
{
}
u8* CSimulationMessage::Serialize(u8* pBuffer) const
{
// TODO: ought to handle serialization exceptions
// TODO: ought to represent common commands more efficiently
u8* pos = CNetMessage::Serialize(pBuffer);
CBufferBinarySerializer serializer(m_ScriptInterface, pos);
serializer.NumberU32_Unbounded("client", m_Client);
serializer.NumberI32_Unbounded("player", m_Player);
serializer.NumberU32_Unbounded("turn", m_Turn);
serializer.ScriptVal("command", m_Data);
return serializer.m_Buffer;
}
const u8* CSimulationMessage::Deserialize(const u8* pStart, const u8* pEnd)
{
// TODO: ought to handle serialization exceptions
// TODO: ought to represent common commands more efficiently
const u8* pos = CNetMessage::Deserialize(pStart, pEnd);
std::istringstream stream(std::string(pos, pEnd));
CStdDeserializer deserializer(m_ScriptInterface, stream);
deserializer.NumberU32_Unbounded(m_Client);
deserializer.NumberI32_Unbounded(m_Player);
deserializer.NumberU32_Unbounded(m_Turn);
deserializer.ScriptVal(m_Data);
return pEnd;
}
size_t CSimulationMessage::GetSerializedLength() const
{
// TODO: serializing twice is stupidly inefficient - we should just
// do it once, store the result, and use it here and in Serialize
CLengthBinarySerializer serializer(m_ScriptInterface);
serializer.NumberU32_Unbounded("client", m_Client);
serializer.NumberI32_Unbounded("player", m_Player);
serializer.NumberU32_Unbounded("turn", m_Turn);
serializer.ScriptVal("command", m_Data);
return CNetMessage::GetSerializedLength() + serializer.m_Length;
}
CStr CSimulationMessage::ToString() const
{
std::string source;
if (!m_ScriptInterface.CallFunction(m_Data.get(), "toSource", source))
source = "ERROR";
std::stringstream stream;
stream << "CSimulationMessage { m_Client: " << m_Client << ", m_Player: " << m_Player << ", m_Turn: " << m_Turn << ", m_Data: " << source << " }";
return stream.str();
}

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2009 Wildfire Games.
/* Copyright (C) 2010 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -29,6 +29,7 @@
// INCLUDES
#include "ps/CStr.h"
#include "scripting/JSSerialization.h"
#include "scriptinterface/ScriptVal.h"
#include "simulation/EntityHandles.h"
// DEFINES
@ -79,6 +80,7 @@ enum NetMessageType
NMT_NOTIFY_REQUEST,
NMT_FORMATION_GOTO,
NMT_FORMATION_CONTACT_ACTION,
NMT_SIMULATION_COMMAND,
NMT_COMMAND_LAST,
NMT_LAST // Last message in the list
};
@ -190,6 +192,7 @@ START_NMT_CLASS_(GameStart, NMT_GAME_START)
END_NMT_CLASS()
START_NMT_CLASS_(EndCommandBatch, NMT_END_COMMAND_BATCH)
NMT_FIELD_INT(m_Turn, u32, 4)
NMT_FIELD_INT(m_TurnLength, u32, 2)
END_NMT_CLASS()

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2009 Wildfire Games.
/* Copyright (C) 2010 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -31,12 +31,24 @@
#include "NetJsEvents.h"
#include "NetSession.h"
#include "NetServer.h"
#include "simulation2/Simulation2.h"
#define LOG_CATEGORY L"net"
// DECLARATIONS
CNetServer* g_NetServer = NULL;
class CServerTurnManager : public CTurnManager
{
public:
CServerTurnManager(CNetServer& server) : m_Server(server) { }
virtual void QueueLocalCommand(CNetMessage* pMessage);
virtual void NewTurn();
virtual bool NewTurnReady();
private:
CNetServer& m_Server;
};
//-----------------------------------------------------------------------------
// Name: CNetServer()
// Desc: Constructor
@ -44,7 +56,8 @@ CNetServer* g_NetServer = NULL;
CNetServer::CNetServer( CGame *pGame, CGameAttributes *pGameAttributes )
: m_JsSessions( &m_IDSessions )
{
//ONCE( ScriptingInit(); );
m_TurnManager = new CServerTurnManager(*this);
m_ServerTurnManager = NULL;
m_Game = pGame;
m_GameAttributes = pGameAttributes;
@ -60,15 +73,14 @@ CNetServer::CNetServer( CGame *pGame, CGameAttributes *pGameAttributes )
m_GameAttributes->SetPlayerUpdateCallback( PlayerAttributeUpdate, this );
m_GameAttributes->SetPlayerSlotAssignmentCallback( PlayerSlotAssignment, this );
// Set turn manager
CSimulation* pSimulation = m_Game->GetSimulation();
if ( pSimulation ) pSimulation->SetTurnManager( this );
if (!g_UseSimulation2)
m_Game->GetSimulation()->SetTurnManager(m_TurnManager);
// 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, DEFAULT_TURN_LENGTH );
m_TurnManager->SetTurnLength( i, CTurnManager::DEFAULT_TURN_LENGTH );
}
g_ScriptingHost.SetGlobal( "g_NetServer", OBJECT_TO_JSVAL( GetScript() ) );
@ -167,6 +179,7 @@ bool CNetServer::SetupSession( CNetSession* pSession )
pSession->AddTransition( NSS_INGAME, ( uint )NMT_NOTIFY_REQUEST, NSS_INGAME, (void*)&OnInGame, pContext );
pSession->AddTransition( NSS_INGAME, ( uint )NMT_FORMATION_GOTO, NSS_INGAME, (void*)&OnInGame, pContext );
pSession->AddTransition( NSS_INGAME, ( uint )NMT_FORMATION_CONTACT_ACTION, NSS_INGAME, (void*)&OnInGame, pContext );
pSession->AddTransition( NSS_INGAME, ( uint )NMT_SIMULATION_COMMAND, NSS_INGAME, (void*)&OnInGame, pContext );
pSession->AddTransition( NSS_INGAME, ( uint )NMT_END_COMMAND_BATCH, NSS_INGAME, (void*)&OnInGame, pContext );
// Set first state
@ -189,7 +202,7 @@ bool CNetServer::HandleConnect( CNetSession* pSession )
handshake.m_Magic = PS_PROTOCOL_MAGIC;
handshake.m_ProtocolVersion = PS_PROTOCOL_VERSION;
handshake.m_SoftwareVersion = PS_PROTOCOL_VERSION;
CNetHost::SendMessage( pSession, &handshake );
SendMessage( pSession, &handshake );
// Store new session into the map
m_IDSessions[ pSession->GetID() ] = pSession;
@ -230,11 +243,11 @@ void CNetServer::SetupPlayer( CNetSession* pSession )
// Validate parameters
if ( !pSession ) return;
assert( m_GameAttributes );
debug_assert( m_GameAttributes );
// Send a new config message to the connected client
BuildGameSetupMessage( &gameSetup );
CNetHost::SendMessage( pSession, &gameSetup );
SendMessage( pSession, &gameSetup );
// Add information for already connected clients and the server
playerJoin.m_Clients.resize( GetSessionCount() );
@ -252,7 +265,7 @@ void CNetServer::SetupPlayer( CNetSession* pSession )
playerJoin.m_Clients[ i + 1 ].m_Name = pCurrSession->GetName();
}
CNetHost::SendMessage( pSession, &playerJoin );
SendMessage( pSession, &playerJoin );
// TODO: Handle observers
@ -268,7 +281,7 @@ void CNetServer::SetupPlayer( CNetSession* pSession )
// Skip the player being setup
if ( !pCurrSession || pCurrSession == pSession ) continue;
CNetHost::SendMessage( pCurrSession, &playerJoin );
SendMessage( pCurrSession, &playerJoin );
}
//Broadcast( &playerJoin );
@ -298,13 +311,13 @@ void CNetServer::SetupPlayer( CNetSession* pSession )
break;
}
CNetHost::SendMessage( pSession, &assignSlot );
SendMessage( pSession, &assignSlot );
if ( pCurrSlot->GetAssignment() == SLOT_SESSION )
{
// Setup player
BuildPlayerConfigMessage( &playerConfig, pCurrSlot->GetPlayer() );
CNetHost::SendMessage( pSession, &playerConfig );
SendMessage( pSession, &playerConfig );
}
}
}
@ -358,7 +371,7 @@ void CNetServer::OnPlayerLeave( CNetSession* pSession )
// TODO Set everything up for re-connect and resume
if ( pPlayerSlot )
{
SetClientPipe( pPlayerSlot->GetSlotID(), NULL );
m_TurnManager->SetClientPipe( pPlayerSlot->GetSlotID(), NULL );
pPlayerSlot->AssignClosed();
}
break;
@ -435,8 +448,8 @@ bool CNetServer::OnHandshake( void* pContext, CFsmEvent* pEvent )
CNetServer* pServer = ( CNetServer* )( ( FsmActionCtx* )pContext )->pHost;
CNetSession* pSession = ( ( FsmActionCtx* )pContext )->pSession;
assert( pServer );
assert( pSession );
debug_assert( pServer );
debug_assert( pSession );
CCliHandshakeMessage* pMessage = ( CCliHandshakeMessage* )pEvent->GetParamRef();
if ( pMessage )
@ -444,7 +457,7 @@ bool CNetServer::OnHandshake( void* pContext, CFsmEvent* pEvent )
if ( pMessage->m_ProtocolVersion != PS_PROTOCOL_VERSION )
{
CCloseRequestMessage closeRequest;
( ( CNetHost* )pServer )->SendMessage( pSession, &closeRequest );
pServer->SendMessage( pSession, &closeRequest );
CErrorMessage error( PS_OK, SS_UNCONNECTED );
pSession->Update( ( uint )NMT_ERROR, &error );
@ -455,7 +468,7 @@ bool CNetServer::OnHandshake( void* pContext, CFsmEvent* pEvent )
handshakeResponse.m_UseProtocolVersion = PS_PROTOCOL_VERSION;
handshakeResponse.m_Message = pServer->m_WelcomeMessage;
handshakeResponse.m_Flags = 0;
( ( CNetHost* )pServer )->SendMessage( pSession, &handshakeResponse );
pServer->SendMessage( pSession, &handshakeResponse );
}
}
@ -494,7 +507,7 @@ bool CNetServer::OnAuthenticate( void* pContext, CFsmEvent* pEvent )
authenticateResult.m_Code = ARC_OK;
authenticateResult.m_SessionID = pSession->GetID();
authenticateResult.m_Message = L"Logged in";
( ( CNetHost* )pServer )->SendMessage( pSession, &authenticateResult );
pServer->SendMessage( pSession, &authenticateResult );
//pServer->AddSession( pSession );
@ -518,7 +531,7 @@ bool CNetServer::OnAuthenticate( void* pContext, CFsmEvent* pEvent )
authenticateResult.m_Code = ARC_PASSWORD_INVALID;
authenticateResult.m_SessionID = 0;
authenticateResult.m_Message = L"Invalid Password";
( ( CNetHost* )pServer )->SendMessage( pSession, &authenticateResult );
pServer->SendMessage( pSession, &authenticateResult );
}
}
@ -552,6 +565,13 @@ bool CNetServer::OnInGame( void* pContext, CFsmEvent* pEvent )
CNetMessage* pMessage = ( CNetMessage* )pEvent->GetParamRef();
if ( pMessage )
{
if (pMessage->GetType() == NMT_SIMULATION_COMMAND)
{
CSimulationMessage* simMessage = static_cast<CSimulationMessage*> (pMessage);
pServer->m_ServerTurnManager->OnSimulationMessage(simMessage);
return true;
}
if ( pMessage->GetType() >= NMT_COMMAND_FIRST && pMessage->GetType() < NMT_COMMAND_LAST )
{
//pSession->m_pPlayer->ValidateCommand(pMsg);
@ -562,6 +582,12 @@ bool CNetServer::OnInGame( void* pContext, CFsmEvent* pEvent )
if ( pMessage->GetType() == NMT_END_COMMAND_BATCH )
{
if (g_UseSimulation2)
{
CEndCommandBatchMessage* endMessage = static_cast<CEndCommandBatchMessage*> (pMessage);
pServer->m_ServerTurnManager->NotifyFinishedClientCommands(pSession->GetID(), endMessage->m_Turn);
}
// TODO Update client timing information and recalculate turn length
pSession->SetReadyForTurn( true );
}
@ -585,8 +611,8 @@ bool CNetServer::OnChat( void* pContext, CFsmEvent* pEvent )
CNetServer* pServer = ( CNetServer* )( ( FsmActionCtx* )pContext )->pHost;
CNetSession* pSession = ( ( FsmActionCtx* )pContext )->pSession;
assert( pSession );
assert( pServer );
debug_assert( pSession );
debug_assert( pServer );
CChatMessage* pMessage = ( CChatMessage* )pEvent->GetParamRef();
if ( pMessage )
@ -734,12 +760,18 @@ int CNetServer::StartGame( void )
{
uint i;
assert( m_Game );
assert( m_GameAttributes );
debug_assert( m_Game );
debug_assert( m_GameAttributes );
if ( m_Game->StartGame( m_GameAttributes ) != PSRETURN_OK ) return -1;
CTurnManager::Initialize( m_GameAttributes->GetSlotCount() );
if (g_UseSimulation2)
{
m_ServerTurnManager = new CNetServerTurnManager(*m_Game->GetSimulation2(), *this, m_Game->GetLocalPlayer()->GetPlayerID(), SERVER_SESSIONID);
m_Game->SetTurnManager(m_ServerTurnManager);
}
m_TurnManager->Initialize( m_GameAttributes->GetSlotCount() );
for ( i = 0; i < m_GameAttributes->GetSlotCount(); i++ )
{
@ -747,7 +779,11 @@ int CNetServer::StartGame( void )
if ( !pCurrSlot ) continue;
if ( pCurrSlot->GetAssignment() == SLOT_SESSION )
CTurnManager::SetClientPipe( i, pCurrSlot->GetSession() );
{
m_TurnManager->SetClientPipe( i, pCurrSlot->GetSession() );
if (g_UseSimulation2)
m_ServerTurnManager->InitialiseClient(pCurrSlot->GetSessionID());
}
}
m_State = SERVER_STATE_INGAME;
@ -778,7 +814,7 @@ void CNetServer::BuildGameSetupMessage( CGameSetupMessage* pMessage )
// Validate parameters
if ( !pMessage ) return;
assert( m_GameAttributes );
debug_assert( m_GameAttributes );
// Iterate through game properties and load them into message
m_GameAttributes->IterateSynchedProperties( GameSetupMessageCallback, pMessage );
@ -964,12 +1000,12 @@ bool CNetServer::AllowObserver( CNetSession* UNUSED( pSession ) )
// Name: NewTurnReady()
// Desc:
//-----------------------------------------------------------------------------
bool CNetServer::NewTurnReady()
bool CServerTurnManager::NewTurnReady()
{
// Check whether all sessions are ready for the next turn
for ( uint i = 0; i < GetSessionCount(); i++ )
for ( uint i = 0; i < m_Server.GetSessionCount(); i++ )
{
CNetSession* pCurrSession = GetSession( i );
CNetSession* pCurrSession = m_Server.GetSession( i );
if ( !pCurrSession ) continue;
if ( !pCurrSession->IsReadyForTurn() )
@ -983,14 +1019,14 @@ bool CNetServer::NewTurnReady()
// Name: NewTurn()
// Desc:
//-----------------------------------------------------------------------------
void CNetServer::NewTurn()
void CServerTurnManager::NewTurn()
{
CScopeLock lock(m_Mutex);
CScopeLock lock(m_Server.m_Mutex);
// Reset session ready for next turn flag
for ( uint i = 0; i < GetSessionCount(); i++ )
for ( uint i = 0; i < m_Server.GetSessionCount(); i++ )
{
CNetSession* pCurrSession = GetSession( i );
CNetSession* pCurrSession = m_Server.GetSession( i );
if ( !pCurrSession ) continue;
pCurrSession->SetReadyForTurn( false );
@ -999,7 +1035,7 @@ void CNetServer::NewTurn()
RecordBatch( 2 );
RotateBatches();
ClearBatch( 2 );
IterateBatch( 1, CSimulation::GetMessageMask, m_Game->GetSimulation() );
IterateBatch( 1, CSimulation::GetMessageMask, m_Server.m_Game->GetSimulation() );
SendBatch( 1 );
//IterateBatch( 1, SendToObservers, this );
}
@ -1008,9 +1044,14 @@ void CNetServer::NewTurn()
// Name: QueueLocalCommand()
// Desc:
//-----------------------------------------------------------------------------
void CNetServer::QueueLocalCommand( CNetMessage *pMessage )
void CServerTurnManager::QueueLocalCommand( CNetMessage *pMessage )
{
QueueIncomingCommand( pMessage );
// Validate parameters
if ( !pMessage ) return;
//LOG( NORMAL, LOG_CATEGORY, L"CServerTurnManager::QueueLocalCommand(): %hs.", pMessage->ToString().c_str() );
QueueMessage( 2, pMessage );
}
//-----------------------------------------------------------------------------
@ -1024,7 +1065,7 @@ void CNetServer::QueueIncomingCommand( CNetMessage* pMessage )
//LOG( NORMAL, LOG_CATEGORY, L"CNetServer::QueueIncomingCommand(): %hs.", pMessage->ToString().c_str() );
QueueMessage( 2, pMessage );
m_TurnManager->QueueMessage( 2, pMessage );
}
//-----------------------------------------------------------------------------
@ -1069,5 +1110,3 @@ void CNetServer::BuildPlayerSlotAssignmentMessage(
break;
}
}

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2009 Wildfire Games.
/* Copyright (C) 2010 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -29,6 +29,7 @@
// INCLUDES
#include "Network.h"
#include "NetSession.h"
#include "NetTurnManager.h"
#include "simulation/TurnManager.h"
#include "scripting/ScriptableObject.h"
#include "ps/GameAttributes.h"
@ -45,7 +46,6 @@
#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"
@ -98,9 +98,9 @@ typedef std::vector< CNetSession* > SessionList;
*/
class CNetServer : public CNetHost,
public CJSObject<CNetServer>,
public CTurnManager
public CJSObject<CNetServer>
{
NONCOPYABLE(CNetServer);
public:
CNetServer( CGame* pGame, CGameAttributes* pGameAttributes );
@ -154,6 +154,8 @@ public:
*/
CNetSession* GetSessionByID( uint sessionID );
CNetTurnManager* GetTurnManager() { debug_assert(m_ServerTurnManager); return m_ServerTurnManager; }
protected:
virtual bool SetupSession ( CNetSession* pSession );
@ -162,10 +164,6 @@ protected:
private:
// Not implemented
CNetServer( const CNetServer& );
CNetServer& operator=( const CNetServer& );
//void ClientConnect ( ENetPeer* pPeer );
//void ClientDisconnect ( ENetPeer* pPeer );
//void ClientReceive ( ENetPeer* pPeer, ENetPacket* pPacket );
@ -251,7 +249,6 @@ private:
//CScriptObject m_ScriptConnect; // Script client connect dispatch
//CScriptObject m_ScriptDisconnect; // Script client disconnect dispatch
//CScriptObject m_ScriptChat; // Script client chat dispatch
CPlayer* m_Player; // Server player
public:
@ -288,8 +285,8 @@ protected:
// Call the JS callback for incoming events
void OnPlayerChat ( const CStrW& from, const CStrW& message );
void OnPlayerJoin ( CNetSession* pSession );
void OnPlayerLeave ( CNetSession* pSession );
virtual void OnPlayerJoin ( CNetSession* pSession );
virtual void OnPlayerLeave ( CNetSession* pSession );
void SetupPlayer ( CNetSession* pSession );
//static bool OnPlayerJoin ( void* pContext, CFsmEvent* pEvent );
@ -303,11 +300,6 @@ protected:
// 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);
@ -318,13 +310,16 @@ protected:
// false otherwise
virtual bool AllowObserver( CNetSession* pSession );
public:
CMutex m_Mutex; // Synchronization object for batches
CGame* m_Game; // Pointer to actual game
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
@ -334,7 +329,6 @@ private:
*/
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
@ -353,10 +347,11 @@ private:
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 );
CTurnManager* m_TurnManager;
CNetServerTurnManager* m_ServerTurnManager;
};
extern CNetServer *g_NetServer;
#endif // NETSERVER_H

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2009 Wildfire Games.
/* Copyright (C) 2010 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -139,7 +139,7 @@ bool CNetHost::Connect( const CStr& host, uint port )
ENetAddress addr;
PeerSession item;
assert( m_Host );
debug_assert( m_Host );
// Bind to specified host
addr.port = port;
@ -196,8 +196,8 @@ bool CNetHost::Disconnect( CNetSession* pSession )
// Validate parameters
if ( !pSession ) return false;
assert( m_Host );
assert( pSession->m_Peer );
debug_assert( m_Host );
debug_assert( pSession->m_Peer );
// Disconnect peer
enet_peer_disconnect( pSession->m_Peer, 0 );
@ -233,7 +233,7 @@ bool CNetHost::Disconnect( CNetSession* pSession )
//-----------------------------------------------------------------------------
/*bool CNetHost::Run( void )
{
assert( m_Host );
debug_assert( m_Host );
// Host created?
if ( !m_Host ) return false;
@ -354,7 +354,7 @@ bool CNetHost::Poll( void )
PeerSession item;
PeerSessionList::iterator it;
assert( m_Host );
debug_assert( m_Host );
// Poll host for events
while ( enet_host_service( m_Host, &event, 0 ) > 0 )
@ -416,20 +416,24 @@ bool CNetHost::Poll( void )
// Is this our session?
if ( it->pPeer == event.peer )
{
bool ok = false;
// Create message from raw data
CNetMessage* pNewMessage = CNetMessageFactory::CreateMessage( event.packet->data, event.packet->dataLength );
if ( !pNewMessage ) return false;
if ( pNewMessage )
{
NET_LOG4( "Message %s of size %lu was received from %p", pNewMessage->ToString().c_str(), (unsigned long)pNewMessage->GetSerializedLength(), event.peer->data );
// Successfully handled?
if ( !HandleMessageReceive( pNewMessage, it->pSession ) ) {
enet_packet_destroy( event.packet );
return false;
ok = HandleMessageReceive( pNewMessage, it->pSession );
delete pNewMessage;
}
// Done using the packet
enet_packet_destroy( event.packet );
if (! ok)
return false;
}
}
@ -443,8 +447,6 @@ bool CNetHost::Poll( void )
//-----------------------------------------------------------------------------
// 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 )
{
@ -501,8 +503,8 @@ bool CNetHost::SendMessage(
// Validate parameters
if ( !pMessage || !pSession ) return false;
assert( pSession->m_Peer );
assert( m_Host );
debug_assert( pSession->m_Peer );
debug_assert( m_Host );
size_t size = pMessage->GetSerializedLength();
@ -544,7 +546,7 @@ CNetMessage* CNetHost::ReceiveMessage( const CNetSession* pSession )
// Validate parameters
if ( !pSession ) return NULL;
assert( pSession->m_Peer );
debug_assert( pSession->m_Peer );
// Let ENet receive a message from peer
ENetPacket* pPacket = enet_peer_receive( pSession->m_Peer, ENET_DEFAULT_CHANNEL );
@ -705,7 +707,7 @@ void CNetSession::Push( CNetMessage* pMessage )
// Validate parameters
if ( !pMessage ) return;
assert( m_Host );
debug_assert( m_Host );
m_Host->SendMessage( this, pMessage );
}
@ -716,7 +718,7 @@ void CNetSession::Push( CNetMessage* pMessage )
//-----------------------------------------------------------------------------
CNetMessage* CNetSession::TryPop( void )
{
assert( m_Host );
debug_assert( m_Host );
return m_Host->ReceiveMessage( this );
}
@ -839,8 +841,8 @@ bool CNetServerSession::BaseHandler(
CNetMessage* pMessage,
CNetSession* pSession )
{
assert( pMessage );
assert( pSession );
debug_assert( pMessage );
debug_assert( pSession );
// Validate parameters
if ( !pMessage || !pSession ) return false;
@ -882,9 +884,9 @@ bool CNetServerSession::HandshakeHandler(
CNetMessage* pMessage,
CNetSession* pSession )
{
assert( pMessage );
assert( pSession );
assert( m_Server );
debug_assert( pMessage );
debug_assert( pSession );
debug_assert( m_Server );
// Validate parameters
if ( !pMessage || !pSession ) return false;

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2009 Wildfire Games.
/* Copyright (C) 2010 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -28,7 +28,6 @@
// INCLUDES
#include "Network.h"
#include "ps/Singleton.h"
#include "ps/GameAttributes.h"
#include "ps/Player.h"
#include "fsm.h"
@ -61,11 +60,11 @@ typedef std::vector< PeerSession > PeerSessionList;
/*
CLASS : CNetHost
DESCRIPTION : CNetHost is a wrapper around ENet host conecept
DESCRIPTION : CNetHost is a wrapper around ENet host concept
NOTES :
*/
class CNetHost : public Singleton< CNetHost >
class CNetHost
{
public:
@ -198,7 +197,7 @@ private:
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
new message is received from a client, its representing
session object's message handler is called for processing
that message.
CNetSession is also a state machine. All client requests
@ -212,6 +211,8 @@ class CNetSession : public CFsm,
public CJSObject< CNetSession >,
public IMessagePipeEnd
{
NONCOPYABLE(CNetSession);
friend class CNetHost;
public:
@ -289,11 +290,6 @@ 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

View File

@ -0,0 +1,265 @@
/* Copyright (C) 2010 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
*/
#include "precompiled.h"
#include "NetTurnManager.h"
#include "NetServer.h"
#include "NetClient.h"
#include "maths/MathUtil.h"
#include "simulation2/Simulation2.h"
static const int TURN_LENGTH = 200; // TODO: this should be a variable controlled by the server depending on latency
static const int COMMAND_DELAY = 2;
//#define NETTURN_LOG debug_printf
CNetTurnManager::CNetTurnManager(CSimulation2& simulation, int playerId, int clientId) :
m_Simulation2(simulation), m_CurrentTurn(0), m_ReadyTurn(1), m_DeltaTime(0),
m_PlayerId(playerId), m_ClientId(clientId)
{
// When we are on turn n, we schedule new commands for n+2.
// We know that all other clients have finished scheduling commands for n (else we couldn't have got here).
// We know we have not yet finished scheduling commands for n+2.
// Hence other clients can be on turn n-1, n, n+1, and no other.
// So they can be sending us commands scheduled for n+1, n+2, n+3.
// So we need a 3-element buffer:
m_QueuedCommands.resize(COMMAND_DELAY + 1);
}
bool CNetTurnManager::Update(float frameLength)
{
m_DeltaTime += frameLength;
// If we haven't reached the next turn yet, do nothing
if (m_DeltaTime < 0)
return false;
#ifdef NETTURN_LOG
NETTURN_LOG(L"Update current=%d ready=%d\n", m_CurrentTurn, m_ReadyTurn);
#endif
// Check that the next turn is ready for execution
if (m_ReadyTurn > m_CurrentTurn)
{
NotifyFinishedOwnCommands(m_CurrentTurn + COMMAND_DELAY);
m_CurrentTurn += 1;
// Put all the client commands into a single list, in a globally consistent order
std::vector<SimulationCommand> commands;
for (std::map<u32, std::vector<SimulationCommand> >::iterator it = m_QueuedCommands[0].begin(); it != m_QueuedCommands[0].end(); ++it)
{
commands.insert(commands.end(), it->second.begin(), it->second.end());
}
m_QueuedCommands.pop_front();
m_QueuedCommands.resize(m_QueuedCommands.size() + 1);
#ifdef NETTURN_LOG
NETTURN_LOG(L"Running %d cmds\n", commands.size());
#endif
m_Simulation2.Update(TURN_LENGTH, commands);
// TODO: Compute state hash, send SyncCheck message
// Set the time for the next turn update
m_DeltaTime -= TURN_LENGTH / 1000.f;
return true;
}
else
{
// Oops, we wanted to start the next turn but it's not ready yet -
// there must be too much network lag.
// TODO: complain to the user.
// TODO: send feedback to the server to increase the turn length.
// Reset the next-turn timer to 0 so we try again next update but
// so we don't rush to catch up in subsequent turns.
// TODO: we should do clever rate adjustment instead of just pausing like this.
m_DeltaTime = 0;
return false;
}
}
void CNetTurnManager::Interpolate(float frameLength)
{
float offset = clamp(m_DeltaTime / (TURN_LENGTH / 1000.f) + 1.0, 0.0, 1.0);
m_Simulation2.Interpolate(frameLength, offset);
}
void CNetTurnManager::AddCommand(int client, int player, CScriptValRooted data, u32 turn)
{
#ifdef NETTURN_LOG
NETTURN_LOG(L"AddCommand(client=%d player=%d turn=%d)\n", client, player, turn);
#endif
if (!(m_CurrentTurn < turn && turn <= m_CurrentTurn + COMMAND_DELAY + 1))
{
debug_warn(L"Received command for invalid turn");
return;
}
SimulationCommand cmd;
cmd.player = player;
cmd.data = data;
m_QueuedCommands[turn - (m_CurrentTurn+1)][client].push_back(cmd);
}
void CNetTurnManager::FinishedAllCommands(u32 turn)
{
#ifdef NETTURN_LOG
NETTURN_LOG(L"FinishedAllCommands(%d)\n", turn);
#endif
debug_assert(turn == m_ReadyTurn + 1);
m_ReadyTurn = turn;
}
void CNetClientTurnManager::PostCommand(CScriptValRooted data)
{
#ifdef NETTURN_LOG
NETTURN_LOG(L"PostCommand()\n");
#endif
// Transmit command to server
CSimulationMessage msg(m_Simulation2.GetScriptInterface(), m_ClientId, m_PlayerId, m_CurrentTurn + COMMAND_DELAY, data.get());
CNetSession* session = m_NetClient.GetSession(0);
m_NetClient.SendMessage(session, &msg);
// Add to our local queue
//AddCommand(m_ClientId, m_PlayerId, data, m_CurrentTurn + COMMAND_DELAY);
// TODO: we should do this when the server stops sending our commands back to us
}
void CNetClientTurnManager::NotifyFinishedOwnCommands(u32 turn)
{
#ifdef NETTURN_LOG
NETTURN_LOG(L"NotifyFinishedOwnCommands(%d)\n", turn);
#endif
CEndCommandBatchMessage msg;
msg.m_TurnLength = TURN_LENGTH;
msg.m_Turn = turn;
CNetSession* session = m_NetClient.GetSession(0);
m_NetClient.SendMessage(session, &msg);
}
void CNetClientTurnManager::OnSimulationMessage(CSimulationMessage* msg)
{
// Command received from the server - store it for later execution
AddCommand(msg->m_Client, msg->m_Player, msg->m_Data, msg->m_Turn);
}
void CNetServerTurnManager::PostCommand(CScriptValRooted data)
{
#ifdef NETTURN_LOG
NETTURN_LOG(L"PostCommand()\n");
#endif
// Transmit command to all clients
CSimulationMessage msg(m_Simulation2.GetScriptInterface(), m_ClientId, m_PlayerId, m_CurrentTurn + COMMAND_DELAY, data.get());
m_NetServer.Broadcast(&msg);
// Add to our local queue
AddCommand(m_ClientId, m_PlayerId, data, m_CurrentTurn + COMMAND_DELAY);
}
void CNetServerTurnManager::NotifyFinishedOwnCommands(u32 turn)
{
#ifdef NETTURN_LOG
NETTURN_LOG(L"NotifyFinishedOwnCommands(%d)\n", turn);
#endif
NotifyFinishedClientCommands(m_ClientId, turn);
}
void CNetServerTurnManager::NotifyFinishedClientCommands(int client, u32 turn)
{
#ifdef NETTURN_LOG
NETTURN_LOG(L"NotifyFinishedClientCommands(client=%d, turn=%d)\n", client, turn);
#endif
// Must be a client we've already heard of
debug_assert(m_ClientsReady.find(client) != m_ClientsReady.end());
// Clients must advance one turn at a time
debug_assert(turn == m_ClientsReady[client] + 1);
m_ClientsReady[client] = turn;
// See if all clients (including self) are ready for a new turn
for (std::map<int, u32>::iterator it = m_ClientsReady.begin(); it != m_ClientsReady.end(); ++it)
{
#ifdef NETTURN_LOG
NETTURN_LOG(L" %d: %d <=? %d\n", it->first, it->second, m_ReadyTurn);
#endif
if (it->second <= m_ReadyTurn)
return;
}
// Tell all clients that the next turn is ready
CEndCommandBatchMessage msg;
msg.m_TurnLength = TURN_LENGTH;
msg.m_Turn = turn;
m_NetServer.Broadcast(&msg);
// Move ourselves to the next turn
FinishedAllCommands(m_ReadyTurn + 1);
}
void CNetServerTurnManager::InitialiseClient(int client)
{
debug_assert(m_ClientsReady.find(client) == m_ClientsReady.end());
m_ClientsReady[client] = 1;
// TODO: do we need some kind of UninitialiseClient in case they leave?
}
void CNetServerTurnManager::OnSimulationMessage(CSimulationMessage* msg)
{
// Send it back to all clients immediately
m_NetServer.Broadcast(msg);
// TODO: we should do some validation of ownership (clients can't send commands on behalf of opposing players)
// TODO: we shouldn't send the message back to the client that first sent it
// Process it ourselves
AddCommand(msg->m_Client, msg->m_Player, msg->m_Data, msg->m_Turn);
}
void CNetLocalTurnManager::PostCommand(CScriptValRooted data)
{
// Add directly to the next turn, ignoring COMMAND_DELAY,
// because we don't need to compensate for network latency
AddCommand(m_ClientId, m_PlayerId, data, m_CurrentTurn + 1);
}
void CNetLocalTurnManager::NotifyFinishedOwnCommands(u32 turn)
{
FinishedAllCommands(turn);
}
void CNetLocalTurnManager::OnSimulationMessage(CSimulationMessage* UNUSED(msg))
{
debug_warn(L"This should never be called");
}

View File

@ -0,0 +1,177 @@
/* Copyright (C) 2010 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef INCLUDED_NETTURNMANAGER
#define INCLUDED_NETTURNMANAGER
#include "simulation2/helpers/SimulationCommand.h"
class CNetServer;
class CNetClient;
class CSimulationMessage;
class CSimulation2;
/*
* This file deals with the logic of the network turn system. The basic idea is as in
* http://www.gamasutra.com/view/feature/3094/1500_archers_on_a_288_network_.php?print=1
*
* Each player performs the simulation for turn N.
* User input is translated into commands scheduled for execution in turn N+2 which are
* distributed to all other clients.
* After a while, a player want to perform the simulation for turn N+1,
* which first requires that it has all the other clients' commands for turn N+1.
* In that case, it does the simulation and tells all the other clients it has finished
* sending commands for turn N+2, and it starts sending commands for turn N+3.
*
* Commands are redistributed immediately by the server.
* To ensure a consistent execution of commands, they are each associated with a
* client session ID (which is globally unique and consistent), which is used to sort them.
*/
/**
* Common network turn system (used by clients, servers, and offline games).
*/
class CNetTurnManager
{
NONCOPYABLE(CNetTurnManager);
public:
/**
* Construct for a given player ID, and a given network session ID.
*/
CNetTurnManager(CSimulation2& simulation, int playerId, int clientId);
virtual ~CNetTurnManager() { }
/**
* Advance the simulation by a certain time. If this brings us past the current
* turn length, the next turn is processed and the function returns true.
* Otherwise, nothing happens and it returns false.
* @param frameLength length of the previous frame in seconds
*/
bool Update(float frameLength);
/**
* Advance the graphics by a certain time.
* @param frameLength length of the previous frame in seconds
*/
void Interpolate(float frameLength);
/**
* Called by networking code when a simulation message is received.
*/
virtual void OnSimulationMessage(CSimulationMessage* msg) = 0;
/**
* Called by simulation code, to add a new command to be distributed to all clients and executed soon.
*/
virtual void PostCommand(CScriptValRooted data) = 0;
/**
* Called when all commands for a given turn have been received.
* This allows Update to progress to that turn.
*/
void FinishedAllCommands(u32 turn);
protected:
/**
* Store a command to be executed at a given turn.
*/
void AddCommand(int client, int player, CScriptValRooted data, u32 turn);
/**
* Called when this client has finished sending all its commands scheduled for the given turn.
*/
virtual void NotifyFinishedOwnCommands(u32 turn) = 0;
CSimulation2& m_Simulation2;
/// The turn that we have most recently executed
u32 m_CurrentTurn;
/// The latest turn for which we have received all commands from all clients
u32 m_ReadyTurn;
/// Commands queued at each turn (index 0 is for m_CurrentTurn+1)
std::deque<std::map<u32, std::vector<SimulationCommand> > > m_QueuedCommands;
int m_PlayerId;
uint m_ClientId;
/// Time remaining until we ought to execute the next turn
float m_DeltaTime;
};
class CNetClientTurnManager : public CNetTurnManager
{
public:
CNetClientTurnManager(CSimulation2& simulation, CNetClient& client, int playerId, int clientId) :
CNetTurnManager(simulation, playerId, clientId), m_NetClient(client)
{
}
virtual void OnSimulationMessage(CSimulationMessage* msg);
virtual void PostCommand(CScriptValRooted data);
protected:
virtual void NotifyFinishedOwnCommands(u32 turn);
CNetClient& m_NetClient;
};
class CNetServerTurnManager : public CNetTurnManager
{
public:
CNetServerTurnManager(CSimulation2& simulation, CNetServer& server, int playerId, int clientId) :
CNetTurnManager(simulation, playerId, clientId), m_NetServer(server)
{
}
virtual void OnSimulationMessage(CSimulationMessage* msg);
virtual void PostCommand(CScriptValRooted data);
void NotifyFinishedClientCommands(int client, u32 turn);
void InitialiseClient(int client);
protected:
virtual void NotifyFinishedOwnCommands(u32 turn);
// Client ID -> ready turn number (the latest turn for which all commands have been received)
std::map<int, u32> m_ClientsReady;
CNetServer& m_NetServer;
};
class CNetLocalTurnManager : public CNetTurnManager
{
public:
CNetLocalTurnManager(CSimulation2& simulation) :
CNetTurnManager(simulation, 1, 0)
{
}
virtual void OnSimulationMessage(CSimulationMessage* msg);
virtual void PostCommand(CScriptValRooted data);
protected:
virtual void NotifyFinishedOwnCommands(u32 turn);
};
#endif // INCLUDED_NETTURNMANAGER

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2009 Wildfire Games.
/* Copyright (C) 2010 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -84,6 +84,8 @@ CGame::CGame():
if (g_UseSimulation2)
{
m_TurnManager = new CNetLocalTurnManager(*m_Simulation2); // this will get replaced if we're a net server/client
m_Simulation2->LoadDefaultScripts();
m_Simulation2->ResetState();
@ -105,12 +107,19 @@ CGame::~CGame()
// Again, the in-game call tree is going to be different to the main menu one.
g_Profiler.StructuralReset();
delete m_TurnManager;
delete m_GameView;
delete m_Simulation2;
delete m_Simulation;
delete m_World;
}
void CGame::SetTurnManager(CNetTurnManager* turnManager)
{
if (m_TurnManager)
delete m_TurnManager;
m_TurnManager = turnManager;
}
/**
@ -145,11 +154,12 @@ PSRETURN CGame::RegisterInit(CGameAttributes* pAttribs)
PSRETURN CGame::ReallyStartGame()
{
// Call the reallyStartGame GUI function, but only if it exists
if (g_GUI->HasPages())
{
jsval fval, rval;
JSBool ok = JS_GetProperty(g_ScriptingHost.getContext(), g_GUI->GetScriptObject(), "reallyStartGame", &fval);
debug_assert(ok);
if (ok && !JSVAL_IS_VOID(fval))
{
ok = JS_CallFunctionValue(g_ScriptingHost.getContext(), g_GUI->GetScriptObject(), fval, 0, NULL, &rval);
}
@ -162,9 +172,6 @@ PSRETURN CGame::ReallyStartGame()
// Mark terrain as modified so the minimap can repaint (is there a cleaner way of handling this?)
g_GameRestarted = true;
g_GUI->SendEventToAll("sessionstart");
return 0;
}
@ -240,20 +247,25 @@ bool CGame::Update(double deltaTime, bool doInterpolate)
deltaTime *= m_SimRate;
bool ok = true;
if (deltaTime)
{
if (g_UseSimulation2)
{
if (m_Simulation2->Update(deltaTime))
PROFILE("update");
if (m_TurnManager->Update(deltaTime))
g_GUI->SendEventToAll("SimulationUpdate");
}
else
{
ok = m_Simulation->Update(deltaTime);
}
}
if (doInterpolate)
{
PROFILE("interpolate");
if (g_UseSimulation2)
m_Simulation2->Interpolate(deltaTime);
m_TurnManager->Interpolate(deltaTime);
else
m_Simulation->Interpolate(deltaTime);
}
@ -272,6 +284,12 @@ bool CGame::Update(double deltaTime, bool doInterpolate)
return ok;
}
void CGame::Interpolate(float frameLength)
{
m_TurnManager->Interpolate(frameLength);
}
/**
* Test player statistics and update game status as required.
*

View File

@ -34,6 +34,7 @@ class CGameView;
class CSimulation;
class CPlayer;
class CGameAttributes;
class CNetTurnManager;
/**
* Default player limit (not counting the Gaia player)
@ -98,6 +99,8 @@ class CGame
EOG_WIN /// Game is over, local player wins
} GameStatus;
CNetTurnManager* m_TurnManager;
public:
CGame();
~CGame();
@ -121,6 +124,8 @@ public:
*/
bool Update(double deltaTime, bool doInterpolate = true);
void Interpolate(float frameLength);
void UpdateGameStatus();
void EndGame();
@ -217,6 +222,17 @@ public:
inline float GetSimRate() const
{ return m_SimRate; }
/**
* Replace the current turn manager.
* This class will take ownership of the pointer.
*/
void SetTurnManager(CNetTurnManager* turnManager);
CNetTurnManager* GetTurnManager() const
{
return m_TurnManager;
}
private:
PSRETURN RegisterInit(CGameAttributes* pAttribs);
};

View File

@ -122,9 +122,6 @@ static void ProcessCommandLineArgs(const CmdLineArgs& args)
// TODO: all these options (and the ones processed elsewhere) should
// be documented somewhere for users.
if (args.Has("autostart"))
g_AutostartMap = args.Get("autostart");
if (args.Has("buildarchive"))
{
// note: VFS init is sure to have been completed by now

View File

@ -63,9 +63,6 @@ extern bool g_VSync;
extern bool g_Quickstart;
// If non-empty, specified map will be automatically loaded
extern CStr g_AutostartMap;
extern CStrW g_CursorName;
class CmdLineArgs;

View File

@ -550,10 +550,16 @@ static void InitVfs(const CmdLineArgs& args)
}
static void InitPs(bool setup_gui)
static void InitPs(bool setup_gui, const CStrW& gui_page)
{
if (setup_gui)
if (!setup_gui)
{
// We do actually need *some* kind of GUI loaded, so use the
// (currently empty) Atlas one
g_GUI->SwitchPage(L"page_atlas.xml", JSVAL_VOID);
return;
}
// The things here aren't strictly GUI, but they're unnecessary when in Atlas
// because the game doesn't draw any text or handle keys or anything
@ -584,10 +590,7 @@ static void InitPs(bool setup_gui)
}
// GUI uses VFS, so this must come after VFS init.
if (g_AutostartMap.empty())
g_GUI->SwitchPage(L"page_pregame.xml", JSVAL_VOID);
else
g_GUI->SwitchPage(L"page_session_new.xml", JSVAL_VOID);
g_GUI->SwitchPage(gui_page, JSVAL_VOID);
// Warn nicely about missing S3TC support
if (!ogl_tex_has_s3tc())
@ -605,13 +608,6 @@ static void InitPs(bool setup_gui)
);
}
}
else
{
// We do actually need *some* kind of GUI loaded, so use the
// (currently empty) Atlas one
g_GUI->SwitchPage(L"page_atlas.xml", JSVAL_VOID);
}
}
static void InitInput()
@ -643,8 +639,7 @@ static void ShutdownPs()
{
SAFE_DELETE(g_GUI);
delete g_Console;
g_Console = 0;
SAFE_DELETE(g_Console);
// disable the special Windows cursor, or free textures for OGL cursors
cursor_draw(0, g_mouse_x, g_mouse_y);
@ -716,19 +711,9 @@ static void InitSDL()
void EndGame()
{
if (g_NetServer)
{
delete g_NetServer;
g_NetServer=NULL;
}
else if (g_NetClient)
{
delete g_NetClient;
g_NetClient=NULL;
}
delete g_Game;
g_Game=NULL;
SAFE_DELETE(g_NetServer);
SAFE_DELETE(g_NetClient);
SAFE_DELETE(g_Game);
}
@ -851,6 +836,8 @@ void EarlyInit()
srand(time(NULL)); // NOTE: this rand should *not* be used for simulation!
}
static bool Autostart(const CmdLineArgs& args);
void Init(const CmdLineArgs& args, int flags)
{
const bool setup_vmode = (flags & INIT_HAVE_VMODE) == 0;
@ -1005,9 +992,6 @@ void Init(const CmdLineArgs& args, int flags)
pwglSwapIntervalEXT(g_VSync? 1 : 0);
#endif
MICROLOG(L"init ps");
InitPs(setup_gui);
ogl_WarnIfError();
InitRenderer();
@ -1040,31 +1024,142 @@ void Init(const CmdLineArgs& args, int flags)
ogl_WarnIfError();
if (! g_AutostartMap.empty())
if (!Autostart(args))
{
// Code copied mostly from atlas/GameInterface/Handlers/Map.cpp -
// maybe should be refactored to avoid duplication
g_GameAttributes.m_MapFile = g_AutostartMap+".pmp";
// Make the whole world visible
g_GameAttributes.m_LOSSetting = LOS_SETTING_ALL_VISIBLE;
g_GameAttributes.m_FogOfWar = false;
for (int i = 1; i < 8; ++i)
g_GameAttributes.GetSlot(i)->AssignLocal();
g_Game = new CGame();
PSRETURN ret = g_Game->StartGame(&g_GameAttributes);
debug_assert(ret == PSRETURN_OK);
LDR_NonprogressiveLoad();
ret = g_Game->ReallyStartGame();
debug_assert(ret == PSRETURN_OK);
InitPs(setup_gui, L"page_pregame.xml");
}
}
void RenderGui(bool RenderingState)
{
g_DoRenderGui = RenderingState;
}
// Network autostart:
class AutostartNetServer : public CNetServer
{
public:
AutostartNetServer(CGame *pGame, CGameAttributes *pGameAttributes, int maxPlayers) :
CNetServer(pGame, pGameAttributes), m_NumPlayers(1), m_MaxPlayers(maxPlayers)
{
}
protected:
virtual void OnPlayerJoin(CNetSession* pSession)
{
for (size_t slot = 0; slot < g_GameAttributes.GetSlotCount(); ++slot)
{
if (g_GameAttributes.GetSlot(slot)->GetAssignment() == SLOT_OPEN)
{
g_GameAttributes.GetSlot(slot)->AssignToSession(pSession);
break;
}
}
m_NumPlayers++;
debug_printf(L"# player joined (got %d, need %d)\n", (int)m_NumPlayers, (int)m_MaxPlayers);
if (m_NumPlayers >= m_MaxPlayers)
{
g_GUI->SwitchPage(L"page_loading.xml", JSVAL_VOID);
int ret = StartGame();
debug_assert(ret == 0);
}
}
virtual void OnPlayerLeave(CNetSession* UNUSED(pSession))
{
debug_warn(L"client left?!");
m_NumPlayers--;
}
private:
size_t m_NumPlayers;
size_t m_MaxPlayers;
};
class AutostartNetClient : public CNetClient
{
public:
AutostartNetClient(CGame *pGame, CGameAttributes *pGameAttributes) :
CNetClient(pGame, pGameAttributes)
{
}
protected:
virtual void OnConnectComplete()
{
debug_printf(L"# connect complete\n");
}
virtual void OnStartGame()
{
g_GUI->SwitchPage(L"page_loading.xml", JSVAL_VOID);
int ret = StartGame();
debug_assert(ret == 0);
}
};
static bool Autostart(const CmdLineArgs& args)
{
/*
* Handle various command-line options, for quick testing of various features:
* -autostart=mapname -- single-player
* -autostart=mapname -autostart-playername=Player -autostart-host -autostart-players=2 -- multiplayer host, wait for 2 players
* -autostart=mapname -autostart-playername=Player -autostart-client -autostart-ip=127.0.0.1 -- multiplayer client, connect to 127.0.0.1
*/
CStr autostartMap = args.Get("autostart");
if (autostartMap.empty())
return false;
g_Game = new CGame();
g_GameAttributes.m_MapFile = autostartMap + ".pmp";
// Make the whole world visible
g_GameAttributes.m_LOSSetting = LOS_SETTING_ALL_VISIBLE;
g_GameAttributes.m_FogOfWar = false;
if (args.Has("autostart-host"))
{
InitPs(true, L"page_loading.xml");
size_t maxPlayers = 2;
if (args.Has("autostart-players"))
maxPlayers = args.Get("autostart-players").ToUInt();
g_NetServer = new AutostartNetServer(g_Game, &g_GameAttributes, maxPlayers);
// TODO: player name, etc
bool ok = g_NetServer->Start(NULL, 0, NULL);
debug_assert(ok);
}
else if (args.Has("autostart-client"))
{
InitPs(true, L"page_loading.xml");
bool ok;
g_NetClient = new AutostartNetClient(g_Game, &g_GameAttributes);
// TODO: player name, etc
ok = g_NetClient->Create();
debug_assert(ok);
ok = g_NetClient->Connect(args.Get("autostart-ip"), DEFAULT_HOST_PORT);
debug_assert(ok);
}
else
{
for (int i = 1; i < 8; ++i)
g_GameAttributes.GetSlot(i)->AssignLocal();
PSRETURN ret = g_Game->StartGame(&g_GameAttributes);
debug_assert(ret == PSRETURN_OK);
LDR_NonprogressiveLoad();
ret = g_Game->ReallyStartGame();
debug_assert(ret == PSRETURN_OK);
InitPs(true, g_UseSimulation2 ? L"page_session_new.xml" : L"page_session.xml");
}
return true;
}

View File

@ -166,15 +166,6 @@ jsval ScriptingHost::ExecuteScript(const CStrW& script, const CStrW& calledFrom,
return rval;
}
// unused
void ScriptingHost::RegisterFunction(const std::string & functionName, JSNative function, int numArgs)
{
JSFunction * func = JS_DefineFunction(m_Context, m_GlobalObject, functionName.c_str(), function, numArgs, 0);
if (func == NULL)
throw PSERROR_Scripting_RegisterFunctionFailed();
}
void ScriptingHost::DefineConstant(const std::string & name, int value)
{
// First remove this constant if it already exists

View File

@ -109,8 +109,6 @@ public:
jsval ExecuteScript(const CStrW& script, const CStrW& calledFrom = L"Console", JSObject* contextObject = NULL );
void RegisterFunction(const std::string & functionName, JSNative function, int numArgs);
void DefineConstant(const std::string & name, int value);
void DefineCustomObjectType(JSClass *clasp, JSNative constructor, uintN nargs, JSPropertySpec *ps, JSFunctionSpec *fs, JSPropertySpec *static_ps, JSFunctionSpec *static_fs);

View File

@ -37,5 +37,6 @@
class ScriptInterface;
class CScriptVal;
class CScriptValRooted;
#endif // INCLUDED_SCRIPTTYPES

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2009 Wildfire Games.
/* Copyright (C) 2010 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -84,15 +84,15 @@ void CTurnManager::SendBatch(uintptr_t batch)
MsgVector::iterator it=messages.begin();
while (it != messages.end())
{
SendMessage(it->m_pMessage, it->m_ClientMask);
SendMessageMasked(it->m_pMessage, it->m_ClientMask);
++it;
}
CEndCommandBatchMessage *pMsg=new CEndCommandBatchMessage();
pMsg->m_TurnLength=m_Batches[batch].m_TurnLength;
SendMessage(pMsg, (size_t)-1);
SendMessageMasked(pMsg, (size_t)-1);
}
void CTurnManager::SendMessage(CNetMessage *pMsg, size_t clientMask)
void CTurnManager::SendMessageMasked(CNetMessage *pMsg, size_t clientMask)
{
for (size_t i=0;i<m_Clients.size();i++)
{

View File

@ -83,7 +83,7 @@ private:
CGameRecord *m_pRecord;
protected:
public:
// Rotate the three batches: {0, 1, 2} => {1, 2, 0}
void RotateBatches();
@ -97,7 +97,7 @@ protected:
// void UpdateTimingData(size_t client, int fps, int currentLatency);
void SetTurnLength(uintptr_t batch, int turnLength);
void SendMessage(CNetMessage *pMsg, uintptr_t clientMask);
void SendMessageMasked(CNetMessage *pMsg, uintptr_t clientMask);
// Add the message to the specified batch. The message is assumed to be
// validated before passed here, and will be blindly trusted.

View File

@ -27,9 +27,14 @@
#include "simulation2/components/ICmpCommandQueue.h"
#include "lib/file/file_system_util.h"
#include "lib/utf8.h"
#include "maths/MathUtil.h"
#include "ps/CLogger.h"
#include "ps/Filesystem.h"
#include "ps/Profile.h"
#include "ps/Pyrogenesis.h"
#include <iomanip>
bool g_UseSimulation2 = true;
@ -37,12 +42,14 @@ class CSimulation2Impl
{
public:
CSimulation2Impl(CUnitManager* unitManager, CTerrain* terrain) :
m_SimContext(), m_ComponentManager(m_SimContext)
m_SimContext(), m_ComponentManager(m_SimContext), m_EnableOOSLog(false)
{
m_SimContext.m_UnitManager = unitManager;
m_SimContext.m_Terrain = terrain;
m_ComponentManager.LoadComponentTypes();
// m_EnableOOSLog = true; // TODO: this should be a command-line flag or similar
// (can't call ResetState here since the scripts haven't been loaded yet)
}
@ -86,8 +93,10 @@ public:
bool LoadScripts(const VfsPath& path);
LibError ReloadChangedFile(const VfsPath& path);
bool Update(float frameTime);
void Interpolate(float frameTime);
bool Update(int turnLength, const std::vector<SimulationCommand>& commands);
void Interpolate(float frameLength, float frameOffset);
void DumpState();
CSimContext m_SimContext;
CComponentManager m_ComponentManager;
@ -99,7 +108,7 @@ public:
uint32_t m_TurnNumber;
static const int TURN_LENGTH = 300; // TODO: Use CTurnManager
bool m_EnableOOSLog;
};
bool CSimulation2Impl::LoadScripts(const VfsPath& path)
@ -141,15 +150,9 @@ LibError CSimulation2Impl::ReloadChangedFile(const VfsPath& path)
return INFO::OK;
}
bool CSimulation2Impl::Update(float frameTime)
bool CSimulation2Impl::Update(int turnLength, const std::vector<SimulationCommand>& commands)
{
// TODO: Use CTurnManager
m_DeltaTime += frameTime;
if (m_DeltaTime >= 0.0)
{
double turnLength = TURN_LENGTH / 1000.0;
fixed turnLengthFixed = fixed::FromInt(TURN_LENGTH) / 1000;
m_DeltaTime -= turnLength;
fixed turnLengthFixed = fixed::FromInt(turnLength) / 1000;
CMessageTurnStart msgTurnStart;
m_ComponentManager.BroadcastMessage(msgTurnStart);
@ -159,7 +162,7 @@ bool CSimulation2Impl::Update(float frameTime)
CmpPtr<ICmpCommandQueue> cmpCommandQueue(m_SimContext, SYSTEM_ENTITY);
if (!cmpCommandQueue.null())
cmpCommandQueue->ProcessCommands();
cmpCommandQueue->FlushTurn(commands);
CMessageUpdate msgUpdate(turnLengthFixed);
m_ComponentManager.BroadcastMessage(msgUpdate);
@ -170,22 +173,42 @@ bool CSimulation2Impl::Update(float frameTime)
// if (m_TurnNumber == 0)
// m_ComponentManager.GetScriptInterface().DumpHeap();
if (m_EnableOOSLog)
DumpState();
++m_TurnNumber;
return true;
}
return false;
return true; // TODO: don't bother with bool return
}
void CSimulation2Impl::Interpolate(float frameTime)
void CSimulation2Impl::Interpolate(float frameLength, float frameOffset)
{
// TODO: Use CTurnManager
double turnLength = TURN_LENGTH / 1000.0;
float offset = clamp(m_DeltaTime / turnLength + 1.0, 0.0, 1.0);
CMessageInterpolate msg(frameTime, offset);
CMessageInterpolate msg(frameLength, frameOffset);
m_ComponentManager.BroadcastMessage(msg);
}
void CSimulation2Impl::DumpState()
{
PROFILE("DumpState");
std::wstringstream name;
name << L"sim_log/" << getpid() << L"/" << std::setw(5) << std::setfill(L'0') << m_TurnNumber << L".txt";
fs::wpath path (psLogDir()/name.str());
CreateDirectories(path.branch_path(), 0700);
std::ofstream file (path.external_file_string().c_str(), std::ofstream::out | std::ofstream::trunc);
file << "State hash: " << std::hex;
std::string hashRaw;
m_ComponentManager.ComputeStateHash(hashRaw);
for (size_t i = 0; i < hashRaw.size(); ++i)
file << std::setfill('0') << std::setw(2) << (int)(unsigned char)hashRaw[i];
file << std::dec << "\n";
file << "\n";
m_ComponentManager.DumpDebugState(file);
}
////////////////////////////////////////////////////////////////
CSimulation2::CSimulation2(CUnitManager* unitManager, CTerrain* terrain) :
@ -200,6 +223,11 @@ CSimulation2::~CSimulation2()
// Forward all method calls to the appropriate CSimulation2Impl/CComponentManager methods:
void CSimulation2::EnableOOSLog()
{
m->m_EnableOOSLog = true;
}
entity_id_t CSimulation2::AddEntity(const std::wstring& templateName)
{
return m->m_ComponentManager.AddEntity(templateName, m->m_ComponentManager.AllocateNewEntity());
@ -261,14 +289,20 @@ void CSimulation2::InitGame(const CScriptVal& data)
GetScriptInterface().CallFunction(GetScriptInterface().GetGlobalObject(), "InitGame", data, ret);
}
bool CSimulation2::Update(float frameTime)
bool CSimulation2::Update(int turnLength)
{
return m->Update(frameTime);
std::vector<SimulationCommand> commands;
return m->Update(turnLength, commands);
}
void CSimulation2::Interpolate(float frameTime)
bool CSimulation2::Update(int turnLength, const std::vector<SimulationCommand>& commands)
{
m->Interpolate(frameTime);
return m->Update(turnLength, commands);
}
void CSimulation2::Interpolate(float frameLength, float frameOffset)
{
m->Interpolate(frameLength, frameOffset);
}
void CSimulation2::RenderSubmit(SceneCollector& collector, const CFrustum& frustum, bool culling)

View File

@ -20,6 +20,8 @@
#include "simulation2/system/CmpPtr.h"
#include "simulation2/system/Components.h"
#include "simulation2/helpers/SimulationCommand.h"
#include "scriptinterface/ScriptVal.h"
#include "lib/file/vfs/vfs_path.h"
@ -32,7 +34,6 @@ class CTerrain;
class IComponent;
class ScriptInterface;
class CMessage;
class CScriptVal;
class SceneCollector;
class CFrustum;
@ -41,6 +42,7 @@ extern bool g_UseSimulation2;
/**
* Public API for simulation system.
* Most code should interact with the simulation only through this API.
*/
class CSimulation2
{
@ -50,6 +52,8 @@ public:
CSimulation2(CUnitManager*, CTerrain*);
~CSimulation2();
void EnableOOSLog();
/**
* Load all scripts in the specified directory (non-recursively),
* so they can register new component types and functions. This
@ -89,8 +93,9 @@ public:
*/
void InitGame(const CScriptVal& data);
bool Update(float frameTime);
void Interpolate(float frameTime);
bool Update(int turnLength);
bool Update(int turnLength, const std::vector<SimulationCommand>& commands);
void Interpolate(float frameLength, float frameOffset);
void RenderSubmit(SceneCollector& collector, const CFrustum& frustum, bool culling);
/**

View File

@ -21,6 +21,8 @@
#include "ICmpCommandQueue.h"
#include "ps/CLogger.h"
#include "ps/Game.h"
#include "network/NetTurnManager.h"
class CCmpCommandQueue : public ICmpCommandQueue
{
@ -31,13 +33,7 @@ public:
DEFAULT_COMPONENT_ALLOCATOR(CommandQueue)
struct Command
{
int player;
CScriptValRooted data;
};
std::vector<Command> m_CmdQueue;
std::vector<SimulationCommand> m_LocalQueue;
static std::string GetSchema()
{
@ -54,11 +50,11 @@ public:
virtual void Serialize(ISerializer& serialize)
{
serialize.NumberU32_Unbounded("num commands", (u32)m_CmdQueue.size());
for (size_t i = 0; i < m_CmdQueue.size(); ++i)
serialize.NumberU32_Unbounded("num commands", (u32)m_LocalQueue.size());
for (size_t i = 0; i < m_LocalQueue.size(); ++i)
{
serialize.NumberI32_Unbounded("player", m_CmdQueue[i].player);
serialize.ScriptVal("data", m_CmdQueue[i].data.get());
serialize.NumberI32_Unbounded("player", m_LocalQueue[i].player);
serialize.ScriptVal("data", m_LocalQueue[i].data.get());
}
}
@ -74,31 +70,47 @@ public:
jsval data;
deserialize.NumberI32_Unbounded(player);
deserialize.ScriptVal(data);
Command c = { player, CScriptValRooted(cx, data) };
m_CmdQueue.push_back(c);
SimulationCommand c = { player, CScriptValRooted(cx, data) };
m_LocalQueue.push_back(c);
}
}
virtual void PushClientCommand(int player, CScriptVal cmd)
virtual void PushLocalCommand(int player, CScriptVal cmd)
{
JSContext* cx = GetSimContext().GetScriptInterface().GetContext();
Command c = { player, CScriptValRooted(cx, cmd) };
m_CmdQueue.push_back(c);
SimulationCommand c = { player, CScriptValRooted(cx, cmd) };
m_LocalQueue.push_back(c);
}
virtual void ProcessCommands()
virtual void PostNetworkCommand(CScriptVal cmd)
{
JSContext* cx = GetSimContext().GetScriptInterface().GetContext();
// TODO: would be nicer to not use globals
g_Game->GetTurnManager()->PostCommand(CScriptValRooted(cx, cmd));
}
virtual void FlushTurn(const std::vector<SimulationCommand>& commands)
{
ScriptInterface& scriptInterface = GetSimContext().GetScriptInterface();
for (size_t i = 0; i < m_CmdQueue.size(); ++i)
std::vector<SimulationCommand> localCommands;
m_LocalQueue.swap(localCommands);
for (size_t i = 0; i < localCommands.size(); ++i)
{
bool ok = scriptInterface.CallFunctionVoid(scriptInterface.GetGlobalObject(), "ProcessCommand", m_CmdQueue[i].player, m_CmdQueue[i].data);
bool ok = scriptInterface.CallFunctionVoid(scriptInterface.GetGlobalObject(), "ProcessCommand", localCommands[i].player, localCommands[i].data);
if (!ok)
LOGERROR(L"Failed to call ProcessCommand() global script function");
}
m_CmdQueue.clear();
for (size_t i = 0; i < commands.size(); ++i)
{
bool ok = scriptInterface.CallFunctionVoid(scriptInterface.GetGlobalObject(), "ProcessCommand", commands[i].player, commands[i].data);
if (!ok)
LOGERROR(L"Failed to call ProcessCommand() global script function");
}
}
};

View File

@ -22,6 +22,7 @@
#include "simulation2/system/InterfaceScripted.h"
BEGIN_INTERFACE_WRAPPER(CommandQueue)
DEFINE_INTERFACE_METHOD_2("PushClientCommand", void, ICmpCommandQueue, PushClientCommand, int, CScriptVal)
// Excluded: ProcessCommands (doesn't make sense for scripts to call it)
DEFINE_INTERFACE_METHOD_2("PushLocalCommand", void, ICmpCommandQueue, PushLocalCommand, int, CScriptVal)
DEFINE_INTERFACE_METHOD_1("PostNetworkCommand", void, ICmpCommandQueue, PostNetworkCommand, CScriptVal)
// Excluded: FlushTurn (doesn't make sense for scripts to call it)
END_INTERFACE_WRAPPER(CommandQueue)

View File

@ -20,35 +20,38 @@
#include "simulation2/system/Interface.h"
#include "simulation2/helpers/SimulationCommand.h"
/**
* Command queue, for sending orders to entities.
* Each command is associated with a player ID (who triggered the command, in some sense)
* and an arbitrary script value.
* Commands can be added to the queue at any time, and will all be executed at the start
* of the next turn.
*
* Typically commands will be sent by the GUI to the networking system, which will eventually
* add them to this queue; the player ID identifies the client who initiated the action, and
* the command processing code ought to check the player has permission to perform that command
* (e.g. make sure they only move their own units). The networking system will add a few turns of
* latency before the commands reach the queue.
* Commands can be added to the local queue at any time, and will all be executed at the start
* of the next turn. (This will typically be used by AI scripts.)
*
* Alternatively, commands may be added directly to the queue by AI scripts, to emulate a player,
* in which case the ID identifies the AI player and the network is not involved.
* Alternatively, commands can be sent to the networking system, and they will be executed
* at the start of some later turn by all players simultaneously. (This will typically be
* used for user inputs.)
*/
class ICmpCommandQueue : public IComponent
{
public:
/**
* Pushes a new command onto the queue. @p cmd does not need to be rooted.
* Pushes a new command onto the local queue. @p cmd does not need to be rooted.
*/
virtual void PushClientCommand(int player, CScriptVal cmd) = 0;
virtual void PushLocalCommand(int player, CScriptVal cmd) = 0;
/**
* Calls the ProcessCommand(player, cmd) global script function for each command in the queue,
* in order of adding to the queue, and empties the queue.
* Send a command associated with the current player to the networking system.
*/
virtual void ProcessCommands() = 0;
virtual void PostNetworkCommand(CScriptVal cmd) = 0;
/**
* Calls the ProcessCommand(player, cmd) global script function for each command in the
* local queue and in @p commands, and empties the local queue.
*/
virtual void FlushTurn(const std::vector<SimulationCommand>& commands) = 0;
DECLARE_INTERFACE_TYPE(CommandQueue)
};

View File

@ -36,6 +36,8 @@ public:
{
ComponentTestHelper test;
std::vector<SimulationCommand> empty;
ICmpCommandQueue* cmp = test.Add<ICmpCommandQueue>(CID_CommandQueue, "");
TS_ASSERT(test.GetScriptInterface().Eval("var cmds = []; function ProcessCommand(player, cmd) { cmds.push([player, cmd]); }"));
@ -43,24 +45,24 @@ public:
CScriptVal cmd;
TS_ASSERT(test.GetScriptInterface().Eval("([1,2,3])", cmd));
cmp->PushClientCommand(1, cmd);
cmp->PushLocalCommand(1, cmd);
TS_ASSERT(test.GetScriptInterface().Eval("({x:4})", cmd));
cmp->PushClientCommand(-1, cmd);
cmp->PushLocalCommand(-1, cmd);
test.Roundtrip();
// Process the first two commands
cmp->ProcessCommands();
cmp->FlushTurn(empty);
TS_ASSERT(test.GetScriptInterface().Eval("({y:5})", cmd));
cmp->PushClientCommand(10, cmd);
cmp->PushLocalCommand(10, cmd);
// Process the next command
cmp->ProcessCommands();
cmp->FlushTurn(empty);
// Process no commands
cmp->ProcessCommands();
cmp->FlushTurn(empty);
test.Roundtrip();

View File

@ -0,0 +1,32 @@
/* Copyright (C) 2010 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef INCLUDED_SIMULATIONCOMMAND
#define INCLUDED_SIMULATIONCOMMAND
#include "scriptinterface/ScriptVal.h"
/**
* Simulation command, typically received over the network in multiplayer games.
*/
struct SimulationCommand
{
int player;
CScriptValRooted data;
};
#endif // INCLUDED_SIMULATIONCOMMAND

View File

@ -116,6 +116,11 @@ void ISerializer::ScriptVal(const char* name, CScriptVal value)
PutScriptVal(name, value.get());
}
void ISerializer::ScriptVal(const char* name, CScriptValRooted value)
{
PutScriptVal(name, value.get());
}
void ISerializer::RawBytes(const char* name, const u8* data, size_t len)
{
Put(name, data, len);

View File

@ -184,6 +184,12 @@ public:
*/
void ScriptVal(const char* name, CScriptVal value);
/**
* Serialize a CScriptValRooted.
* The value must not contain any unserializable values (like functions).
*/
void ScriptVal(const char* name, CScriptValRooted value);
/**
* Serialize a stream of bytes.
* It is the caller's responsibility to deal with portability (padding, endianness, etc);

View File

@ -185,6 +185,11 @@ void CStdDeserializer::ScriptVal(CScriptVal& out)
out = ReadScriptVal(NULL);
}
void CStdDeserializer::ScriptVal(CScriptValRooted& out)
{
out = CScriptValRooted(m_ScriptInterface.GetContext(), ReadScriptVal(NULL));
}
void CStdDeserializer::ScriptObjectAppend(jsval& obj)
{
if (!JSVAL_IS_OBJECT(obj))

View File

@ -31,6 +31,7 @@ public:
virtual void ScriptVal(jsval& out);
virtual void ScriptVal(CScriptVal& out);
virtual void ScriptVal(CScriptValRooted& out);
virtual void ScriptObjectAppend(jsval& obj);
virtual void ScriptString(JSString*& out);

View File

@ -305,8 +305,8 @@ void ActorViewer::Render()
void ActorViewer::Update(float dt)
{
m.Simulation2.Update(dt);
m.Simulation2.Interpolate(dt);
m.Simulation2.Update((int)(dt*1000));
m.Simulation2.Interpolate(dt, 0);
if (m.WalkEnabled && m.CurrentSpeed)
{

View File

@ -179,7 +179,7 @@ void ViewGame::Update(float frameLength)
{
// Update unit interpolation
if (g_UseSimulation2)
g_Game->GetSimulation2()->Interpolate(0.0);
g_Game->Interpolate(0.0);
else
g_Game->GetSimulation()->Interpolate(0.0);
}
@ -205,7 +205,7 @@ void ViewGame::Update(float frameLength)
// not in every call to g_Game->Update
if (g_UseSimulation2)
{
g_Game->GetSimulation2()->Interpolate(actualFrameLength);
g_Game->Interpolate(actualFrameLength);
}
else
{