Ticket #4095: rename.patch

File rename.patch, 58.3 KB (added by echotangoecho, 8 years ago)

Split the turnmanagers across files as suggested by elexis, remove the virtual functions OnSyncError and DisplayOOSError from the CTurnManager.

  • source/gui/scripting/ScriptFunctions.cpp

     
    3535#include "lib/timer.h"
    3636#include "lib/utf8.h"
    3737#include "lobby/scripting/JSInterface_Lobby.h"
    3838#include "maths/FixedVector3D.h"
    3939#include "network/NetClient.h"
     40#include "network/NetMessage.h"
    4041#include "network/NetServer.h"
    41 #include "network/NetTurnManager.h"
    4242#include "ps/CConsole.h"
    4343#include "ps/CLogger.h"
    4444#include "ps/Errors.h"
    4545#include "ps/GUID.h"
    4646#include "ps/Game.h"
     
    6868#include "simulation2/components/ICmpPlayerManager.h"
    6969#include "simulation2/components/ICmpRangeManager.h"
    7070#include "simulation2/components/ICmpSelectable.h"
    7171#include "simulation2/components/ICmpTemplateManager.h"
    7272#include "simulation2/helpers/Selection.h"
     73#include "simulation2/system/TurnManager.h"
    7374#include "soundmanager/SoundManager.h"
    7475#include "soundmanager/scripting/JSInterface_Sound.h"
    7576#include "tools/atlas/GameInterface/GameLoop.h"
    7677
    7778/*
  • source/network/NetTurnManager.cpp

     
    1616 */
    1717
    1818#include "precompiled.h"
    1919
    2020#include "NetTurnManager.h"
    21 #include "NetMessage.h"
    2221
    2322#include "network/NetServer.h"
    2423#include "network/NetClient.h"
    25 #include "network/NetMessage.h"
    2624
    2725#include "gui/GUIManager.h"
    28 #include "maths/MathUtil.h"
    2926#include "ps/CLogger.h"
    30 #include "ps/Profile.h"
    3127#include "ps/Pyrogenesis.h"
    3228#include "ps/Replay.h"
    33 #include "ps/SavedGame.h"
    34 #include "scriptinterface/ScriptInterface.h"
    3529#include "simulation2/Simulation2.h"
    3630
    3731#include <sstream>
    3832#include <fstream>
    3933#include <iomanip>
    4034
    41 const u32 DEFAULT_TURN_LENGTH_MP = 500;
    42 const u32 DEFAULT_TURN_LENGTH_SP = 200;
    43 
    44 static const int COMMAND_DELAY = 2;
    45 
    4635#if 0
    4736#define NETTURN_LOG(args) debug_printf args
    4837#else
    4938#define NETTURN_LOG(args)
    5039#endif
    static std::string Hexify(const std::str  
    5645    for (size_t i = 0; i < s.size(); ++i)
    5746        str << std::setfill('0') << std::setw(2) << (int)(unsigned char)s[i];
    5847    return str.str();
    5948}
    6049
    61 CNetTurnManager::CNetTurnManager(CSimulation2& simulation, u32 defaultTurnLength, int clientId, IReplayLogger& replay) :
    62     m_Simulation2(simulation), m_CurrentTurn(0), m_ReadyTurn(1), m_TurnLength(defaultTurnLength), m_DeltaSimTime(0),
    63     m_PlayerId(-1), m_ClientId(clientId), m_HasSyncError(false), m_Replay(replay),
    64     m_TimeWarpNumTurns(0), m_FinalTurn(std::numeric_limits<u32>::max())
    65 {
    66     // When we are on turn n, we schedule new commands for n+2.
    67     // We know that all other clients have finished scheduling commands for n (else we couldn't have got here).
    68     // We know we have not yet finished scheduling commands for n+2.
    69     // Hence other clients can be on turn n-1, n, n+1, and no other.
    70     // So they can be sending us commands scheduled for n+1, n+2, n+3.
    71     // So we need a 3-element buffer:
    72     m_QueuedCommands.resize(COMMAND_DELAY + 1);
    73 }
    74 
    75 void CNetTurnManager::ResetState(u32 newCurrentTurn, u32 newReadyTurn)
    76 {
    77     m_CurrentTurn = newCurrentTurn;
    78     m_ReadyTurn = newReadyTurn;
    79     m_DeltaSimTime = 0;
    80     size_t queuedCommandsSize = m_QueuedCommands.size();
    81     m_QueuedCommands.clear();
    82     m_QueuedCommands.resize(queuedCommandsSize);
    83 }
    84 
    85 void CNetTurnManager::SetPlayerID(int playerId)
    86 {
    87     m_PlayerId = playerId;
    88 }
    89 
    90 bool CNetTurnManager::WillUpdate(float simFrameLength)
    91 {
    92     // Keep this in sync with the return value of Update()
    93 
    94     if (m_CurrentTurn > m_FinalTurn)
    95         return false;
    96 
    97     if (m_DeltaSimTime + simFrameLength < 0)
    98         return false;
    99 
    100     if (m_ReadyTurn <= m_CurrentTurn)
    101         return false;
    102 
    103     return true;
    104 }
    105 
    106 bool CNetTurnManager::Update(float simFrameLength, size_t maxTurns)
    107 {
    108     if (m_CurrentTurn > m_FinalTurn)
    109         return false;
    110 
    111     m_DeltaSimTime += simFrameLength;
    112 
    113     // If the game becomes laggy, m_DeltaSimTime increases progressively.
    114     // The engine will fast forward accordingly to catch up.
    115     // To keep the game playable, stop fast forwarding after 2 turn lengths.
    116     m_DeltaSimTime = std::min(m_DeltaSimTime, 2.0f * m_TurnLength / 1000.0f);
    117 
    118     // If we haven't reached the next turn yet, do nothing
    119     if (m_DeltaSimTime < 0)
    120         return false;
    121 
    122     NETTURN_LOG((L"Update current=%d ready=%d\n", m_CurrentTurn, m_ReadyTurn));
    123 
    124     // Check that the next turn is ready for execution
    125     if (m_ReadyTurn <= m_CurrentTurn)
    126     {
    127         // Oops, we wanted to start the next turn but it's not ready yet -
    128         // there must be too much network lag.
    129         // TODO: complain to the user.
    130         // TODO: send feedback to the server to increase the turn length.
    131 
    132         // Reset the next-turn timer to 0 so we try again next update but
    133         // so we don't rush to catch up in subsequent turns.
    134         // TODO: we should do clever rate adjustment instead of just pausing like this.
    135         m_DeltaSimTime = 0;
    136 
    137         return false;
    138     }
    139 
    140     maxTurns = std::max((size_t)1, maxTurns); // always do at least one turn
    141 
    142     for (size_t i = 0; i < maxTurns; ++i)
    143     {
    144         // Check that we've reached the i'th next turn
    145         if (m_DeltaSimTime < 0)
    146             break;
    147 
    148         // Check that the i'th next turn is still ready
    149         if (m_ReadyTurn <= m_CurrentTurn)
    150             break;
    151 
    152         NotifyFinishedOwnCommands(m_CurrentTurn + COMMAND_DELAY);
    153 
    154         // Increase now, so Update can send new commands for a subsequent turn
    155         ++m_CurrentTurn;
    156 
    157         // Clean up any destroyed entities since the last turn (e.g. placement previews
    158         // or rally point flags generated by the GUI). (Must do this before the time warp
    159         // serialization.)
    160         m_Simulation2.FlushDestroyedEntities();
    161 
    162         // Save the current state for rewinding, if enabled
    163         if (m_TimeWarpNumTurns && (m_CurrentTurn % m_TimeWarpNumTurns) == 0)
    164         {
    165             PROFILE3("time warp serialization");
    166             std::stringstream stream;
    167             m_Simulation2.SerializeState(stream);
    168             m_TimeWarpStates.push_back(stream.str());
    169         }
    170 
    171         // Put all the client commands into a single list, in a globally consistent order
    172         std::vector<SimulationCommand> commands;
    173         for (std::pair<const u32, std::vector<SimulationCommand>>& p : m_QueuedCommands[0])
    174             commands.insert(commands.end(), std::make_move_iterator(p.second.begin()), std::make_move_iterator(p.second.end()));
    175 
    176         m_QueuedCommands.pop_front();
    177         m_QueuedCommands.resize(m_QueuedCommands.size() + 1);
    178 
    179         m_Replay.Turn(m_CurrentTurn-1, m_TurnLength, commands);
    180 
    181         NETTURN_LOG((L"Running %d cmds\n", commands.size()));
    182 
    183         m_Simulation2.Update(m_TurnLength, commands);
    184 
    185         NotifyFinishedUpdate(m_CurrentTurn);
    186 
    187         // Set the time for the next turn update
    188         m_DeltaSimTime -= m_TurnLength / 1000.f;
    189     }
    190 
    191     return true;
    192 }
    193 
    194 bool CNetTurnManager::UpdateFastForward()
    195 {
    196     m_DeltaSimTime = 0;
    197 
    198     NETTURN_LOG((L"UpdateFastForward current=%d ready=%d\n", m_CurrentTurn, m_ReadyTurn));
    199 
    200     // Check that the next turn is ready for execution
    201     if (m_ReadyTurn <= m_CurrentTurn)
    202         return false;
    203 
    204     while (m_ReadyTurn > m_CurrentTurn)
    205     {
    206         // TODO: It would be nice to remove some of the duplication with Update()
    207         // (This is similar but doesn't call any Notify functions or update DeltaTime,
    208         // it just updates the simulation state)
    209 
    210         ++m_CurrentTurn;
    211 
    212         m_Simulation2.FlushDestroyedEntities();
    213 
    214         // Put all the client commands into a single list, in a globally consistent order
    215         std::vector<SimulationCommand> commands;
    216         for (std::pair<const u32, std::vector<SimulationCommand>>& p : m_QueuedCommands[0])
    217             commands.insert(commands.end(), std::make_move_iterator(p.second.begin()), std::make_move_iterator(p.second.end()));
    218 
    219         m_QueuedCommands.pop_front();
    220         m_QueuedCommands.resize(m_QueuedCommands.size() + 1);
    221 
    222         m_Replay.Turn(m_CurrentTurn-1, m_TurnLength, commands);
    223 
    224         NETTURN_LOG((L"Running %d cmds\n", commands.size()));
    225 
    226         m_Simulation2.Update(m_TurnLength, commands);
    227     }
    228 
    229     return true;
    230 }
    231 
    232 void CNetTurnManager::OnSyncError(u32 turn, const CStr& expectedHash, std::vector<CSyncErrorMessage::S_m_PlayerNames>& playerNames)
    233 {
    234     NETTURN_LOG((L"OnSyncError(%d, %hs)\n", turn, Hexify(expectedHash).c_str()));
    235 
    236     // Only complain the first time
    237     if (m_HasSyncError)
    238         return;
    239 
    240     bool quick = !TurnNeedsFullHash(turn);
    241     std::string hash;
    242     ENSURE(m_Simulation2.ComputeStateHash(hash, quick));
    243 
    244     OsPath path = psLogDir() / "oos_dump.txt";
    245     std::ofstream file (OsString(path).c_str(), std::ofstream::out | std::ofstream::trunc);
    246     m_Simulation2.DumpDebugState(file);
    247     file.close();
    248 
    249     hash = Hexify(hash);
    250     const std::string& expectedHashHex = Hexify(expectedHash);
    251 
    252     DisplayOOSError(turn, hash, expectedHashHex, false, &playerNames, &path);
    253 }
    254 
    255 void CNetTurnManager::DisplayOOSError(u32 turn, const CStr& hash, const CStr& expectedHash, bool isReplay, std::vector<CSyncErrorMessage::S_m_PlayerNames>* playerNames = NULL, OsPath* path = NULL)
    256 {
    257     m_HasSyncError = true;
    258 
    259     std::stringstream msg;
    260     msg << "Out of sync on turn " << turn;
    261 
    262     if (playerNames)
    263         for (size_t i = 0; i < playerNames->size(); ++i)
    264             msg << (i == 0 ? "\nPlayers: " : ", ") << utf8_from_wstring((*playerNames)[i].m_Name);
    265 
    266     if (isReplay)
    267         msg << "\n\n" << "The current game state is different from the original game state.";
    268     else
    269         msg << "\n\n" << "Your game state is " << (expectedHash == hash ? "identical to" : "different from") << " the hosts game state.";
    270 
    271     if (path)
    272         msg << "\n\n" << "Dumping current state to " << CStr(path->string8()).EscapeToPrintableASCII();
    273 
    274     LOGERROR("%s", msg.str());
    275 
    276     if (g_GUI)
    277         g_GUI->DisplayMessageBox(600, 350, L"Sync error", wstring_from_utf8(msg.str()));
    278 }
    279 
    280 void CNetTurnManager::Interpolate(float simFrameLength, float realFrameLength)
    281 {
    282     // TODO: using m_TurnLength might be a bit dodgy when length changes - maybe
    283     // we need to save the previous turn length?
    284 
    285     float offset = clamp(m_DeltaSimTime / (m_TurnLength / 1000.f) + 1.0, 0.0, 1.0);
    286 
    287     // Stop animations while still updating the selection highlight
    288     if (m_CurrentTurn > m_FinalTurn)
    289         simFrameLength = 0;
    290 
    291     m_Simulation2.Interpolate(simFrameLength, offset, realFrameLength);
    292 }
    293 
    294 void CNetTurnManager::AddCommand(int client, int player, JS::HandleValue data, u32 turn)
    295 {
    296     NETTURN_LOG((L"AddCommand(client=%d player=%d turn=%d)\n", client, player, turn));
    297 
    298     if (!(m_CurrentTurn < turn && turn <= m_CurrentTurn + COMMAND_DELAY + 1))
    299     {
    300         debug_warn(L"Received command for invalid turn");
    301         return;
    302     }
    303 
    304     m_Simulation2.GetScriptInterface().FreezeObject(data, true);
    305     m_QueuedCommands[turn - (m_CurrentTurn+1)][client].emplace_back(player, m_Simulation2.GetScriptInterface().GetContext(), data);
    306 }
    307 
    308 void CNetTurnManager::FinishedAllCommands(u32 turn, u32 turnLength)
    309 {
    310     NETTURN_LOG((L"FinishedAllCommands(%d, %d)\n", turn, turnLength));
    311 
    312     ENSURE(turn == m_ReadyTurn + 1);
    313     m_ReadyTurn = turn;
    314     m_TurnLength = turnLength;
    315 }
    316 
    317 bool CNetTurnManager::TurnNeedsFullHash(u32 turn)
    318 {
    319     // Check immediately for errors caused by e.g. inconsistent game versions
    320     // (The hash is computed after the first sim update, so we start at turn == 1)
    321     if (turn == 1)
    322         return true;
    323 
    324     // Otherwise check the full state every ~10 seconds in multiplayer games
    325     // (TODO: should probably remove this when we're reasonably sure the game
    326     // isn't too buggy, since the full hash is still pretty slow)
    327     if (turn % 20 == 0)
    328         return true;
    329 
    330     return false;
    331 }
    332 
    333 void CNetTurnManager::EnableTimeWarpRecording(size_t numTurns)
    334 {
    335     m_TimeWarpStates.clear();
    336     m_TimeWarpNumTurns = numTurns;
    337 }
    338 
    339 void CNetTurnManager::RewindTimeWarp()
    340 {
    341     if (m_TimeWarpStates.empty())
    342         return;
    343 
    344     std::stringstream stream(m_TimeWarpStates.back());
    345     m_Simulation2.DeserializeState(stream);
    346     m_TimeWarpStates.pop_back();
    347 
    348     // Reset the turn manager state, so we won't execute stray commands and
    349     // won't do the next snapshot until the appropriate time.
    350     // (Ideally we ought to serialise the turn manager state and restore it
    351     // here, but this is simpler for now.)
    352     ResetState(0, 1);
    353 }
    354 
    355 void CNetTurnManager::QuickSave()
    356 {
    357     TIMER(L"QuickSave");
    358 
    359     std::stringstream stream;
    360     if (!m_Simulation2.SerializeState(stream))
    361     {
    362         LOGERROR("Failed to quicksave game");
    363         return;
    364     }
    365 
    366     m_QuickSaveState = stream.str();
    367     if (g_GUI)
    368         m_QuickSaveMetadata = g_GUI->GetSavedGameData();
    369     else
    370         m_QuickSaveMetadata = std::string();
    371 
    372     LOGMESSAGERENDER("Quicksaved game");
    373 
    374 }
    375 
    376 void CNetTurnManager::QuickLoad()
    377 {
    378     TIMER(L"QuickLoad");
    379 
    380     if (m_QuickSaveState.empty())
    381     {
    382         LOGERROR("Cannot quickload game - no game was quicksaved");
    383         return;
    384     }
    385 
    386     std::stringstream stream(m_QuickSaveState);
    387     if (!m_Simulation2.DeserializeState(stream))
    388     {
    389         LOGERROR("Failed to quickload game");
    390         return;
    391     }
    392 
    393     if (g_GUI && !m_QuickSaveMetadata.empty())
    394         g_GUI->RestoreSavedGameData(m_QuickSaveMetadata);
    395 
    396     LOGMESSAGERENDER("Quickloaded game");
    397 
    398     // See RewindTimeWarp
    399     ResetState(0, 1);
    400 }
    401 
    402 
    40350CNetClientTurnManager::CNetClientTurnManager(CSimulation2& simulation, CNetClient& client, int clientId, IReplayLogger& replay) :
    404     CNetTurnManager(simulation, DEFAULT_TURN_LENGTH_MP, clientId, replay), m_NetClient(client)
     51    CTurnManager(simulation, DEFAULT_TURN_LENGTH_MP, clientId, replay), m_NetClient(client)
    40552{
    40653}
    40754
    40855void CNetClientTurnManager::PostCommand(JS::HandleValue data)
    40956{
    void CNetClientTurnManager::NotifyFinish  
    461108void CNetClientTurnManager::OnDestroyConnection()
    462109{
    463110    NotifyFinishedOwnCommands(m_CurrentTurn + COMMAND_DELAY);
    464111}
    465112
    466 void CNetClientTurnManager::OnSimulationMessage(CSimulationMessage* msg)
    467 {
    468     // Command received from the server - store it for later execution
    469     AddCommand(msg->m_Client, msg->m_Player, msg->m_Data, msg->m_Turn);
    470 }
    471 
    472 
    473 CNetLocalTurnManager::CNetLocalTurnManager(CSimulation2& simulation, IReplayLogger& replay) :
    474     CNetTurnManager(simulation, DEFAULT_TURN_LENGTH_SP, 0, replay)
     113void CNetClientTurnManager::OnSyncError(u32 turn, const CStr& expectedHash, const std::vector<CSyncErrorMessage::S_m_PlayerNames>& playerNames)
    475114{
    476 }
    477 
    478 void CNetLocalTurnManager::PostCommand(JS::HandleValue data)
    479 {
    480     // Add directly to the next turn, ignoring COMMAND_DELAY,
    481     // because we don't need to compensate for network latency
    482     AddCommand(m_ClientId, m_PlayerId, data, m_CurrentTurn + 1);
    483 }
     115    NETTURN_LOG((L"OnSyncError(%d, %hs)\n", turn, Hexify(expectedHash).c_str()));
    484116
    485 void CNetLocalTurnManager::NotifyFinishedOwnCommands(u32 turn)
    486 {
    487     FinishedAllCommands(turn, m_TurnLength);
    488 }
     117    // Only complain the first time
     118    if (m_HasSyncError)
     119        return;
    489120
    490 void CNetLocalTurnManager::NotifyFinishedUpdate(u32 UNUSED(turn))
    491 {
    492 #if 0 // this hurts performance and is only useful for verifying log replays
     121    bool quick = !TurnNeedsFullHash(turn);
    493122    std::string hash;
    494     {
    495         PROFILE3("state hash check");
    496         ENSURE(m_Simulation2.ComputeStateHash(hash));
    497     }
    498     m_Replay.Hash(hash);
    499 #endif
    500 }
    501 
    502 void CNetLocalTurnManager::OnSimulationMessage(CSimulationMessage* UNUSED(msg))
    503 {
    504     debug_warn(L"This should never be called");
    505 }
    506 
    507 CNetReplayTurnManager::CNetReplayTurnManager(CSimulation2& simulation, IReplayLogger& replay) :
    508     CNetLocalTurnManager(simulation, replay)
    509 {
    510 }
    511 
    512 void CNetReplayTurnManager::StoreReplayCommand(u32 turn, int player, const std::string& command)
    513 {
    514     // Using the pair we make sure that commands per turn will be processed in the correct order
    515     m_ReplayCommands[turn].emplace_back(player, command);
    516 }
    517 
    518 void CNetReplayTurnManager::StoreReplayHash(u32 turn, const std::string& hash, bool quick)
    519 {
    520     m_ReplayHash[turn] = std::make_pair(hash, quick);
    521 }
    522 
    523 void CNetReplayTurnManager::StoreReplayTurnLength(u32 turn, u32 turnLength)
    524 {
    525     m_ReplayTurnLengths[turn] = turnLength;
    526 
    527     // Initialize turn length
    528     if (turn == 0)
    529         m_TurnLength = m_ReplayTurnLengths[0];
    530 }
     123    ENSURE(m_Simulation2.ComputeStateHash(hash, quick));
    531124
    532 void CNetReplayTurnManager::StoreFinalReplayTurn(u32 turn)
    533 {
    534     m_FinalTurn = turn;
    535 }
     125    OsPath path = psLogDir() / "oos_dump.txt";
     126    std::ofstream file (OsString(path).c_str(), std::ofstream::out | std::ofstream::trunc);
     127    m_Simulation2.DumpDebugState(file);
     128    file.close();
    536129
    537 void CNetReplayTurnManager::NotifyFinishedUpdate(u32 turn)
    538 {
    539     if (turn == 1 && m_FinalTurn == 0)
    540         g_GUI->SendEventToAll("ReplayFinished");
     130    hash = Hexify(hash);
     131    const std::string& expectedHashHex = Hexify(expectedHash);
    541132
    542     if (turn > m_FinalTurn)
    543         return;
     133    m_HasSyncError = true;
    544134
    545     DoTurn(turn);
     135    std::stringstream msg;
     136    msg << "Out of sync on turn " << turn;
    546137
    547     // Compare hash if it exists in the replay and if we didn't have an OOS already
    548     if (m_HasSyncError || m_ReplayHash.find(turn) == m_ReplayHash.end())
    549         return;
     138    for (size_t i = 0; i < playerNames.size(); ++i)
     139        msg << (i == 0 ? "\nPlayers: " : ", ") << utf8_from_wstring(playerNames[i].m_Name);
    550140
    551     std::string expectedHash = m_ReplayHash[turn].first;
    552     bool quickHash = m_ReplayHash[turn].second;
     141    msg << "\n\n" << "Your game state is " << (expectedHash == hash ? "identical to" : "different from")
     142        << " the hosts game state." << "\n\n" << "Dumping current state to " << CStr(path.string8()).EscapeToPrintableASCII();
    553143
    554     // Compute hash
    555     std::string hash;
    556     ENSURE(m_Simulation2.ComputeStateHash(hash, quickHash));
    557     hash = Hexify(hash);
     144    LOGERROR("%s", msg.str());
    558145
    559     if (hash != expectedHash)
    560         DisplayOOSError(turn, hash, expectedHash, true);
     146    if (g_GUI)
     147        g_GUI->DisplayMessageBox(600, 350, L"Sync error", wstring_from_utf8(msg.str()));
    561148}
    562149
    563 void CNetReplayTurnManager::DoTurn(u32 turn)
     150void CNetClientTurnManager::OnSimulationMessage(CSimulationMessage* msg)
    564151{
    565     debug_printf("Executing turn %u of %u\n", turn, m_FinalTurn);
    566 
    567     m_TurnLength = m_ReplayTurnLengths[turn];
    568 
    569     // Simulate commands for that turn
    570     for (const std::pair<player_id_t, std::string>& p : m_ReplayCommands[turn])
    571     {
    572         JS::RootedValue command(m_Simulation2.GetScriptInterface().GetContext());
    573         m_Simulation2.GetScriptInterface().ParseJSON(p.second, &command);
    574         AddCommand(m_ClientId, p.first, command, m_CurrentTurn + 1);
    575     }
    576 
    577     if (turn == m_FinalTurn)
    578         g_GUI->SendEventToAll("ReplayFinished");
     152    // Command received from the server - store it for later execution
     153    AddCommand(msg->m_Client, msg->m_Player, msg->m_Data, msg->m_Turn);
    579154}
    580155
    581156CNetServerTurnManager::CNetServerTurnManager(CNetServerWorker& server) :
    582157    m_NetServer(server), m_ReadyTurn(1), m_TurnLength(DEFAULT_TURN_LENGTH_MP), m_HasSyncError(false)
    583158{
  • source/network/NetTurnManager.h

     
    1616 */
    1717
    1818#ifndef INCLUDED_NETTURNMANAGER
    1919#define INCLUDED_NETTURNMANAGER
    2020
    21 #include "simulation2/helpers/SimulationCommand.h"
    22 #include "lib/os_path.h"
    23 #include "NetMessage.h"
    24 
    25 #include <list>
    26 #include <map>
    27 #include <vector>
    28 
    29 extern const u32 DEFAULT_TURN_LENGTH_MP;
    30 extern const u32 DEFAULT_TURN_LENGTH_SP;
     21#include "network/NetMessage.h"
     22#include "simulation2/system/TurnManager.h"
    3123
    3224class CNetServerWorker;
    3325class CNetClient;
    34 class CSimulationMessage;
    35 class CSimulation2;
    36 class IReplayLogger;
    37 
    38 /*
    39  * This file deals with the logic of the network turn system. The basic idea is as in
    40  * http://www.gamasutra.com/view/feature/3094/1500_archers_on_a_288_network_.php?print=1
    41  *
    42  * Each player performs the simulation for turn N.
    43  * User input is translated into commands scheduled for execution in turn N+2 which are
    44  * distributed to all other clients.
    45  * After a while, a player wants to perform the simulation for turn N+1,
    46  * which first requires that it has all the other clients' commands for turn N+1.
    47  * In that case, it does the simulation and tells all the other clients (via the server)
    48  * it has finished sending commands for turn N+2, and it starts sending commands for turn N+3.
    49  *
    50  * Commands are redistributed immediately by the server.
    51  * To ensure a consistent execution of commands, they are each associated with a
    52  * client session ID (which is globally unique and consistent), which is used to sort them.
    53  */
    54 
    55 /**
    56  * Common network turn system (used by clients and offline games).
    57  */
    58 class CNetTurnManager
    59 {
    60     NONCOPYABLE(CNetTurnManager);
    61 public:
    62     /**
    63      * Construct for a given network session ID.
    64      */
    65     CNetTurnManager(CSimulation2& simulation, u32 defaultTurnLength, int clientId, IReplayLogger& replay);
    66 
    67     virtual ~CNetTurnManager() { }
    68 
    69     void ResetState(u32 newCurrentTurn, u32 newReadyTurn);
    70 
    71     /**
    72      * Set the current user's player ID, which will be added into command messages.
    73      */
    74     void SetPlayerID(int playerId);
    75 
    76     /**
    77      * Advance the simulation by a certain time. If this brings us past the current
    78      * turn length, the next turns are processed and the function returns true.
    79      * Otherwise, nothing happens and it returns false.
    80      *
    81      * @param simFrameLength Length of the previous frame, in simulation seconds
    82      * @param maxTurns Maximum number of turns to simulate at once
    83      */
    84     bool Update(float simFrameLength, size_t maxTurns);
    85 
    86     /**
    87      * Advance the simulation by as much as possible. Intended for catching up
    88      * over a small number of turns when rejoining a multiplayer match.
    89      * Returns true if it advanced by at least one turn.
    90      */
    91     bool UpdateFastForward();
    92 
    93     /**
    94      * Returns whether Update(simFrameLength, ...) will process at least one new turn.
    95      * @param simFrameLength Length of the previous frame, in simulation seconds
    96      */
    97     bool WillUpdate(float simFrameLength);
    98 
    99     /**
    100      * Advance the graphics by a certain time.
    101      * @param simFrameLength Length of the previous frame, in simulation seconds
    102      * @param realFrameLength Length of the previous frame, in real time seconds
    103      */
    104     void Interpolate(float simFrameLength, float realFrameLength);
    105 
    106     /**
    107      * Called by networking code when a simulation message is received.
    108      */
    109     virtual void OnSimulationMessage(CSimulationMessage* msg) = 0;
    110 
    111     /**
    112      * Called when there has been an out-of-sync error.
    113      */
    114     virtual void OnSyncError(u32 turn, const CStr& expectedHash, std::vector<CSyncErrorMessage::S_m_PlayerNames>& playerNames);
    115 
    116     /**
    117      * Shows a message box when an out of sync error has been detected in the session or visual replay.
    118      */
    119     virtual void DisplayOOSError(u32 turn, const CStr& hash, const CStr& expectedHash, bool isReplay, std::vector<CSyncErrorMessage::S_m_PlayerNames>* playerNames, OsPath* path);
    120 
    121     /**
    122      * Called by simulation code, to add a new command to be distributed to all clients and executed soon.
    123      */
    124     virtual void PostCommand(JS::HandleValue data) = 0;
    125 
    126     /**
    127      * Called when all commands for a given turn have been received.
    128      * This allows Update to progress to that turn.
    129      */
    130     void FinishedAllCommands(u32 turn, u32 turnLength);
    131 
    132     /**
    133      * Enables the recording of state snapshots every @p numTurns,
    134      * which can be jumped back to via RewindTimeWarp().
    135      * If @p numTurns is 0 then recording is disabled.
    136      */
    137     void EnableTimeWarpRecording(size_t numTurns);
    138 
    139     /**
    140      * Jumps back to the latest recorded state snapshot (if any).
    141      */
    142     void RewindTimeWarp();
    143 
    144     void QuickSave();
    145     void QuickLoad();
    146 
    147     u32 GetCurrentTurn() { return m_CurrentTurn; }
    148 
    149 protected:
    150     /**
    151      * Store a command to be executed at a given turn.
    152      */
    153     void AddCommand(int client, int player, JS::HandleValue data, u32 turn);
    154 
    155     /**
    156      * Called when this client has finished sending all its commands scheduled for the given turn.
    157      */
    158     virtual void NotifyFinishedOwnCommands(u32 turn) = 0;
    159 
    160     /**
    161      * Called when this client has finished a simulation update.
    162      */
    163     virtual void NotifyFinishedUpdate(u32 turn) = 0;
    164 
    165     /**
    166      * Returns whether we should compute a complete state hash for the given turn,
    167      * instead of a quick less-complete hash.
    168      */
    169     bool TurnNeedsFullHash(u32 turn);
    170 
    171     CSimulation2& m_Simulation2;
    172 
    173     /// The turn that we have most recently executed
    174     u32 m_CurrentTurn;
    175 
    176     /// The latest turn for which we have received all commands from all clients
    177     u32 m_ReadyTurn;
    178 
    179     // Current turn length
    180     u32 m_TurnLength;
    181 
    182     /// Commands queued at each turn (index 0 is for m_CurrentTurn+1)
    183     std::deque<std::map<u32, std::vector<SimulationCommand>>> m_QueuedCommands;
    184 
    185     int m_PlayerId;
    186     uint m_ClientId;
    187 
    188     /// Simulation time remaining until we ought to execute the next turn (as a negative value to
    189     /// add elapsed time increments to until we reach 0).
    190     float m_DeltaSimTime;
    191 
    192     bool m_HasSyncError;
    193 
    194     IReplayLogger& m_Replay;
    195 
    196     // The number of the last turn that is allowed to be executed (used for replays)
    197     u32 m_FinalTurn;
    198 
    199 private:
    200     size_t m_TimeWarpNumTurns; // 0 if disabled
    201     std::list<std::string> m_TimeWarpStates;
    202     std::string m_QuickSaveState; // TODO: should implement a proper disk-based quicksave system
    203     std::string m_QuickSaveMetadata;
    204 };
    205 
    20626
    20727/**
    208  * Implementation of CNetTurnManager for network clients.
     28 * Implementation of CTurnManager for network clients.
    20929 */
    210 class CNetClientTurnManager : public CNetTurnManager
     30class CNetClientTurnManager : public CTurnManager
    21131{
    21232public:
    21333    CNetClientTurnManager(CSimulation2& simulation, CNetClient& client, int clientId, IReplayLogger& replay);
    21434
    21535    virtual void OnSimulationMessage(CSimulationMessage* msg);
    21636
    21737    virtual void PostCommand(JS::HandleValue data);
    21838
    21939    /**
    220      * Notifiy the server that all commands are sent to prepare the connection for termination.
     40     * Notify the server that all commands are sent to prepare the connection for termination.
    22141     */
    22242    void OnDestroyConnection();
    22343
     44    void OnSyncError(u32 turn, const CStr& expectedHash, const std::vector<CSyncErrorMessage::S_m_PlayerNames>& playerNames);
     45
    22446protected:
    22547    virtual void NotifyFinishedOwnCommands(u32 turn);
    22648
    22749    virtual void NotifyFinishedUpdate(u32 turn);
    22850
    22951    CNetClient& m_NetClient;
    23052};
    23153
    23254/**
    233  * Implementation of CNetTurnManager for offline games.
    234  */
    235 class CNetLocalTurnManager : public CNetTurnManager
    236 {
    237 public:
    238     CNetLocalTurnManager(CSimulation2& simulation, IReplayLogger& replay);
    239 
    240     virtual void OnSimulationMessage(CSimulationMessage* msg);
    241 
    242     virtual void PostCommand(JS::HandleValue data);
    243 
    244 protected:
    245     virtual void NotifyFinishedOwnCommands(u32 turn);
    246 
    247     virtual void NotifyFinishedUpdate(u32 turn);
    248 };
    249 
    250 
    251 
    252 /**
    253  * Implementation of CNetTurnManager for replay games.
    254  */
    255 class CNetReplayTurnManager : public CNetLocalTurnManager
    256 {
    257 public:
    258     CNetReplayTurnManager(CSimulation2& simulation, IReplayLogger& replay);
    259 
    260     void StoreReplayCommand(u32 turn, int player, const std::string& command);
    261 
    262     void StoreReplayTurnLength(u32 turn, u32 turnLength);
    263 
    264     void StoreReplayHash(u32 turn, const std::string& hash, bool quick);
    265 
    266     void StoreFinalReplayTurn(u32 turn);
    267 
    268 
    269 protected:
    270     virtual void NotifyFinishedUpdate(u32 turn);
    271 
    272     void DoTurn(u32 turn);
    273 
    274     // Contains the commands of every player on each turn
    275     std::map<u32, std::vector<std::pair<player_id_t, std::string>>> m_ReplayCommands;
    276 
    277     // Contains the length of every turn
    278     std::map<u32, u32> m_ReplayTurnLengths;
    279 
    280     // Contains all replay hash values and weather or not the quick hash method was used
    281     std::map<u32, std::pair<std::string, bool>> m_ReplayHash;
    282 };
    283 /**
    28455 * The server-side counterpart to CNetClientTurnManager.
    28556 * Records the turn state of each client, and sends turn advancement messages
    28657 * when all clients are ready.
    28758 *
    28859 * Thread-safety:
  • source/network/tests/test_Net.h

     
    2121#include "lib/external_libraries/enet.h"
    2222#include "lib/external_libraries/libsdl.h"
    2323#include "lib/tex/tex.h"
    2424#include "network/NetServer.h"
    2525#include "network/NetClient.h"
    26 #include "network/NetTurnManager.h"
    2726#include "network/NetMessage.h"
    2827#include "network/NetMessages.h"
    2928#include "ps/CLogger.h"
    3029#include "ps/Game.h"
    3130#include "ps/Filesystem.h"
    3231#include "ps/Loader.h"
    3332#include "ps/XML/Xeromyces.h"
    3433#include "scriptinterface/ScriptInterface.h"
    3534#include "simulation2/Simulation2.h"
     35#include "simulation2/system/TurnManager.h"
    3636
    3737class TestNetComms : public CxxTest::TestSuite
    3838{
    3939public:
    4040    void setUp()
    public:  
    137137        // and prints a load of debug output so you can see if anything funny's going on
    138138
    139139        ScriptInterface scriptInterface("Engine", "Test", g_ScriptRuntime);
    140140        JSContext* cx = scriptInterface.GetContext();
    141141        JSAutoRequest rq(cx);
    142        
     142
    143143        TestStdoutLogger logger;
    144144
    145145        std::vector<CNetClient*> clients;
    146146
    147147        CGame client1Game(true);
    public:  
    202202    void test_rejoin_DISABLED()
    203203    {
    204204        ScriptInterface scriptInterface("Engine", "Test", g_ScriptRuntime);
    205205        JSContext* cx = scriptInterface.GetContext();
    206206        JSAutoRequest rq(cx);
    207        
     207
    208208        TestStdoutLogger logger;
    209209
    210210        std::vector<CNetClient*> clients;
    211211
    212212        CGame client1Game(true);
    public:  
    308308        client1Game.GetTurnManager()->Update(1.0f, 1);
    309309        client3Game.GetTurnManager()->Update(1.0f, 1);
    310310        wait(clients, 100);
    311311
    312312        // (This SetTurnLength thing doesn't actually detect errors unless you change
    313         // CNetTurnManager::TurnNeedsFullHash to always return true)
     313        // CTurnManager::TurnNeedsFullHash to always return true)
    314314
    315315        {
    316316            JS::RootedValue cmd(cx);
    317317            client1.GetScriptInterface().Eval("({type:'debug-print', message:'[>>> client1 test sim command 3]\\n'})", &cmd);
    318318            client1Game.GetTurnManager()->PostCommand(cmd);
  • source/ps/Game.cpp

     
    2727#include "gui/CGUI.h"
    2828#include "lib/config2.h"
    2929#include "lib/timer.h"
    3030#include "network/NetClient.h"
    3131#include "network/NetServer.h"
    32 #include "network/NetTurnManager.h"
    3332#include "ps/CConsole.h"
    3433#include "ps/CLogger.h"
    3534#include "ps/CStr.h"
    3635#include "ps/Loader.h"
    3736#include "ps/LoaderThunks.h"
     
    4544#include "renderer/WaterManager.h"
    4645#include "scriptinterface/ScriptInterface.h"
    4746#include "simulation2/Simulation2.h"
    4847#include "simulation2/components/ICmpPlayer.h"
    4948#include "simulation2/components/ICmpPlayerManager.h"
     49#include "simulation2/system/TurnManager.h"
    5050#include "soundmanager/ISoundManager.h"
    5151
    5252#include "tools/atlas/GameInterface/GameLoop.h"
    5353
    5454extern bool g_GameRestarted;
    CGame::CGame(bool disableGraphics, bool  
    8585    // Need to set the CObjectManager references after various objects have
    8686    // been initialised, so do it here rather than via the initialisers above.
    8787    if (m_GameView)
    8888        m_World->GetUnitManager().SetObjectManager(m_GameView->GetObjectManager());
    8989
    90     m_TurnManager = new CNetLocalTurnManager(*m_Simulation2, GetReplayLogger()); // this will get replaced if we're a net server/client
     90    m_TurnManager = new CLocalTurnManager(*m_Simulation2, GetReplayLogger()); // this will get replaced if we're a net server/client
    9191
    9292    m_Simulation2->LoadDefaultScripts();
    9393}
    9494
    9595/**
    CGame::~CGame()  
    108108    delete m_World;
    109109    delete m_ReplayLogger;
    110110    delete m_ReplayStream;
    111111}
    112112
    113 void CGame::SetTurnManager(CNetTurnManager* turnManager)
     113void CGame::SetTurnManager(CTurnManager* turnManager)
    114114{
    115115    if (m_TurnManager)
    116116        delete m_TurnManager;
    117117
    118118    m_TurnManager = turnManager;
    int CGame::LoadVisualReplayData()  
    125125{
    126126    ENSURE(m_IsVisualReplay);
    127127    ENSURE(!m_ReplayPath.empty());
    128128    ENSURE(m_ReplayStream);
    129129
    130     CNetReplayTurnManager* replayTurnMgr = static_cast<CNetReplayTurnManager*>(GetTurnManager());
     130    CReplayTurnManager* replayTurnMgr = static_cast<CReplayTurnManager*>(GetTurnManager());
    131131
    132132    u32 currentTurn = 0;
    133133    std::string type;
    134134    while ((*m_ReplayStream >> type).good())
    135135    {
    bool CGame::StartVisualReplay(const std:  
    173173    debug_printf("Starting to replay %s\n", replayPath.c_str());
    174174
    175175    m_IsVisualReplay = true;
    176176    ScriptInterface& scriptInterface = m_Simulation2->GetScriptInterface();
    177177
    178     SetTurnManager(new CNetReplayTurnManager(*m_Simulation2, GetReplayLogger()));
     178    SetTurnManager(new CReplayTurnManager(*m_Simulation2, GetReplayLogger()));
    179179
    180180    m_ReplayPath = replayPath;
    181181    m_ReplayStream = new std::ifstream(m_ReplayPath.c_str());
    182182
    183183    std::string type;
    bool CGame::StartVisualReplay(const std:  
    200200void CGame::RegisterInit(const JS::HandleValue attribs, const std::string& savedState)
    201201{
    202202    ScriptInterface& scriptInterface = m_Simulation2->GetScriptInterface();
    203203    JSContext* cx = scriptInterface.GetContext();
    204204    JSAutoRequest rq(cx);
    205    
     205
    206206    m_InitialSavedState = savedState;
    207207    m_IsSavedGame = !savedState.empty();
    208208
    209209    m_Simulation2->SetInitAttributes(attribs);
    210210
    int CGame::LoadInitialState()  
    286286 **/
    287287PSRETURN CGame::ReallyStartGame()
    288288{
    289289    JSContext* cx = m_Simulation2->GetScriptInterface().GetContext();
    290290    JSAutoRequest rq(cx);
    291    
     291
    292292    // Call the script function InitGame only for new games, not saved games
    293293    if (!m_IsSavedGame)
    294294    {
    295         // Perform some simulation initializations (replace skirmish entities, explore territories, etc.) 
     295        // Perform some simulation initializations (replace skirmish entities, explore territories, etc.)
    296296        // that needs to be done before setting up the AI and shouldn't be done in Atlas
    297297        if (!g_AtlasGameLoop->running)
    298298            m_Simulation2->PreInitGame();
    299299
    300300        JS::RootedValue settings(cx);
    PSRETURN CGame::ReallyStartGame()  
    308308    // and we could end up rendering before having set up any models (so they'd
    309309    // all be invisible)
    310310    Interpolate(0, 0);
    311311
    312312    m_GameStarted=true;
    313    
     313
    314314    // Render a frame to begin loading assets
    315315    if (CRenderer::IsInitialised())
    316316        Render();
    317317
    318318    if (g_NetClient)
    void CGame::Update(const double deltaRea  
    378378{
    379379    if (m_Paused || !m_TurnManager)
    380380        return;
    381381
    382382    const double deltaSimTime = deltaRealTime * m_SimRate;
    383    
     383
    384384    if (deltaSimTime)
    385385    {
    386386        // To avoid confusing the profiler, we need to trigger the new turn
    387387        // while we're not nested inside any PROFILE blocks
    388388        if (m_TurnManager->WillUpdate(deltaSimTime))
  • source/ps/Game.h

     
    2525#include "simulation2/helpers/Player.h"
    2626
    2727class CWorld;
    2828class CSimulation2;
    2929class CGameView;
    30 class CNetTurnManager;
     30class CTurnManager;
    3131class IReplayLogger;
    3232struct CColor;
    3333
    3434/**
    3535 * The container that holds the rules, resources and attributes of the game.
    class CGame  
    7575    /**
    7676     * Differs from m_PlayerID if a defeated player or observer views another player.
    7777     */
    7878    player_id_t m_ViewedPlayerID;
    7979
    80     CNetTurnManager* m_TurnManager;
     80    CTurnManager* m_TurnManager;
    8181
    8282public:
    8383    CGame(bool disableGraphics = false, bool replayLog = true);
    8484    ~CGame();
    8585
    public:  
    183183
    184184    /**
    185185     * Replace the current turn manager.
    186186     * This class will take ownership of the pointer.
    187187     */
    188     void SetTurnManager(CNetTurnManager* turnManager);
     188    void SetTurnManager(CTurnManager* turnManager);
    189189
    190     CNetTurnManager* GetTurnManager() const
     190    CTurnManager* GetTurnManager() const
    191191    {   return m_TurnManager; }
    192192
    193193    IReplayLogger& GetReplayLogger() const
    194194    {   return *m_ReplayLogger; }
    195195
  • source/simulation2/components/CCmpCommandQueue.cpp

     
    1 /* Copyright (C) 2010 Wildfire Games.
     1/* Copyright (C) 2016 Wildfire Games.
    22 * This file is part of 0 A.D.
    33 *
    44 * 0 A.D. is free software: you can redistribute it and/or modify
    55 * it under the terms of the GNU General Public License as published by
    66 * the Free Software Foundation, either version 2 of the License, or
     
    2121#include "ICmpCommandQueue.h"
    2222
    2323#include "ps/CLogger.h"
    2424#include "ps/Game.h"
    2525#include "ps/Profile.h"
    26 #include "network/NetTurnManager.h"
     26#include "simulation2/system/TurnManager.h"
    2727
    2828class CCmpCommandQueue : public ICmpCommandQueue
    2929{
    3030public:
    3131    static void ClassInit(CComponentManager& UNUSED(componentManager))
    public:  
    6464
    6565    virtual void Deserialize(const CParamNode& UNUSED(paramNode), IDeserializer& deserialize)
    6666    {
    6767        JSContext* cx = GetSimContext().GetScriptInterface().GetContext();
    6868        JSAutoRequest rq(cx);
    69    
     69
    7070        u32 numCmds;
    7171        deserialize.NumberU32_Unbounded("num commands", numCmds);
    7272        for (size_t i = 0; i < numCmds; ++i)
    7373        {
    7474            i32 player;
    public:  
    8787
    8888    virtual void PostNetworkCommand(JS::HandleValue cmd1)
    8989    {
    9090        JSContext* cx = GetSimContext().GetScriptInterface().GetContext();
    9191        JSAutoRequest rq(cx);
    92        
     92
    9393        // TODO: This is a workaround because we need to pass a MutableHandle to StringifyJSON.
    9494        JS::RootedValue cmd(cx, cmd1.get());
    9595
    9696        PROFILE2_EVENT("post net command");
    9797        PROFILE2_ATTR("command: %s", GetSimContext().GetScriptInterface().StringifyJSON(&cmd, false).c_str());
    public:  
    104104    virtual void FlushTurn(const std::vector<SimulationCommand>& commands)
    105105    {
    106106        ScriptInterface& scriptInterface = GetSimContext().GetScriptInterface();
    107107        JSContext* cx = scriptInterface.GetContext();
    108108        JSAutoRequest rq(cx);
    109        
     109
    110110        JS::RootedValue global(cx, scriptInterface.GetGlobalObject());
    111111        std::vector<SimulationCommand> localCommands;
    112112        m_LocalQueue.swap(localCommands);
    113113
    114114        for (size_t i = 0; i < localCommands.size(); ++i)
  • source/simulation2/system/TurnManager.cpp

     
     1/* Copyright (C) 2016 Wildfire Games.
     2 * This file is part of 0 A.D.
     3 *
     4 * 0 A.D. is free software: you can redistribute it and/or modify
     5 * it under the terms of the GNU General Public License as published by
     6 * the Free Software Foundation, either version 2 of the License, or
     7 * (at your option) any later version.
     8 *
     9 * 0 A.D. is distributed in the hope that it will be useful,
     10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
     11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     12 * GNU General Public License for more details.
     13 *
     14 * You should have received a copy of the GNU General Public License
     15 * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
     16 */
     17
     18#include "precompiled.h"
     19
     20#include "TurnManager.h"
     21
     22#include "gui/GUIManager.h"
     23#include "maths/MathUtil.h"
     24#include "ps/CLogger.h"
     25#include "ps/Profile.h"
     26#include "ps/Pyrogenesis.h"
     27#include "ps/Replay.h"
     28#include "simulation2/Simulation2.h"
     29
     30#include <sstream>
     31#include <fstream>
     32#include <iomanip>
     33
     34const u32 DEFAULT_TURN_LENGTH_MP = 500;
     35const u32 DEFAULT_TURN_LENGTH_SP = 200;
     36const int COMMAND_DELAY = 2;
     37
     38#if 0
     39#define TURN_LOG(args) debug_printf args
     40#else
     41#define TURN_LOG(args)
     42#endif
     43
     44static std::string Hexify(const std::string& s)
     45{
     46    std::stringstream str;
     47    str << std::hex;
     48    for (size_t i = 0; i < s.size(); ++i)
     49        str << std::setfill('0') << std::setw(2) << (int)(unsigned char)s[i];
     50    return str.str();
     51}
     52
     53CTurnManager::CTurnManager(CSimulation2& simulation, u32 defaultTurnLength, int clientId, IReplayLogger& replay) :
     54    m_Simulation2(simulation), m_CurrentTurn(0), m_ReadyTurn(1), m_TurnLength(defaultTurnLength), m_DeltaSimTime(0),
     55    m_PlayerId(-1), m_ClientId(clientId), m_HasSyncError(false), m_Replay(replay),
     56    m_TimeWarpNumTurns(0), m_FinalTurn(std::numeric_limits<u32>::max())
     57{
     58    // When we are on turn n, we schedule new commands for n+2.
     59    // We know that all other clients have finished scheduling commands for n (else we couldn't have got here).
     60    // We know we have not yet finished scheduling commands for n+2.
     61    // Hence other clients can be on turn n-1, n, n+1, and no other.
     62    // So they can be sending us commands scheduled for n+1, n+2, n+3.
     63    // So we need a 3-element buffer:
     64    m_QueuedCommands.resize(COMMAND_DELAY + 1);
     65}
     66
     67void CTurnManager::ResetState(u32 newCurrentTurn, u32 newReadyTurn)
     68{
     69    m_CurrentTurn = newCurrentTurn;
     70    m_ReadyTurn = newReadyTurn;
     71    m_DeltaSimTime = 0;
     72    size_t queuedCommandsSize = m_QueuedCommands.size();
     73    m_QueuedCommands.clear();
     74    m_QueuedCommands.resize(queuedCommandsSize);
     75}
     76
     77void CTurnManager::SetPlayerID(int playerId)
     78{
     79    m_PlayerId = playerId;
     80}
     81
     82bool CTurnManager::WillUpdate(float simFrameLength)
     83{
     84    // Keep this in sync with the return value of Update()
     85
     86    if (m_CurrentTurn > m_FinalTurn)
     87        return false;
     88
     89    if (m_DeltaSimTime + simFrameLength < 0)
     90        return false;
     91
     92    if (m_ReadyTurn <= m_CurrentTurn)
     93        return false;
     94
     95    return true;
     96}
     97
     98bool CTurnManager::Update(float simFrameLength, size_t maxTurns)
     99{
     100    if (m_CurrentTurn > m_FinalTurn)
     101        return false;
     102
     103    m_DeltaSimTime += simFrameLength;
     104
     105    // If the game becomes laggy, m_DeltaSimTime increases progressively.
     106    // The engine will fast forward accordingly to catch up.
     107    // To keep the game playable, stop fast forwarding after 2 turn lengths.
     108    m_DeltaSimTime = std::min(m_DeltaSimTime, 2.0f * m_TurnLength / 1000.0f);
     109
     110    // If we haven't reached the next turn yet, do nothing
     111    if (m_DeltaSimTime < 0)
     112        return false;
     113
     114    TURN_LOG((L"Update current=%d ready=%d\n", m_CurrentTurn, m_ReadyTurn));
     115
     116    // Check that the next turn is ready for execution
     117    if (m_ReadyTurn <= m_CurrentTurn)
     118    {
     119        // Oops, we wanted to start the next turn but it's not ready yet -
     120        // there must be too much network lag.
     121        // TODO: complain to the user.
     122        // TODO: send feedback to the server to increase the turn length.
     123
     124        // Reset the next-turn timer to 0 so we try again next update but
     125        // so we don't rush to catch up in subsequent turns.
     126        // TODO: we should do clever rate adjustment instead of just pausing like this.
     127        m_DeltaSimTime = 0;
     128
     129        return false;
     130    }
     131
     132    maxTurns = std::max((size_t)1, maxTurns); // always do at least one turn
     133
     134    for (size_t i = 0; i < maxTurns; ++i)
     135    {
     136        // Check that we've reached the i'th next turn
     137        if (m_DeltaSimTime < 0)
     138            break;
     139
     140        // Check that the i'th next turn is still ready
     141        if (m_ReadyTurn <= m_CurrentTurn)
     142            break;
     143
     144        NotifyFinishedOwnCommands(m_CurrentTurn + COMMAND_DELAY);
     145
     146        // Increase now, so Update can send new commands for a subsequent turn
     147        ++m_CurrentTurn;
     148
     149        // Clean up any destroyed entities since the last turn (e.g. placement previews
     150        // or rally point flags generated by the GUI). (Must do this before the time warp
     151        // serialization.)
     152        m_Simulation2.FlushDestroyedEntities();
     153
     154        // Save the current state for rewinding, if enabled
     155        if (m_TimeWarpNumTurns && (m_CurrentTurn % m_TimeWarpNumTurns) == 0)
     156        {
     157            PROFILE3("time warp serialization");
     158            std::stringstream stream;
     159            m_Simulation2.SerializeState(stream);
     160            m_TimeWarpStates.push_back(stream.str());
     161        }
     162
     163        // Put all the client commands into a single list, in a globally consistent order
     164        std::vector<SimulationCommand> commands;
     165        for (std::pair<const u32, std::vector<SimulationCommand>>& p : m_QueuedCommands[0])
     166            commands.insert(commands.end(), std::make_move_iterator(p.second.begin()), std::make_move_iterator(p.second.end()));
     167
     168        m_QueuedCommands.pop_front();
     169        m_QueuedCommands.resize(m_QueuedCommands.size() + 1);
     170
     171        m_Replay.Turn(m_CurrentTurn-1, m_TurnLength, commands);
     172
     173        TURN_LOG((L"Running %d cmds\n", commands.size()));
     174
     175        m_Simulation2.Update(m_TurnLength, commands);
     176
     177        NotifyFinishedUpdate(m_CurrentTurn);
     178
     179        // Set the time for the next turn update
     180        m_DeltaSimTime -= m_TurnLength / 1000.f;
     181    }
     182
     183    return true;
     184}
     185
     186bool CTurnManager::UpdateFastForward()
     187{
     188    m_DeltaSimTime = 0;
     189
     190    TURN_LOG((L"UpdateFastForward current=%d ready=%d\n", m_CurrentTurn, m_ReadyTurn));
     191
     192    // Check that the next turn is ready for execution
     193    if (m_ReadyTurn <= m_CurrentTurn)
     194        return false;
     195
     196    while (m_ReadyTurn > m_CurrentTurn)
     197    {
     198        // TODO: It would be nice to remove some of the duplication with Update()
     199        // (This is similar but doesn't call any Notify functions or update DeltaTime,
     200        // it just updates the simulation state)
     201
     202        ++m_CurrentTurn;
     203
     204        m_Simulation2.FlushDestroyedEntities();
     205
     206        // Put all the client commands into a single list, in a globally consistent order
     207        std::vector<SimulationCommand> commands;
     208        for (std::pair<const u32, std::vector<SimulationCommand>>& p : m_QueuedCommands[0])
     209            commands.insert(commands.end(), std::make_move_iterator(p.second.begin()), std::make_move_iterator(p.second.end()));
     210
     211        m_QueuedCommands.pop_front();
     212        m_QueuedCommands.resize(m_QueuedCommands.size() + 1);
     213
     214        m_Replay.Turn(m_CurrentTurn-1, m_TurnLength, commands);
     215
     216        TURN_LOG((L"Running %d cmds\n", commands.size()));
     217
     218        m_Simulation2.Update(m_TurnLength, commands);
     219    }
     220
     221    return true;
     222}
     223
     224void CTurnManager::Interpolate(float simFrameLength, float realFrameLength)
     225{
     226    // TODO: using m_TurnLength might be a bit dodgy when length changes - maybe
     227    // we need to save the previous turn length?
     228
     229    float offset = clamp(m_DeltaSimTime / (m_TurnLength / 1000.f) + 1.0, 0.0, 1.0);
     230
     231    // Stop animations while still updating the selection highlight
     232    if (m_CurrentTurn > m_FinalTurn)
     233        simFrameLength = 0;
     234
     235    m_Simulation2.Interpolate(simFrameLength, offset, realFrameLength);
     236}
     237
     238void CTurnManager::AddCommand(int client, int player, JS::HandleValue data, u32 turn)
     239{
     240    TURN_LOG((L"AddCommand(client=%d player=%d turn=%d)\n", client, player, turn));
     241
     242    if (!(m_CurrentTurn < turn && turn <= m_CurrentTurn + COMMAND_DELAY + 1))
     243    {
     244        debug_warn(L"Received command for invalid turn");
     245        return;
     246    }
     247
     248    m_Simulation2.GetScriptInterface().FreezeObject(data, true);
     249    m_QueuedCommands[turn - (m_CurrentTurn+1)][client].emplace_back(player, m_Simulation2.GetScriptInterface().GetContext(), data);
     250}
     251
     252void CTurnManager::FinishedAllCommands(u32 turn, u32 turnLength)
     253{
     254    TURN_LOG((L"FinishedAllCommands(%d, %d)\n", turn, turnLength));
     255
     256    ENSURE(turn == m_ReadyTurn + 1);
     257    m_ReadyTurn = turn;
     258    m_TurnLength = turnLength;
     259}
     260
     261bool CTurnManager::TurnNeedsFullHash(u32 turn)
     262{
     263    // Check immediately for errors caused by e.g. inconsistent game versions
     264    // (The hash is computed after the first sim update, so we start at turn == 1)
     265    if (turn == 1)
     266        return true;
     267
     268    // Otherwise check the full state every ~10 seconds in multiplayer games
     269    // (TODO: should probably remove this when we're reasonably sure the game
     270    // isn't too buggy, since the full hash is still pretty slow)
     271    if (turn % 20 == 0)
     272        return true;
     273
     274    return false;
     275}
     276
     277void CTurnManager::EnableTimeWarpRecording(size_t numTurns)
     278{
     279    m_TimeWarpStates.clear();
     280    m_TimeWarpNumTurns = numTurns;
     281}
     282
     283void CTurnManager::RewindTimeWarp()
     284{
     285    if (m_TimeWarpStates.empty())
     286        return;
     287
     288    std::stringstream stream(m_TimeWarpStates.back());
     289    m_Simulation2.DeserializeState(stream);
     290    m_TimeWarpStates.pop_back();
     291
     292    // Reset the turn manager state, so we won't execute stray commands and
     293    // won't do the next snapshot until the appropriate time.
     294    // (Ideally we ought to serialise the turn manager state and restore it
     295    // here, but this is simpler for now.)
     296    ResetState(0, 1);
     297}
     298
     299void CTurnManager::QuickSave()
     300{
     301    TIMER(L"QuickSave");
     302
     303    std::stringstream stream;
     304    if (!m_Simulation2.SerializeState(stream))
     305    {
     306        LOGERROR("Failed to quicksave game");
     307        return;
     308    }
     309
     310    m_QuickSaveState = stream.str();
     311    if (g_GUI)
     312        m_QuickSaveMetadata = g_GUI->GetSavedGameData();
     313    else
     314        m_QuickSaveMetadata = std::string();
     315
     316    LOGMESSAGERENDER("Quicksaved game");
     317
     318}
     319
     320void CTurnManager::QuickLoad()
     321{
     322    TIMER(L"QuickLoad");
     323
     324    if (m_QuickSaveState.empty())
     325    {
     326        LOGERROR("Cannot quickload game - no game was quicksaved");
     327        return;
     328    }
     329
     330    std::stringstream stream(m_QuickSaveState);
     331    if (!m_Simulation2.DeserializeState(stream))
     332    {
     333        LOGERROR("Failed to quickload game");
     334        return;
     335    }
     336
     337    if (g_GUI && !m_QuickSaveMetadata.empty())
     338        g_GUI->RestoreSavedGameData(m_QuickSaveMetadata);
     339
     340    LOGMESSAGERENDER("Quickloaded game");
     341
     342    // See RewindTimeWarp
     343    ResetState(0, 1);
     344}
     345
     346CLocalTurnManager::CLocalTurnManager(CSimulation2& simulation, IReplayLogger& replay) :
     347    CTurnManager(simulation, DEFAULT_TURN_LENGTH_SP, 0, replay)
     348{
     349}
     350
     351void CLocalTurnManager::PostCommand(JS::HandleValue data)
     352{
     353    // Add directly to the next turn, ignoring COMMAND_DELAY,
     354    // because we don't need to compensate for network latency
     355    AddCommand(m_ClientId, m_PlayerId, data, m_CurrentTurn + 1);
     356}
     357
     358void CLocalTurnManager::NotifyFinishedOwnCommands(u32 turn)
     359{
     360    FinishedAllCommands(turn, m_TurnLength);
     361}
     362
     363void CLocalTurnManager::NotifyFinishedUpdate(u32 UNUSED(turn))
     364{
     365#if 0 // this hurts performance and is only useful for verifying log replays
     366    std::string hash;
     367    {
     368        PROFILE3("state hash check");
     369        ENSURE(m_Simulation2.ComputeStateHash(hash));
     370    }
     371    m_Replay.Hash(hash);
     372#endif
     373}
     374
     375void CLocalTurnManager::OnSimulationMessage(CSimulationMessage* UNUSED(msg))
     376{
     377    debug_warn(L"This should never be called");
     378}
     379
     380CReplayTurnManager::CReplayTurnManager(CSimulation2& simulation, IReplayLogger& replay) :
     381    CLocalTurnManager(simulation, replay)
     382{
     383}
     384
     385void CReplayTurnManager::StoreReplayCommand(u32 turn, int player, const std::string& command)
     386{
     387    // Using the pair we make sure that commands per turn will be processed in the correct order
     388    m_ReplayCommands[turn].emplace_back(player, command);
     389}
     390
     391void CReplayTurnManager::StoreReplayHash(u32 turn, const std::string& hash, bool quick)
     392{
     393    m_ReplayHash[turn] = std::make_pair(hash, quick);
     394}
     395
     396void CReplayTurnManager::StoreReplayTurnLength(u32 turn, u32 turnLength)
     397{
     398    m_ReplayTurnLengths[turn] = turnLength;
     399
     400    // Initialize turn length
     401    if (turn == 0)
     402        m_TurnLength = m_ReplayTurnLengths[0];
     403}
     404
     405void CReplayTurnManager::StoreFinalReplayTurn(u32 turn)
     406{
     407    m_FinalTurn = turn;
     408}
     409
     410void CReplayTurnManager::OnSyncError(u32 turn)
     411{
     412    m_HasSyncError = true;
     413
     414    std::stringstream msg;
     415    msg << "Out of sync on turn " << turn << "\n\n" << "The current game state is different from the original game state.";
     416
     417    LOGERROR("%s", msg.str());
     418
     419    if (g_GUI)
     420        g_GUI->DisplayMessageBox(600, 350, L"Sync error", wstring_from_utf8(msg.str()));
     421}
     422
     423void CReplayTurnManager::NotifyFinishedUpdate(u32 turn)
     424{
     425    if (turn == 1 && m_FinalTurn == 0)
     426        g_GUI->SendEventToAll("ReplayFinished");
     427
     428    if (turn > m_FinalTurn)
     429        return;
     430
     431    DoTurn(turn);
     432
     433    // Compare hash if it exists in the replay and if we didn't have an OOS already
     434    if (m_HasSyncError || m_ReplayHash.find(turn) == m_ReplayHash.end())
     435        return;
     436
     437    std::string expectedHash = m_ReplayHash[turn].first;
     438    bool quickHash = m_ReplayHash[turn].second;
     439
     440    // Compute hash
     441    std::string hash;
     442    ENSURE(m_Simulation2.ComputeStateHash(hash, quickHash));
     443    hash = Hexify(hash);
     444
     445    if (hash != expectedHash)
     446        OnSyncError(turn);
     447}
     448
     449void CReplayTurnManager::DoTurn(u32 turn)
     450{
     451    debug_printf("Executing turn %u of %u\n", turn, m_FinalTurn);
     452
     453    m_TurnLength = m_ReplayTurnLengths[turn];
     454
     455    // Simulate commands for that turn
     456    for (const std::pair<player_id_t, std::string>& p : m_ReplayCommands[turn])
     457    {
     458        JS::RootedValue command(m_Simulation2.GetScriptInterface().GetContext());
     459        m_Simulation2.GetScriptInterface().ParseJSON(p.second, &command);
     460        AddCommand(m_ClientId, p.first, command, m_CurrentTurn + 1);
     461    }
     462
     463    if (turn == m_FinalTurn)
     464        g_GUI->SendEventToAll("ReplayFinished");
     465}
  • source/simulation2/system/TurnManager.h

     
     1/* Copyright (C) 2016 Wildfire Games.
     2 * This file is part of 0 A.D.
     3 *
     4 * 0 A.D. is free software: you can redistribute it and/or modify
     5 * it under the terms of the GNU General Public License as published by
     6 * the Free Software Foundation, either version 2 of the License, or
     7 * (at your option) any later version.
     8 *
     9 * 0 A.D. is distributed in the hope that it will be useful,
     10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
     11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     12 * GNU General Public License for more details.
     13 *
     14 * You should have received a copy of the GNU General Public License
     15 * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
     16 */
     17
     18#ifndef INCLUDED_TURNMANAGER
     19#define INCLUDED_TURNMANAGER
     20
     21#include "ps/CStr.h"
     22#include "simulation2/helpers/SimulationCommand.h"
     23
     24#include <list>
     25#include <map>
     26#include <vector>
     27
     28extern const u32 DEFAULT_TURN_LENGTH_MP;
     29extern const u32 DEFAULT_TURN_LENGTH_SP;
     30extern const int COMMAND_DELAY;
     31
     32class CSimulationMessage;
     33class CSimulation2;
     34class IReplayLogger;
     35
     36/*
     37 * This file deals with the logic of the turn system. The basic idea is as in
     38 * http://www.gamasutra.com/view/feature/3094/1500_archers_on_a_288_network_.php?print=1
     39 *
     40 * Each player performs the simulation for turn N.
     41 * User input is translated into commands scheduled for execution in turn N+2 which are
     42 * distributed to all other clients.
     43 * After a while, a player wants to perform the simulation for turn N+1,
     44 * which first requires that it has all the other clients' commands for turn N+1.
     45 * In that case, it does the simulation and tells all the other clients (via the server)
     46 * it has finished sending commands for turn N+2, and it starts sending commands for turn N+3.
     47 *
     48 * Commands are redistributed immediately by the server.
     49 * To ensure a consistent execution of commands, they are each associated with a
     50 * client session ID (which is globally unique and consistent), which is used to sort them.
     51 */
     52
     53/**
     54 * Common turn system (used by clients and offline games).
     55 */
     56class CTurnManager
     57{
     58    NONCOPYABLE(CTurnManager);
     59public:
     60    /**
     61     * Construct for a given session ID.
     62     */
     63    CTurnManager(CSimulation2& simulation, u32 defaultTurnLength, int clientId, IReplayLogger& replay);
     64
     65    virtual ~CTurnManager() { }
     66
     67    void ResetState(u32 newCurrentTurn, u32 newReadyTurn);
     68
     69    /**
     70     * Set the current user's player ID, which will be added into command messages.
     71     */
     72    void SetPlayerID(int playerId);
     73
     74    /**
     75     * Advance the simulation by a certain time. If this brings us past the current
     76     * turn length, the next turns are processed and the function returns true.
     77     * Otherwise, nothing happens and it returns false.
     78     *
     79     * @param simFrameLength Length of the previous frame, in simulation seconds
     80     * @param maxTurns Maximum number of turns to simulate at once
     81     */
     82    bool Update(float simFrameLength, size_t maxTurns);
     83
     84    /**
     85     * Advance the simulation by as much as possible. Intended for catching up
     86     * over a small number of turns when rejoining a multiplayer match.
     87     * Returns true if it advanced by at least one turn.
     88     */
     89    bool UpdateFastForward();
     90
     91    /**
     92     * Returns whether Update(simFrameLength, ...) will process at least one new turn.
     93     * @param simFrameLength Length of the previous frame, in simulation seconds
     94     */
     95    bool WillUpdate(float simFrameLength);
     96
     97    /**
     98     * Advance the graphics by a certain time.
     99     * @param simFrameLength Length of the previous frame, in simulation seconds
     100     * @param realFrameLength Length of the previous frame, in real time seconds
     101     */
     102    void Interpolate(float simFrameLength, float realFrameLength);
     103
     104    /**
     105     * Called when a simulation message is received.
     106     */
     107    virtual void OnSimulationMessage(CSimulationMessage* msg) = 0;
     108
     109    /**
     110     * Called by simulation code, to add a new command to be distributed to all clients and executed soon.
     111     */
     112    virtual void PostCommand(JS::HandleValue data) = 0;
     113
     114    /**
     115     * Called when all commands for a given turn have been received.
     116     * This allows Update to progress to that turn.
     117     */
     118    void FinishedAllCommands(u32 turn, u32 turnLength);
     119
     120    /**
     121     * Enables the recording of state snapshots every @p numTurns,
     122     * which can be jumped back to via RewindTimeWarp().
     123     * If @p numTurns is 0 then recording is disabled.
     124     */
     125    void EnableTimeWarpRecording(size_t numTurns);
     126
     127    /**
     128     * Jumps back to the latest recorded state snapshot (if any).
     129     */
     130    void RewindTimeWarp();
     131
     132    void QuickSave();
     133    void QuickLoad();
     134
     135    u32 GetCurrentTurn() { return m_CurrentTurn; }
     136
     137protected:
     138    /**
     139     * Store a command to be executed at a given turn.
     140     */
     141    void AddCommand(int client, int player, JS::HandleValue data, u32 turn);
     142
     143    /**
     144     * Called when this client has finished sending all its commands scheduled for the given turn.
     145     */
     146    virtual void NotifyFinishedOwnCommands(u32 turn) = 0;
     147
     148    /**
     149     * Called when this client has finished a simulation update.
     150     */
     151    virtual void NotifyFinishedUpdate(u32 turn) = 0;
     152
     153    /**
     154     * Returns whether we should compute a complete state hash for the given turn,
     155     * instead of a quick less-complete hash.
     156     */
     157    bool TurnNeedsFullHash(u32 turn);
     158
     159    CSimulation2& m_Simulation2;
     160
     161    /// The turn that we have most recently executed
     162    u32 m_CurrentTurn;
     163
     164    /// The latest turn for which we have received all commands from all clients
     165    u32 m_ReadyTurn;
     166
     167    // Current turn length
     168    u32 m_TurnLength;
     169
     170    /// Commands queued at each turn (index 0 is for m_CurrentTurn+1)
     171    std::deque<std::map<u32, std::vector<SimulationCommand>>> m_QueuedCommands;
     172
     173    int m_PlayerId;
     174    uint m_ClientId;
     175
     176    /// Simulation time remaining until we ought to execute the next turn (as a negative value to
     177    /// add elapsed time increments to until we reach 0).
     178    float m_DeltaSimTime;
     179
     180    bool m_HasSyncError;
     181
     182    IReplayLogger& m_Replay;
     183
     184    // The number of the last turn that is allowed to be executed (used for replays)
     185    u32 m_FinalTurn;
     186
     187private:
     188    size_t m_TimeWarpNumTurns; // 0 if disabled
     189    std::list<std::string> m_TimeWarpStates;
     190    std::string m_QuickSaveState; // TODO: should implement a proper disk-based quicksave system
     191    std::string m_QuickSaveMetadata;
     192};
     193
     194/**
     195 * Implementation of CTurnManager for offline games.
     196 */
     197class CLocalTurnManager : public CTurnManager
     198{
     199public:
     200    CLocalTurnManager(CSimulation2& simulation, IReplayLogger& replay);
     201
     202    virtual void OnSimulationMessage(CSimulationMessage* msg);
     203
     204    virtual void PostCommand(JS::HandleValue data);
     205
     206protected:
     207    virtual void NotifyFinishedOwnCommands(u32 turn);
     208
     209    virtual void NotifyFinishedUpdate(u32 turn);
     210};
     211
     212/**
     213 * Implementation of CTurnManager for replay games.
     214 */
     215class CReplayTurnManager : public CLocalTurnManager
     216{
     217public:
     218    CReplayTurnManager(CSimulation2& simulation, IReplayLogger& replay);
     219
     220    void StoreReplayCommand(u32 turn, int player, const std::string& command);
     221
     222    void StoreReplayTurnLength(u32 turn, u32 turnLength);
     223
     224    void StoreReplayHash(u32 turn, const std::string& hash, bool quick);
     225
     226    void StoreFinalReplayTurn(u32 turn);
     227
     228    void OnSyncError(u32 turn);
     229
     230protected:
     231    virtual void NotifyFinishedUpdate(u32 turn);
     232
     233    void DoTurn(u32 turn);
     234
     235    // Contains the commands of every player on each turn
     236    std::map<u32, std::vector<std::pair<player_id_t, std::string>>> m_ReplayCommands;
     237
     238    // Contains the length of every turn
     239    std::map<u32, u32> m_ReplayTurnLengths;
     240
     241    // Contains all replay hash values and weather or not the quick hash method was used
     242    std::map<u32, std::pair<std::string, bool>> m_ReplayHash;
     243};
     244
     245#endif // INCLUDED_TURNMANAGER