From 8ddef2fee0edbedaf1893fb125defa9d64f23f2c Mon Sep 17 00:00:00 2001 From: Ykkrosh Date: Mon, 6 Dec 2010 19:58:06 +0000 Subject: [PATCH] Add fast-forward/rewind commands, to help with testing. This was SVN commit r8803. --- binaries/data/config/default.cfg | 2 + .../data/mods/public/gui/session/input.js | 11 +++ .../data/mods/public/gui/session/session.xml | 7 +- source/gui/scripting/ScriptFunctions.cpp | 12 +++ source/network/NetTurnManager.cpp | 82 +++++++++++++++---- source/network/NetTurnManager.h | 21 ++++- source/network/tests/test_Net.h | 12 +-- source/ps/Game.cpp | 8 +- 8 files changed, 128 insertions(+), 27 deletions(-) diff --git a/binaries/data/config/default.cfg b/binaries/data/config/default.cfg index f1e91b94f1..0c3f91e106 100644 --- a/binaries/data/config/default.cfg +++ b/binaries/data/config/default.cfg @@ -157,6 +157,8 @@ hotkey.session.batchtrain = Shift ; Modifier to train units in batches hotkey.session.deselectgroup = Ctrl ; Modifier to deselect units when clicking group icon, instead of selecting hotkey.session.rotate.cw = RightBracket ; Rotate building placement preview clockwise hotkey.session.rotate.ccw = LeftBracket ; Rotate building placement preview anticlockwise +hotkey.timewarp.fastforward = Space +hotkey.timewarp.rewind = Backspace ; > OVERLAY KEYS hotkey.fps.toggle = "Shift+F" ; Toggle frame counter diff --git a/binaries/data/mods/public/gui/session/input.js b/binaries/data/mods/public/gui/session/input.js index 1ada8d6599..8c492acbc8 100644 --- a/binaries/data/mods/public/gui/session/input.js +++ b/binaries/data/mods/public/gui/session/input.js @@ -577,6 +577,17 @@ function handleInputBeforeGui(ev, hoveredObject) function handleInputAfterGui(ev) { + // Handle the time-warp testing features, restricted to single-player + if (!g_IsNetworked && getGUIObjectByName("devTimeWarp").checked) + { + if (ev.type == "hotkeydown" && ev.hotkey == "timewarp.fastforward") + Engine.SetSimRate(20.0); + else if (ev.type == "hotkeyup" && ev.hotkey == "timewarp.fastforward") + Engine.SetSimRate(1.0); + else if (ev.type == "hotkeyup" && ev.hotkey == "timewarp.rewind") + Engine.RewindTimeWarp(); + } + // State-machine processing: switch (inputState) diff --git a/binaries/data/mods/public/gui/session/session.xml b/binaries/data/mods/public/gui/session/session.xml index 8c5da9470c..39018cdee5 100644 --- a/binaries/data/mods/public/gui/session/session.xml +++ b/binaries/data/mods/public/gui/session/session.xml @@ -98,7 +98,7 @@ /> - diff --git a/source/gui/scripting/ScriptFunctions.cpp b/source/gui/scripting/ScriptFunctions.cpp index d03c039152..e5f69c4acf 100644 --- a/source/gui/scripting/ScriptFunctions.cpp +++ b/source/gui/scripting/ScriptFunctions.cpp @@ -386,6 +386,16 @@ void DumpSimState(void* UNUSED(cbdata)) g_Game->GetSimulation2()->DumpDebugState(file); } +void EnableTimeWarpRecording(void* UNUSED(cbdata), unsigned int numTurns) +{ + g_Game->GetTurnManager()->EnableTimeWarpRecording(numTurns); +} + +void RewindTimeWarp(void* UNUSED(cbdata)) +{ + g_Game->GetTurnManager()->RewindTimeWarp(); +} + } // namespace void GuiScriptingInit(ScriptInterface& scriptInterface) @@ -438,4 +448,6 @@ void GuiScriptingInit(ScriptInterface& scriptInterface) scriptInterface.RegisterFunction("DebugWarn"); scriptInterface.RegisterFunction("ForceGC"); scriptInterface.RegisterFunction("DumpSimState"); + scriptInterface.RegisterFunction("EnableTimeWarpRecording"); + scriptInterface.RegisterFunction("RewindTimeWarp"); } diff --git a/source/network/NetTurnManager.cpp b/source/network/NetTurnManager.cpp index 4013647dfa..065c7e4c80 100644 --- a/source/network/NetTurnManager.cpp +++ b/source/network/NetTurnManager.cpp @@ -73,7 +73,7 @@ void CNetTurnManager::SetPlayerID(int playerId) m_PlayerId = playerId; } -bool CNetTurnManager::Update(float frameLength) +bool CNetTurnManager::Update(float frameLength, size_t maxTurns) { m_DeltaTime += frameLength; @@ -84,12 +84,46 @@ bool CNetTurnManager::Update(float frameLength) NETTURN_LOG((L"Update current=%d ready=%d\n", m_CurrentTurn, m_ReadyTurn)); // Check that the next turn is ready for execution - if (m_ReadyTurn > m_CurrentTurn) + if (m_ReadyTurn <= m_CurrentTurn) { + // Oops, we wanted to start the next turn but it's not ready yet - + // there must be too much network lag. + // TODO: complain to the user. + // TODO: send feedback to the server to increase the turn length. + + // Reset the next-turn timer to 0 so we try again next update but + // so we don't rush to catch up in subsequent turns. + // TODO: we should do clever rate adjustment instead of just pausing like this. + m_DeltaTime = 0; + + return false; + } + + maxTurns = std::max((size_t)1, maxTurns); // always do at least one turn + + for (size_t i = 0; i < maxTurns; ++i) + { + // Check that we've reached the i'th next turn + if (m_DeltaTime < 0) + break; + + // Check that the i'th next turn is still ready + if (m_ReadyTurn <= m_CurrentTurn) + break; + NotifyFinishedOwnCommands(m_CurrentTurn + COMMAND_DELAY); m_CurrentTurn += 1; // increase the turn number now, so Update can send new commands for a subsequent turn + // Save the current state for rewinding, if enabled + if (m_TimeWarpNumTurns && (m_CurrentTurn % m_TimeWarpNumTurns) == 0) + { + PROFILE("time warp serialization"); + std::stringstream stream; + m_Simulation2.SerializeState(stream); + m_TimeWarpStates.push_back(stream.str()); + } + // Put all the client commands into a single list, in a globally consistent order std::vector commands; for (std::map >::iterator it = m_QueuedCommands[0].begin(); it != m_QueuedCommands[0].end(); ++it) @@ -109,23 +143,9 @@ bool CNetTurnManager::Update(float frameLength) // Set the time for the next turn update m_DeltaTime -= m_TurnLength / 1000.f; - - return true; } - else - { - // Oops, we wanted to start the next turn but it's not ready yet - - // there must be too much network lag. - // TODO: complain to the user. - // TODO: send feedback to the server to increase the turn length. - // Reset the next-turn timer to 0 so we try again next update but - // so we don't rush to catch up in subsequent turns. - // TODO: we should do clever rate adjustment instead of just pausing like this. - m_DeltaTime = 0; - - return false; - } + return true; } void CNetTurnManager::OnSyncError(u32 turn, const std::string& expectedHash) @@ -187,6 +207,34 @@ void CNetTurnManager::FinishedAllCommands(u32 turn, u32 turnLength) m_TurnLength = turnLength; } +void CNetTurnManager::EnableTimeWarpRecording(size_t numTurns) +{ + m_TimeWarpStates.clear(); + m_TimeWarpNumTurns = numTurns; +} + +void CNetTurnManager::RewindTimeWarp() +{ + if (m_TimeWarpStates.empty()) + return; + + std::stringstream stream(m_TimeWarpStates.back()); + m_Simulation2.DeserializeState(stream); + m_TimeWarpStates.pop_back(); + + // Reset the turn manager state, so we won't execute stray commands and + // won't do the next snapshot until the appropriate time. + // (Ideally we ought to serialise the turn manager state and restore it + // here, but this is simpler for now.) + m_CurrentTurn = 0; + m_ReadyTurn = 1; + m_DeltaTime = 0; + size_t queuedCommandsSize = m_QueuedCommands.size(); + m_QueuedCommands.clear(); + m_QueuedCommands.resize(queuedCommandsSize); +} + + CNetClientTurnManager::CNetClientTurnManager(CSimulation2& simulation, CNetClient& client, int clientId, IReplayLogger& replay) : CNetTurnManager(simulation, DEFAULT_TURN_LENGTH_MP, clientId, replay), m_NetClient(client) diff --git a/source/network/NetTurnManager.h b/source/network/NetTurnManager.h index 153d0cf95b..a4a4866bff 100644 --- a/source/network/NetTurnManager.h +++ b/source/network/NetTurnManager.h @@ -66,11 +66,12 @@ public: /** * Advance the simulation by a certain time. If this brings us past the current - * turn length, the next turn is processed and the function returns true. + * turn length, the next turns are processed and the function returns true. * Otherwise, nothing happens and it returns false. * @param frameLength length of the previous frame in seconds + * @param maxTurns maximum number of turns to simulate at once */ - bool Update(float frameLength); + bool Update(float frameLength, size_t maxTurns); /** * Advance the graphics by a certain time. @@ -99,6 +100,18 @@ public: */ void FinishedAllCommands(u32 turn, u32 turnLength); + /** + * Enables the recording of state snapshots every @p numTurns, + * which can be jumped back to via RewindTimeWarp(). + * If @p numTurns is 0 then recording is disabled. + */ + void EnableTimeWarpRecording(size_t numTurns); + + /** + * Jumps back to the latest recorded state snapshot (if any). + */ + void RewindTimeWarp(); + protected: /** * Store a command to be executed at a given turn. @@ -138,6 +151,10 @@ protected: bool m_HasSyncError; IReplayLogger& m_Replay; + +private: + size_t m_TimeWarpNumTurns; // 0 if disabled + std::list m_TimeWarpStates; }; /** diff --git a/source/network/tests/test_Net.h b/source/network/tests/test_Net.h index 8758f2b576..9090bb8d0c 100644 --- a/source/network/tests/test_Net.h +++ b/source/network/tests/test_Net.h @@ -186,13 +186,13 @@ public: } wait(clients, 100); - client1Game.GetTurnManager()->Update(1.0f); - client2Game.GetTurnManager()->Update(1.0f); - client3Game.GetTurnManager()->Update(1.0f); + client1Game.GetTurnManager()->Update(1.0f, 1); + client2Game.GetTurnManager()->Update(1.0f, 1); + client3Game.GetTurnManager()->Update(1.0f, 1); wait(clients, 100); - client1Game.GetTurnManager()->Update(1.0f); - client2Game.GetTurnManager()->Update(1.0f); - client3Game.GetTurnManager()->Update(1.0f); + client1Game.GetTurnManager()->Update(1.0f, 1); + client2Game.GetTurnManager()->Update(1.0f, 1); + client3Game.GetTurnManager()->Update(1.0f, 1); wait(clients, 100); } }; diff --git a/source/ps/Game.cpp b/source/ps/Game.cpp index 43cdbade47..b7b3f2f354 100644 --- a/source/ps/Game.cpp +++ b/source/ps/Game.cpp @@ -229,8 +229,14 @@ bool CGame::Update(double deltaTime, bool doInterpolate) bool ok = true; if (deltaTime) { + // At the normal sim rate, we currently want to render at least one + // frame per simulation turn, so let maxTurns be 1. But for fast-forward + // sim rates we want to allow more, so it's not bounded by framerate, + // so just use the sim rate itself as the number of turns per frame. + size_t maxTurns = (size_t)m_SimRate; + PROFILE("update"); - if (m_TurnManager->Update(deltaTime)) + if (m_TurnManager->Update(deltaTime, maxTurns)) g_GUI->SendEventToAll("SimulationUpdate"); }