diff --git a/binaries/data/mods/official/gui/test/functions_sim_entity.js b/binaries/data/mods/official/gui/test/functions_sim_entity.js index e682e7d7f9..258b55151f 100644 --- a/binaries/data/mods/official/gui/test/functions_sim_entity.js +++ b/binaries/data/mods/official/gui/test/functions_sim_entity.js @@ -85,8 +85,8 @@ function worldClickHandler(event) if (!selection[i].actions) continue; //console.write("Requesting notification for " + event.entity); - selection[i].requestNotification( event.entity, action, true, false ); - selection[i].order( ORDER_GOTO, event.entity.position.x, event.entity.position.z - selection[i].actions.escort.distance, true); + //selection[i].requestNotification( event.entity, action, true, false ); + //selection[i].order( ORDER_GOTO, event.entity.position.x, event.entity.position.z - selection[i].actions.escort.distance, true); } } else @@ -96,7 +96,7 @@ function worldClickHandler(event) if (!selection[i].actions) continue; //console.write("Requesting notification for " + event.entity); - selection[i].requestNotification( event.entity, action, true, false ); + //selection[i].requestNotification( event.entity, action, true, false ); } } diff --git a/source/dcdt/se/Abstraction.h b/source/dcdt/se/Abstraction.h index 2e46740a8f..543ecb4e37 100644 --- a/source/dcdt/se/Abstraction.h +++ b/source/dcdt/se/Abstraction.h @@ -1,157 +1,159 @@ -//Abstraction.h - -//DJD: definition of Abstraction class { - -#ifndef ABSTRACTION_H -#define ABSTRACTION_H - -#include -#include "FunnelDeque.h" - -//defines an invalid entry, and "infinity", for our purposes -#define INVALID -1 -#define INFINITY FLT_MAX - -//prototypes for classes defined later -template class SeLinkFace; -class Abstraction; -typedef SeLinkFace SeDcdtFace; - -//class which contains information about our topological abstraction -class Abstraction -{ -protected: - //list of adjacent nodes - //if this node is degree-1 in a tree, all will be NULL - //if it is degree-1 otherwise, one will be the root of the tree, the others will be NULL - //if it is degree-2 in a ring, all will be NULL - //if it is degree-2 otherwise, two will be degree-3 nodes on the ends of the corridor, the other will be NULL - //if it is degree-3, all will be degree-3 nodes on the end of the corridors - SeDcdtFace *m_CAdjacent[3]; - //sum of interior angles of triangles between this one and the corresponding adjacent one - //INVALID if the corresponding adjacent node is NULL - float m_dAngle[3]; - //width between the corresponding edge and the one counterclockwise to it, through the triangle - float m_dWidth[3]; - //smallest width through the triangles between this one and the corresponding adjacent one - //INVALID if the corresponding adjacent node is NULL - float m_dChoke[3]; - //degree of this node - int m_nDegree; - //connected component to which this triangle belongs - int m_nComponent; - - //makes sure the index is in the range [0, 2] - bool CheckBounds(int n) - { - return ((n >= 0) && (n < 3)); - } -public: - //creates the abstraction with given degree - Abstraction(int nDegree) - { - //starts all adjacents as NULL and other information as INVALID - for (int i = 0; i < 3; i++) - { - m_CAdjacent[i] = NULL; - m_dAngle[i] = INVALID; - m_dWidth[i] = INVALID; - m_dChoke[i] = INVALID; - } - m_nDegree = nDegree; - m_nComponent = INVALID; - } - - //default destructor - ~Abstraction() - { - } - - //accessor for the degree of the node - int Degree() - { - return m_nDegree; - } - - //mutator for the degree of the node - void Degree(int nDegree) - { - m_nDegree = nDegree; - } - - //accessor for the connected component of the node - int Component() - { - return m_nComponent; - } - - //mutator for the connected component of the node - void Component(int nComponent) - { - m_nComponent = nComponent; - } - - //accessor for the adjacent node with index given - SeDcdtFace *Adjacent(int n) - { - return (CheckBounds(n)) ? m_CAdjacent[n] : NULL; - } - - //mutator for the adjacent node with index given - void Adjacent(int n, SeDcdtFace *CAdjacent) - { - if (CheckBounds(n)) - { - m_CAdjacent[n] = CAdjacent; - } - } - - //accessor for the angle with index given - float Angle(int n) - { - return (CheckBounds(n)) ? m_dAngle[n] : INVALID; - } - - //mutator for the angle with index given - void Angle(int n, float dAngle) - { - if (CheckBounds(n)) - { - m_dAngle[n] = dAngle; - } - } - - //accessor for the width with index given - float Width(int n) - { - return (CheckBounds(n)) ? m_dWidth[n] : INVALID; - } - - //mutator for the width with index given - void Width(int n, float dWidth) - { - if (CheckBounds(n)) - { - m_dWidth[n] = dWidth; - } - } - - //accessor for the choke point width with index given - float Choke(int n) - { - return (CheckBounds(n)) ? m_dChoke[n] : INVALID; - } - - //mutator for the choke point width with index given - void Choke(int n, float dChoke) - { - if (CheckBounds(n)) - { - m_dChoke[n] = dChoke; - } - } -}; - -#endif - -//definition of Abstraction class } +//Abstraction.h + +//DJD: definition of Abstraction class { + +#ifndef ABSTRACTION_H +#define ABSTRACTION_H + +#include +#include "FunnelDeque.h" + +//defines an invalid entry, and "infinity", for our purposes +#define INVALID -1 +#ifndef INFINITY +#define INFINITY FLT_MAX +#endif + +//prototypes for classes defined later +template class SeLinkFace; +class Abstraction; +typedef SeLinkFace SeDcdtFace; + +//class which contains information about our topological abstraction +class Abstraction +{ +protected: + //list of adjacent nodes + //if this node is degree-1 in a tree, all will be NULL + //if it is degree-1 otherwise, one will be the root of the tree, the others will be NULL + //if it is degree-2 in a ring, all will be NULL + //if it is degree-2 otherwise, two will be degree-3 nodes on the ends of the corridor, the other will be NULL + //if it is degree-3, all will be degree-3 nodes on the end of the corridors + SeDcdtFace *m_CAdjacent[3]; + //sum of interior angles of triangles between this one and the corresponding adjacent one + //INVALID if the corresponding adjacent node is NULL + float m_dAngle[3]; + //width between the corresponding edge and the one counterclockwise to it, through the triangle + float m_dWidth[3]; + //smallest width through the triangles between this one and the corresponding adjacent one + //INVALID if the corresponding adjacent node is NULL + float m_dChoke[3]; + //degree of this node + int m_nDegree; + //connected component to which this triangle belongs + int m_nComponent; + + //makes sure the index is in the range [0, 2] + bool CheckBounds(int n) + { + return ((n >= 0) && (n < 3)); + } +public: + //creates the abstraction with given degree + Abstraction(int nDegree) + { + //starts all adjacents as NULL and other information as INVALID + for (int i = 0; i < 3; i++) + { + m_CAdjacent[i] = NULL; + m_dAngle[i] = INVALID; + m_dWidth[i] = INVALID; + m_dChoke[i] = INVALID; + } + m_nDegree = nDegree; + m_nComponent = INVALID; + } + + //default destructor + ~Abstraction() + { + } + + //accessor for the degree of the node + int Degree() + { + return m_nDegree; + } + + //mutator for the degree of the node + void Degree(int nDegree) + { + m_nDegree = nDegree; + } + + //accessor for the connected component of the node + int Component() + { + return m_nComponent; + } + + //mutator for the connected component of the node + void Component(int nComponent) + { + m_nComponent = nComponent; + } + + //accessor for the adjacent node with index given + SeDcdtFace *Adjacent(int n) + { + return (CheckBounds(n)) ? m_CAdjacent[n] : NULL; + } + + //mutator for the adjacent node with index given + void Adjacent(int n, SeDcdtFace *CAdjacent) + { + if (CheckBounds(n)) + { + m_CAdjacent[n] = CAdjacent; + } + } + + //accessor for the angle with index given + float Angle(int n) + { + return (CheckBounds(n)) ? m_dAngle[n] : INVALID; + } + + //mutator for the angle with index given + void Angle(int n, float dAngle) + { + if (CheckBounds(n)) + { + m_dAngle[n] = dAngle; + } + } + + //accessor for the width with index given + float Width(int n) + { + return (CheckBounds(n)) ? m_dWidth[n] : INVALID; + } + + //mutator for the width with index given + void Width(int n, float dWidth) + { + if (CheckBounds(n)) + { + m_dWidth[n] = dWidth; + } + } + + //accessor for the choke point width with index given + float Choke(int n) + { + return (CheckBounds(n)) ? m_dChoke[n] : INVALID; + } + + //mutator for the choke point width with index given + void Choke(int n, float dChoke) + { + if (CheckBounds(n)) + { + m_dChoke[n] = dChoke; + } + } +}; + +#endif + +//definition of Abstraction class } diff --git a/source/dcdt/se/FunnelDeque.h b/source/dcdt/se/FunnelDeque.h index cac0e1ca95..c2836b7f6f 100644 --- a/source/dcdt/se/FunnelDeque.h +++ b/source/dcdt/se/FunnelDeque.h @@ -9,7 +9,9 @@ #include "sr_polygon.h" //define a floating point value for pi +#ifndef PI #define PI 3.1415926535897932384626433832795f +#endif //a node in the funnel deque class FunnelNode diff --git a/source/dcdt/se/Location.cpp b/source/dcdt/se/Location.cpp index 42b0eecf36..d33900248c 100644 --- a/source/dcdt/se/Location.cpp +++ b/source/dcdt/se/Location.cpp @@ -152,7 +152,7 @@ float SeDcdt::RandomY() //returns a random number between the values given float SeDcdt::RandomBetween(float min, float max) { - float base = (float)rand() / (float)RAND_MAX; + float base = (float)rand() / (float)RAND_MAX; // TODO: Make net-safe return (base * (max - min) + min); } diff --git a/source/network/Client.cpp b/source/network/Client.cpp index 12cc435b5c..285e7fc49c 100644 --- a/source/network/Client.cpp +++ b/source/network/Client.cpp @@ -37,7 +37,8 @@ CNetClient::CNetClient(CGame *pGame, CGameAttributes *pGameAttribs): m_JSI_ServerSessions(&m_ServerSessions), m_pLocalPlayerSlot(NULL), m_pGame(pGame), - m_pGameAttributes(pGameAttribs) + m_pGameAttributes(pGameAttribs), + m_TurnPending(false) { ONCE( ScriptingInit(); ); @@ -51,11 +52,10 @@ CNetClient::~CNetClient() g_ScriptingHost.SetGlobal("g_NetClient", JSVAL_NULL); SessionMap::iterator it=m_ServerSessions.begin(); - while (it != m_ServerSessions.end()) + for (; it != m_ServerSessions.end(); ++it) { delete it->second; } - // (Isn't this an infinite loop?) } void CNetClient::ScriptingInit() @@ -333,7 +333,7 @@ bool CNetClient::InGameHandler(CNetMessage *pMsg, CNetSession *pSession) if (msgType >= NMT_COMMAND_FIRST && msgType < NMT_COMMAND_LAST) { - pClient->QueueMessage(1, pMsg); + pClient->QueueIncomingMessage(pMsg); TAKEN(pMsg); } @@ -343,17 +343,10 @@ bool CNetClient::InGameHandler(CNetMessage *pMsg, CNetSession *pSession) { CEndCommandBatch *msg=(CEndCommandBatch *)pMsg; pClient->SetTurnLength(1, msg->m_TurnLength); - - // FIXME When the command batch has ended, we should start accepting - // commands for the next turn. This will be accomplished by calling - // NewTurn. *BUT* we shouldn't prematurely proceed game simulation - // since this will produce jerky playback (everything expects a sim - // turn to have a certain duration). - - // We should make sure that any commands received after this message - // are queued in the next batch (#2 instead of #1). If we're already - // putting everything new in batch 2 - we should fast-forward a bit to - // catch up with the server. + + //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); } @@ -363,6 +356,13 @@ bool CNetClient::InGameHandler(CNetMessage *pMsg, CNetSession *pSession) } } +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; @@ -437,10 +437,16 @@ 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/fps; + Push(pMsg); return 0; } } @@ -450,11 +456,20 @@ 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/fps; Push(pMsg); @@ -463,5 +478,6 @@ void CNetClient::NewTurn() void CNetClient::QueueLocalCommand(CNetMessage *pMsg) { // Don't save these locally, since they'll be bounced by the server anyway + //debug_printf("Sending command from client\n"); Push(pMsg); } diff --git a/source/network/Client.h b/source/network/Client.h index 1d1f6678d5..b00ca70017 100644 --- a/source/network/Client.h +++ b/source/network/Client.h @@ -1,12 +1,13 @@ #ifndef INCLUDED_NETWORK_CLIENT #define INCLUDED_NETWORK_CLIENT -#include "ps/CStr.h" #include "Session.h" #include "simulation/TurnManager.h" #include "simulation/ScriptObject.h" #include "scripting/ScriptableObject.h" +#include "ps/CStr.h" +#include "ps/ThreadUtil.h" #include "ps/scripting/JSMap.h" #include @@ -51,12 +52,22 @@ class CNetClient: public CNetSession, protected CTurnManager, public CJSObject - #include "Server.h" #include "ServerSession.h" #include "Network.h" @@ -14,6 +12,7 @@ #include "ps/Player.h" #include "ps/CLogger.h" #include "ps/CConsole.h" +#include "ps/ThreadUtil.h" #define LOG_CAT_NET "net" @@ -346,8 +345,6 @@ void CNetServer::Broadcast(CNetMessage *pMsg) int CNetServer::StartGame() { - Broadcast(new CStartGame()); - if (m_pGame->StartGame(m_pGameAttributes) != PSRETURN_OK) { return -1; @@ -370,8 +367,11 @@ int CNetServer::StartGame() ++it; } + debug_printf("Server StartGame\n"); + Broadcast(new CStartGame()); + // This is the signal for everyone to start their simulations. - SendBatch(1); + //SendBatch(1); return 0; } @@ -382,26 +382,51 @@ void CNetServer::GetDefaultListenAddress(CSocketAddress &address) address=CSocketAddress(PS_DEFAULT_PORT, IPv4); } +bool CNetServer::NewTurnReady() +{ + // Wait for all clients to check in + SessionMap::iterator it = m_Sessions.begin(); + for (; it != m_Sessions.end(); ++it) + { + if (!it->second->IsReadyForTurn()) + return false; + } + return true; +} + void CNetServer::NewTurn() { + CScopeLock lock(m_Mutex); + + // Clear ready flags on clients + SessionMap::iterator it = m_Sessions.begin(); + for (; it != m_Sessions.end(); ++it) + { + it->second->SetReadyForTurn(false); + } + RecordBatch(2); RotateBatches(); ClearBatch(2); IterateBatch(1, CSimulation::GetMessageMask, m_pGame->GetSimulation()); + //debug_printf("In NewTurn - sending batch\n"); SendBatch(1); //IterateBatch(1, SendToObservers, this); } void CNetServer::QueueLocalCommand(CNetMessage *pMsg) { + //debug_printf("Queueing command from server\n"); QueueIncomingCommand(pMsg); } void CNetServer::QueueIncomingCommand(CNetMessage *pMsg) { + CScopeLock lock(m_Mutex); LOG(NORMAL, LOG_CAT_NET, "CNetServer::QueueIncomingCommand(): %s.", pMsg->GetString().c_str()); + debug_printf("Got a command! queueing it to 2 turns from now\n"); QueueMessage(2, pMsg); } diff --git a/source/network/Server.h b/source/network/Server.h index 5f25504b01..0615159e18 100644 --- a/source/network/Server.h +++ b/source/network/Server.h @@ -81,6 +81,9 @@ private: // JS Interface Methods bool JSI_Open(JSContext *cx, uintN argc, jsval *argv); static void ScriptingInit(); + + // Synchronization object for batches + CMutex m_Mutex; protected: friend class CNetServerSession; @@ -108,6 +111,7 @@ protected: virtual void OnAccept(const CSocketAddress &); // OVERRIDES FROM CTurnManager + virtual bool NewTurnReady(); virtual void NewTurn(); virtual void QueueLocalCommand(CNetMessage *pMsg); diff --git a/source/network/ServerSession.cpp b/source/network/ServerSession.cpp index 9f6bf3a4b1..5c70f65f1d 100644 --- a/source/network/ServerSession.cpp +++ b/source/network/ServerSession.cpp @@ -12,7 +12,8 @@ CNetServerSession::CNetServerSession(CNetServer *pServer, CSocketInternal *pInt, m_pPlayer(NULL), m_pPlayerSlot(NULL), m_IsObserver(false), - m_ID(-1) + m_ID(-1), + m_ReadyForTurn(false) { ONCE( ScriptingInit(); @@ -193,7 +194,7 @@ bool CNetServerSession::InGameHandler(CNetMessage *pMsg, CNetSession *pNetSessio switch (pMsg->GetType()) { case NMT_EndCommandBatch: - // TODO Update client timing information and recalculate turn length + pSession->m_ReadyForTurn = true; HANDLED(pMsg); default: diff --git a/source/network/ServerSession.h b/source/network/ServerSession.h index 36e56d792f..1241b0d192 100644 --- a/source/network/ServerSession.h +++ b/source/network/ServerSession.h @@ -22,6 +22,7 @@ class CNetServerSession: public CNetSession, public CJSObject CPlayerSlot *m_pPlayerSlot; bool m_IsObserver; int m_ID; + bool m_ReadyForTurn; // Is the last turn acknowledged? // JS INTERFACE static void ScriptingInit(); @@ -51,6 +52,10 @@ public: { return m_pPlayerSlot; } inline int GetID() { return m_ID; } + inline bool IsReadyForTurn() + { return m_ReadyForTurn; } + inline void SetReadyForTurn(bool value) + { m_ReadyForTurn = value; } // Called by server when starting the game, after sending NMT_StartGame to // all connected clients. diff --git a/source/ps/GameSetup/GameSetup.cpp b/source/ps/GameSetup/GameSetup.cpp index 56baa9c86a..39bd88091c 100644 --- a/source/ps/GameSetup/GameSetup.cpp +++ b/source/ps/GameSetup/GameSetup.cpp @@ -900,7 +900,7 @@ void EarlyInit() timer_Init(); // Initialise the low-quality rand function - srand(time(NULL)); + srand(time(NULL)); // NOTE: this rand should *not* be used for simulation! } void Init(const CmdLineArgs& args, uint flags) diff --git a/source/renderer/ModelRenderer.cpp b/source/renderer/ModelRenderer.cpp index 51c9cd362f..224ddfbd90 100644 --- a/source/renderer/ModelRenderer.cpp +++ b/source/renderer/ModelRenderer.cpp @@ -388,7 +388,7 @@ void BatchModelRenderer::EndFrame() if (mdeftracker->m_Slots > mostslots) { mostslots = mdeftracker->m_Slots; - debug_printf("BatchModelRenderer: SubmissionSlots maximum: %u\n", mostslots); + //debug_printf("BatchModelRenderer: SubmissionSlots maximum: %u\n", mostslots); } mdeftracker->m_Slots = 0; } diff --git a/source/simulation/Entity.cpp b/source/simulation/Entity.cpp index d18c52f4f0..9ce6ed9857 100644 --- a/source/simulation/Entity.cpp +++ b/source/simulation/Entity.cpp @@ -473,6 +473,11 @@ void CEntity::UpdateOrders( size_t timestep ) while( !m_orderQueue.empty() ) { CEntityOrder* current = &m_orderQueue.front(); + CStr name = me; +#ifdef DEBUG_SYNCHRONIZATION + debug_printf("Order for %ls: %d (src %d)\n", + m_base->m_Tag.c_str(), current->m_type, current->m_source); +#endif if( current->m_type != m_lastState ) { @@ -877,6 +882,7 @@ void CEntity::DispatchFormationEvent( int type ) } void CEntity::Repath() { + debug_printf("Repath\n"); CVector2D destination; CEntityOrder::EOrderSource orderSource = CEntityOrder::SOURCE_PLAYER; diff --git a/source/simulation/EntityOrders.h b/source/simulation/EntityOrders.h index 32b17a6419..7bf4114c87 100644 --- a/source/simulation/EntityOrders.h +++ b/source/simulation/EntityOrders.h @@ -82,22 +82,22 @@ class CEntityOrder public: enum EOrderType { - ORDER_INVALID, - ORDER_GOTO_NOPATHING, - ORDER_GOTO_SMOOTHED, - ORDER_GOTO_COLLISION, - ORDER_GOTO_WAYPOINT, - ORDER_GOTO_WAYPOINT_CONTACT, - ORDER_GOTO, - ORDER_RUN, - ORDER_PATROL, - ORDER_PATH_END_MARKER, - ORDER_GENERIC, - ORDER_GENERIC_NOPATHING, - ORDER_PRODUCE, - ORDER_START_CONSTRUCTION, - ORDER_NOTIFY_REQUEST, - ORDER_LAST + ORDER_INVALID, // 0 + ORDER_GOTO_NOPATHING, // 1 + ORDER_GOTO_SMOOTHED, // 2 + ORDER_GOTO_COLLISION, // 3 + ORDER_GOTO_WAYPOINT, // 4 + ORDER_GOTO_WAYPOINT_CONTACT,// 5 + ORDER_GOTO, // 6 + ORDER_RUN, // 7 + ORDER_PATROL, // 8 + ORDER_PATH_END_MARKER, // 9 + ORDER_GENERIC, // 10 + ORDER_GENERIC_NOPATHING, // 11 + ORDER_PRODUCE, // 12 + ORDER_START_CONSTRUCTION, // 13 + ORDER_NOTIFY_REQUEST, // 14 + ORDER_LAST // 15 }; EOrderType m_type; diff --git a/source/simulation/EntityScriptInterface.cpp b/source/simulation/EntityScriptInterface.cpp index 7728f24cfa..8c6d9f78e2 100644 --- a/source/simulation/EntityScriptInterface.cpp +++ b/source/simulation/EntityScriptInterface.cpp @@ -30,6 +30,7 @@ #include "ProductionQueue.h" #include "TechnologyCollection.h" #include "TerritoryManager.h" +#include "Simulation.h" #include "Stance.h" #include @@ -134,7 +135,7 @@ JSBool CEntity::Construct( JSContext* cx, JSObject* UNUSED(obj), uint argc, jsva debug_assert( argc >= 2 ); CVector3D position; - float orientation = (float)( PI * ( (double)( rand() & 0x7fff ) / (double)0x4000 ) ); + float orientation = g_Game->GetSimulation()->RandFloat() * 2 * PI; JSObject* jsEntityTemplate = JSVAL_TO_OBJECT( argv[0] ); CStrW templateName; @@ -398,7 +399,7 @@ jsval_t CEntity::GetSpawnPoint( JSContext* UNUSED(cx), uintN argc, jsval* argv ) // Pick a start point - int edge = rand() & 3; + int edge = g_Game->GetSimulation()->RandInt( 4 ); int point; double max_w = oabb->m_w + spawn_clearance + 1.0; @@ -411,12 +412,12 @@ jsval_t CEntity::GetSpawnPoint( JSContext* UNUSED(cx), uintN argc, jsval* argv ) CVector2D pos( m_position ); if( edge & 1 ) { - point = rand() % ( 2 * d_count ) - d_count; + point = g_Game->GetSimulation()->RandInt( 2 * d_count ) - d_count; pos += ( oabb->m_v * (float)max_w + d_step * (float)point ) * ( ( edge & 2 ) ? -1.0f : 1.0f ); } else { - point = rand() % ( 2 * w_count ) - w_count; + point = g_Game->GetSimulation()->RandInt( 2 * w_count ) - w_count; pos += ( oabb->m_u * (float)max_d + w_step * (float)point ) * ( ( edge & 2 ) ? -1.0f : 1.0f ); } @@ -478,8 +479,7 @@ jsval_t CEntity::GetSpawnPoint( JSContext* UNUSED(cx), uintN argc, jsval* argv ) else if( m_bounds->m_type == CBoundingObject::BOUND_CIRCLE ) { float ang; - ang = (float)( rand() & 0x7fff ) / (float)0x4000; /* 0...2 */ - ang *= PI; + ang = g_Game->GetSimulation()->RandFloat() * 2 * PI; float radius = m_bounds->m_radius + 1.0f + spawn_clearance; float d_ang = spawn_clearance / ( 2.0f * radius ); float ang_end = ang + 2.0f * PI; diff --git a/source/simulation/Simulation.cpp b/source/simulation/Simulation.cpp index 587c10d474..cecfbce690 100644 --- a/source/simulation/Simulation.cpp +++ b/source/simulation/Simulation.cpp @@ -83,20 +83,30 @@ bool CSimulation::Update(double frameTime) if (m_DeltaTime >= 0.0) { // A new simulation frame is required. - - PROFILE( "simulation turn" ); - Simulate(); - double turnLength = m_pTurnManager->GetTurnLength() / 1000.0; - m_DeltaTime -= turnLength; - if (m_DeltaTime >= 0.0) + if (m_pTurnManager->NewTurnReady()) { - // The desired sim frame rate can't be achieved - we're being called - // with average(frameTime) > turnLength. - // Let the caller know we can't go fast enough - they should try - // cutting down on Interpolate and rendering, and call us a few times - // with frameTime == 0 to give us a chance to catch up. - ok = false; - debug_printf("WARNING: missing a simulation turn due to low FPS"); + PROFILE( "simulation turn" ); + Simulate(); + double turnLength = m_pTurnManager->GetTurnLength() / 1000.0; + m_DeltaTime -= turnLength; + if (m_DeltaTime >= 0.0) + { + // The desired sim frame rate can't be achieved - we're being called + // with average(frameTime) > turnLength. + // Let the caller know we can't go fast enough - they should try + // cutting down on Interpolate and rendering, and call us a few times + // with frameTime == 0 to give us a chance to catch up. + ok = false; + debug_printf("WARNING: missing a simulation turn due to low FPS\n"); + } + } + else + { + // The network is lagging behind the simulation rate. + // Set delta time back to zero so we don't jump into the middle + // of the next simulation frame when we get the next turn. + // This creates "lag" on the client rather than just jumpiness. + m_DeltaTime = 0; } } @@ -141,7 +151,9 @@ void CSimulation::Simulate() uint time = m_pTurnManager->GetTurnLength(); m_Time += time / 1000.0f; - //debug_printf("Simulation turn: %.3lf\n", m_Time); +#ifdef DEBUG_SYNCHRONIZATION + debug_printf("Simulation turn: %.3lf\n", m_Time); +#endif PROFILE_START( "scheduler tick" ); g_Scheduler.Update(time); @@ -167,6 +179,10 @@ void CSimulation::Simulate() m_pTurnManager->NewTurn(); m_pTurnManager->IterateBatch(0, TranslateMessage, this); PROFILE_END( "turn manager update" ); + +#ifdef DEBUG_SYNCHRONIZATION + debug_printf("End turn\n", m_Time); +#endif } // Location randomizer, for group orders... diff --git a/source/simulation/TurnManager.h b/source/simulation/TurnManager.h index 62a6bc6467..8f9b15d3a9 100644 --- a/source/simulation/TurnManager.h +++ b/source/simulation/TurnManager.h @@ -100,10 +100,14 @@ public: // CSimulation will use this to determine when to perform the deterministic // update and call NewTurn() uint GetTurnLength(); - + // Called by CSimulation when the current turn time has passed. virtual void NewTurn()=0; + + // Used by CSimulation to ask whether it can call NewTurn. + virtual bool NewTurnReady() { return true; } + // Apply a function to all messages in a given batch. void IterateBatch(uint batch, BatchIteratorFunc *func, void *userdata); // Queue a command originating from the local player.