1
1
forked from 0ad/0ad

Add fast-forward/rewind commands, to help with testing.

This was SVN commit r8803.
This commit is contained in:
Ykkrosh 2010-12-06 19:58:06 +00:00
parent c2f4f56ad6
commit 8ddef2fee0
8 changed files with 128 additions and 27 deletions

View File

@ -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

View File

@ -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)

View File

@ -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>
<!-- ================================ ================================ -->

View File

@ -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");
}

View File

@ -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)

View File

@ -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;
};
/**

View File

@ -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);
}
};

View File

@ -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");
}