LCOV - code coverage report
Current view: top level - source/simulation2/system - TurnManager.cpp (source / functions) Hit Total Coverage
Test: 0 A.D. test coverage report Lines: 2 137 1.5 %
Date: 2023-01-19 00:18:29 Functions: 2 15 13.3 %

          Line data    Source code
       1             : /* Copyright (C) 2021 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/Pyrogenesis.h"
      25             : #include "ps/Profile.h"
      26             : #include "ps/CLogger.h"
      27             : #include "ps/Replay.h"
      28             : #include "ps/Util.h"
      29             : #include "scriptinterface/Object.h"
      30             : #include "simulation2/Simulation2.h"
      31             : 
      32             : #if 0
      33             : #define NETTURN_LOG(...) debug_printf(__VA_ARGS__)
      34             : #else
      35             : #define NETTURN_LOG(...)
      36             : #endif
      37             : 
      38           1 : const CStr CTurnManager::EventNameSavegameLoaded = "SavegameLoaded";
      39             : 
      40           0 : CTurnManager::CTurnManager(CSimulation2& simulation, u32 defaultTurnLength, u32 commandDelay, int clientId, IReplayLogger& replay)
      41           0 :     : m_Simulation2(simulation), m_CurrentTurn(0), m_CommandDelay(commandDelay), m_ReadyTurn(commandDelay - 1), m_TurnLength(defaultTurnLength),
      42             :     m_PlayerId(-1), m_ClientId(clientId), m_DeltaSimTime(0), m_Replay(replay),
      43           0 :     m_FinalTurn(std::numeric_limits<u32>::max()), m_TimeWarpNumTurns(0)
      44             : {
      45           0 :     ScriptRequest rq(m_Simulation2.GetScriptInterface());
      46           0 :     m_QuickSaveMetadata.init(rq.cx);
      47           0 :     m_QueuedCommands.resize(1);
      48           0 : }
      49             : 
      50           0 : void CTurnManager::ResetState(u32 newCurrentTurn, u32 newReadyTurn)
      51             : {
      52           0 :     m_CurrentTurn = newCurrentTurn;
      53           0 :     m_ReadyTurn = newReadyTurn;
      54           0 :     m_DeltaSimTime = 0;
      55           0 :     size_t queuedCommandsSize = m_QueuedCommands.size();
      56           0 :     m_QueuedCommands.clear();
      57           0 :     m_QueuedCommands.resize(queuedCommandsSize);
      58           0 : }
      59             : 
      60           0 : void CTurnManager::SetPlayerID(int playerId)
      61             : {
      62           0 :     m_PlayerId = playerId;
      63           0 : }
      64             : 
      65           0 : bool CTurnManager::Update(float simFrameLength, size_t maxTurns)
      66             : {
      67           0 :     if (m_CurrentTurn > m_FinalTurn)
      68           0 :         return false;
      69             : 
      70           0 :     m_DeltaSimTime += simFrameLength;
      71             : 
      72             :     // If the game becomes laggy, m_DeltaSimTime increases progressively.
      73             :     // The engine will fast forward accordingly to catch up.
      74             :     // To keep the game playable, stop fast forwarding after 2 turn lengths.
      75           0 :     m_DeltaSimTime = std::min(m_DeltaSimTime, 2.0f * m_TurnLength / 1000.0f);
      76             : 
      77             :     // If we haven't reached the next turn yet, do nothing
      78           0 :     if (m_DeltaSimTime < 0)
      79           0 :         return false;
      80             : 
      81             :     NETTURN_LOG("Update current=%d ready=%d\n", m_CurrentTurn, m_ReadyTurn);
      82             : 
      83             :     // Check that the next turn is ready for execution
      84           0 :     if (m_ReadyTurn <= m_CurrentTurn && m_CommandDelay > 1)
      85             :     {
      86             :         // Oops, we wanted to start the next turn but it's not ready yet -
      87             :         // there must be too much network lag.
      88             :         // TODO: complain to the user.
      89             :         // TODO: send feedback to the server to increase the turn length.
      90             : 
      91             :         // Reset the next-turn timer to 0 so we try again next update but
      92             :         // so we don't rush to catch up in subsequent turns.
      93             :         // TODO: we should do clever rate adjustment instead of just pausing like this.
      94           0 :         m_DeltaSimTime = 0;
      95             : 
      96           0 :         return false;
      97             :     }
      98             : 
      99           0 :     maxTurns = std::max((size_t)1, maxTurns); // always do at least one turn
     100             : 
     101           0 :     for (size_t i = 0; i < maxTurns; ++i)
     102             :     {
     103             :         // Check that we've reached the i'th next turn
     104           0 :         if (m_DeltaSimTime < 0)
     105           0 :             break;
     106             : 
     107             :         // Check that the i'th next turn is still ready
     108           0 :         if (m_ReadyTurn <= m_CurrentTurn && m_CommandDelay > 1)
     109           0 :             break;
     110             : 
     111             :         // To avoid confusing the profiler, we need to trigger the new turn
     112             :         // while we're not nested inside any PROFILE blocks
     113           0 :         g_Profiler.Turn();
     114             : 
     115           0 :         NotifyFinishedOwnCommands(m_CurrentTurn + m_CommandDelay);
     116             : 
     117             :         // Increase now, so Update can send new commands for a subsequent turn
     118           0 :         ++m_CurrentTurn;
     119             : 
     120             :         // Clean up any destroyed entities since the last turn (e.g. placement previews
     121             :         // or rally point flags generated by the GUI). (Must do this before the time warp
     122             :         // serialization.)
     123           0 :         m_Simulation2.FlushDestroyedEntities();
     124             : 
     125             :         // Save the current state for rewinding, if enabled
     126           0 :         if (m_TimeWarpNumTurns && (m_CurrentTurn % m_TimeWarpNumTurns) == 0)
     127             :         {
     128           0 :             PROFILE3("time warp serialization");
     129           0 :             std::stringstream stream;
     130           0 :             m_Simulation2.SerializeState(stream);
     131           0 :             m_TimeWarpStates.push_back(stream.str());
     132             :         }
     133             : 
     134             :         // Put all the client commands into a single list, in a globally consistent order
     135           0 :         std::vector<SimulationCommand> commands;
     136           0 :         for (std::pair<const u32, std::vector<SimulationCommand>>& p : m_QueuedCommands[0])
     137           0 :             commands.insert(commands.end(), std::make_move_iterator(p.second.begin()), std::make_move_iterator(p.second.end()));
     138             : 
     139           0 :         m_QueuedCommands.pop_front();
     140           0 :         m_QueuedCommands.resize(m_QueuedCommands.size() + 1);
     141             : 
     142           0 :         m_Replay.Turn(m_CurrentTurn-1, m_TurnLength, commands);
     143             : 
     144             :         NETTURN_LOG("Running %d cmds\n", commands.size());
     145             : 
     146           0 :         m_Simulation2.Update(m_TurnLength, commands);
     147             : 
     148           0 :         NotifyFinishedUpdate(m_CurrentTurn);
     149             : 
     150             :         // Set the time for the next turn update
     151           0 :         m_DeltaSimTime -= m_TurnLength / 1000.f;
     152             :     }
     153             : 
     154           0 :     return true;
     155             : }
     156             : 
     157           0 : bool CTurnManager::UpdateFastForward()
     158             : {
     159           0 :     m_DeltaSimTime = 0;
     160             : 
     161             :     NETTURN_LOG("UpdateFastForward current=%d ready=%d\n", m_CurrentTurn, m_ReadyTurn);
     162             : 
     163             :     // Check that the next turn is ready for execution
     164           0 :     if (m_ReadyTurn <= m_CurrentTurn)
     165           0 :         return false;
     166             : 
     167           0 :     while (m_ReadyTurn > m_CurrentTurn)
     168             :     {
     169             :         // TODO: It would be nice to remove some of the duplication with Update()
     170             :         // (This is similar but doesn't call any Notify functions or update DeltaTime,
     171             :         // it just updates the simulation state)
     172             : 
     173           0 :         ++m_CurrentTurn;
     174             : 
     175           0 :         m_Simulation2.FlushDestroyedEntities();
     176             : 
     177             :         // Put all the client commands into a single list, in a globally consistent order
     178           0 :         std::vector<SimulationCommand> commands;
     179           0 :         for (std::pair<const u32, std::vector<SimulationCommand>>& p : m_QueuedCommands[0])
     180           0 :             commands.insert(commands.end(), std::make_move_iterator(p.second.begin()), std::make_move_iterator(p.second.end()));
     181             : 
     182           0 :         m_QueuedCommands.pop_front();
     183           0 :         m_QueuedCommands.resize(m_QueuedCommands.size() + 1);
     184             : 
     185           0 :         m_Replay.Turn(m_CurrentTurn-1, m_TurnLength, commands);
     186             : 
     187             :         NETTURN_LOG("Running %d cmds\n", commands.size());
     188             : 
     189           0 :         m_Simulation2.Update(m_TurnLength, commands);
     190             :     }
     191             : 
     192           0 :     return true;
     193             : }
     194             : 
     195           0 : void CTurnManager::Interpolate(float simFrameLength, float realFrameLength)
     196             : {
     197             :     // TODO: using m_TurnLength might be a bit dodgy when length changes - maybe
     198             :     // we need to save the previous turn length?
     199             : 
     200           0 :     float offset = Clamp(m_DeltaSimTime / (m_TurnLength / 1000.f) + 1.0, 0.0, 1.0);
     201             : 
     202             :     // Stop animations while still updating the selection highlight
     203           0 :     if (m_CurrentTurn > m_FinalTurn)
     204           0 :         simFrameLength = 0;
     205             : 
     206           0 :     m_Simulation2.Interpolate(simFrameLength, offset, realFrameLength);
     207           0 : }
     208             : 
     209           0 : void CTurnManager::AddCommand(int client, int player, JS::HandleValue data, u32 turn)
     210             : {
     211             :     NETTURN_LOG("AddCommand(client=%d player=%d turn=%d current=%d, ready=%d)\n", client, player, turn, m_CurrentTurn, m_ReadyTurn);
     212             : 
     213             :     // Reject commands for turns that we should not be able to compute (in the past).
     214           0 :     if (m_CurrentTurn >= turn)
     215             :     {
     216             :         // The most likely explanation is that an observer that's lagging behind is sending commands,
     217             :         // which is possible when cheats are enabled. Report & ignore.
     218             :         // It seems a bad idea to error out too badly here:
     219             :         // nefarious clients could try and send broken commands to DOS.
     220           0 :         LOGWARNING("Received command for invalid turn %i (current turn is %i)", turn, m_CurrentTurn);
     221           0 :         return;
     222             :     }
     223             : 
     224           0 :     ScriptRequest rq(m_Simulation2.GetScriptInterface());
     225             : 
     226           0 :     Script::FreezeObject(rq, data, true);
     227             : 
     228           0 :     size_t command_in_turns = turn - (m_CurrentTurn+1);
     229           0 :     if (m_QueuedCommands.size() <= command_in_turns)
     230           0 :         m_QueuedCommands.resize(command_in_turns+1);
     231           0 :     m_QueuedCommands[turn - (m_CurrentTurn+1)][client].emplace_back(player, rq.cx, data);
     232             : }
     233             : 
     234           0 : void CTurnManager::FinishedAllCommands(u32 turn, u32 turnLength)
     235             : {
     236             :     NETTURN_LOG("FinishedAllCommands(%d, %d)\n", turn, turnLength);
     237             : 
     238           0 :     ENSURE(turn == m_ReadyTurn + 1);
     239           0 :     m_ReadyTurn = turn;
     240           0 :     m_TurnLength = turnLength;
     241           0 : }
     242             : 
     243           0 : bool CTurnManager::TurnNeedsFullHash(u32 turn) const
     244             : {
     245             :     // Check immediately for errors caused by e.g. inconsistent game versions
     246             :     // (The hash is computed after the first sim update, so we start at turn == 1)
     247           0 :     if (turn == 1)
     248           0 :         return true;
     249             : 
     250             :     // Otherwise check the full state every ~10 seconds in multiplayer games
     251             :     // (TODO: should probably remove this when we're reasonably sure the game
     252             :     // isn't too buggy, since the full hash is still pretty slow)
     253           0 :     if (turn % 20 == 0)
     254           0 :         return true;
     255             : 
     256           0 :     return false;
     257             : }
     258             : 
     259           0 : void CTurnManager::EnableTimeWarpRecording(size_t numTurns)
     260             : {
     261           0 :     m_TimeWarpStates.clear();
     262           0 :     m_TimeWarpNumTurns = numTurns;
     263           0 : }
     264             : 
     265           0 : void CTurnManager::RewindTimeWarp()
     266             : {
     267           0 :     if (m_TimeWarpStates.empty())
     268           0 :         return;
     269             : 
     270           0 :     std::stringstream stream(m_TimeWarpStates.back());
     271           0 :     m_Simulation2.DeserializeState(stream);
     272           0 :     m_TimeWarpStates.pop_back();
     273             : 
     274             :     // Reset the turn manager state, so we won't execute stray commands and
     275             :     // won't do the next snapshot until the appropriate time.
     276             :     // (Ideally we ought to serialise the turn manager state and restore it
     277             :     // here, but this is simpler for now.)
     278           0 :     ResetState(1, m_CommandDelay);
     279             : }
     280             : 
     281           0 : void CTurnManager::QuickSave(JS::HandleValue GUIMetadata)
     282             : {
     283           0 :     TIMER(L"QuickSave");
     284             : 
     285           0 :     std::stringstream stream;
     286           0 :     if (!m_Simulation2.SerializeState(stream))
     287             :     {
     288           0 :         LOGERROR("Failed to quicksave game");
     289           0 :         return;
     290             :     }
     291             : 
     292           0 :     m_QuickSaveState = stream.str();
     293             : 
     294           0 :     ScriptRequest rq(m_Simulation2.GetScriptInterface());
     295             : 
     296           0 :     m_QuickSaveMetadata.set(Script::DeepCopy(rq, GUIMetadata));
     297             :     // Freeze state to ensure that consectuvie loads don't modify the state
     298           0 :     Script::FreezeObject(rq, m_QuickSaveMetadata, true);
     299             : 
     300           0 :     LOGMESSAGERENDER("Quicksaved game");
     301             : }
     302             : 
     303           0 : void CTurnManager::QuickLoad()
     304             : {
     305           0 :     TIMER(L"QuickLoad");
     306             : 
     307           0 :     if (m_QuickSaveState.empty())
     308             :     {
     309           0 :         LOGERROR("Cannot quickload game - no game was quicksaved");
     310           0 :         return;
     311             :     }
     312             : 
     313           0 :     std::stringstream stream(m_QuickSaveState);
     314           0 :     if (!m_Simulation2.DeserializeState(stream))
     315             :     {
     316           0 :         LOGERROR("Failed to quickload game");
     317           0 :         return;
     318             :     }
     319             : 
     320             :     // See RewindTimeWarp
     321           0 :     ResetState(1, m_CommandDelay);
     322             : 
     323           0 :     if (!g_GUI)
     324           0 :         return;
     325             : 
     326           0 :     ScriptRequest rq(m_Simulation2.GetScriptInterface());
     327             : 
     328             :     // Provide a copy, so that GUI components don't have to clone to get mutable objects
     329           0 :     JS::RootedValue quickSaveMetadataClone(rq.cx, Script::DeepCopy(rq, m_QuickSaveMetadata));
     330             : 
     331           0 :     JS::RootedValueArray<1> paramData(rq.cx);
     332           0 :     paramData[0].set(quickSaveMetadataClone);
     333           0 :     g_GUI->SendEventToAll(EventNameSavegameLoaded, paramData);
     334             : 
     335           0 :     LOGMESSAGERENDER("Quickloaded game");
     336           3 : }

Generated by: LCOV version 1.13