Add fast-forward/rewind commands, to help with testing.
This was SVN commit r8803.
This commit is contained in:
parent
c2f4f56ad6
commit
8ddef2fee0
@ -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
|
||||
|
@ -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)
|
||||
|
@ -98,7 +98,7 @@
|
||||
/>
|
||||
|
||||
<!-- Dev/cheat commands -->
|
||||
<object name="devCommands" size="100%-156 72 100%-16 200" type="image" sprite="devCommandsBackground"
|
||||
<object name="devCommands" size="100%-156 72 100%-16 216" type="image" sprite="devCommandsBackground"
|
||||
hidden="true" hotkey="session.devcommands.toggle">
|
||||
<action on="Press">
|
||||
toggleDeveloperOverlay();
|
||||
@ -139,6 +139,11 @@
|
||||
<object size="100%-16 112 100% 128" type="checkbox" style="wheatCrossBox">
|
||||
<action on="Press">Engine.PostNetworkCommand({"type": "reveal-map", "enable": this.checked});</action>
|
||||
</object>
|
||||
|
||||
<object size="0 128 100%-18 144" type="text" style="devCommandsText">Enable time warp</object>
|
||||
<object size="100%-16 128 100% 144" type="checkbox" name="devTimeWarp" style="wheatCrossBox">
|
||||
<action on="Press">Engine.EnableTimeWarpRecording(this.checked ? 10 : 0);</action>
|
||||
</object>
|
||||
</object>
|
||||
|
||||
<!-- ================================ ================================ -->
|
||||
|
@ -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<void, &DebugWarn>("DebugWarn");
|
||||
scriptInterface.RegisterFunction<void, &ForceGC>("ForceGC");
|
||||
scriptInterface.RegisterFunction<void, &DumpSimState>("DumpSimState");
|
||||
scriptInterface.RegisterFunction<void, unsigned int, &EnableTimeWarpRecording>("EnableTimeWarpRecording");
|
||||
scriptInterface.RegisterFunction<void, &RewindTimeWarp>("RewindTimeWarp");
|
||||
}
|
||||
|
@ -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<SimulationCommand> commands;
|
||||
for (std::map<u32, std::vector<SimulationCommand> >::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)
|
||||
|
@ -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<std::string> m_TimeWarpStates;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
@ -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");
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user