Replay: deterministic record & playback (#4)
Add deterministic record/playback for a run. Recording captures `(seed, config hash, ordered tick-tagged commands)` and re-simulates on playback — no state snapshots. `DotaFactory.exe --replay <file>` re-plays a recorded run view-only with manual speed/pause. Reviewed-on: #4 Co-authored-by: Malte Langkabel <malte.langkabel@gmail.com> Co-committed-by: Malte Langkabel <malte.langkabel@gmail.com>
This commit was merged in pull request #4.
This commit is contained in:
92
src/lib/sim/CommandManager.cpp
Normal file
92
src/lib/sim/CommandManager.cpp
Normal file
@@ -0,0 +1,92 @@
|
||||
#include "CommandManager.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "Command.h"
|
||||
#include "ReplayRecorder.h"
|
||||
#include "Simulation.h"
|
||||
#include "Tick.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
// Periodic RNG checksum cadence (see docs/replay_design.md "Cadence").
|
||||
constexpr Tick kChecksumIntervalTicks = 30;
|
||||
} // namespace
|
||||
|
||||
CommandManager::CommandManager(Simulation& simulation)
|
||||
: m_simulation(simulation)
|
||||
{
|
||||
}
|
||||
|
||||
CommandManager::~CommandManager() = default;
|
||||
|
||||
void CommandManager::enqueue(std::shared_ptr<const Command> command)
|
||||
{
|
||||
if (m_replayMode)
|
||||
{
|
||||
return; // playback is driven by the recorded stream; ignore live input
|
||||
}
|
||||
if (command)
|
||||
{
|
||||
m_queue.push_back(std::move(command));
|
||||
}
|
||||
}
|
||||
|
||||
void CommandManager::drain()
|
||||
{
|
||||
// Apply in FIFO order. A queued ResetCommand reinitializes the simulation in
|
||||
// place; the reference stays valid and any commands after it apply to the
|
||||
// fresh state.
|
||||
for (const std::shared_ptr<const Command>& command : m_queue)
|
||||
{
|
||||
if (command->kind == CommandKind::Reset)
|
||||
{
|
||||
m_simulation.apply(*command);
|
||||
if (m_recorder)
|
||||
{
|
||||
// Restart is a file boundary: a fresh file with the new seed.
|
||||
m_recorder->startNewRun(m_simulation.getSeed(),
|
||||
m_simulation.rngFingerprint());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Commands drain before the tick batch, so currentTick is the count of
|
||||
// completed ticks the command is pinned to.
|
||||
const Tick tick = m_simulation.currentTick();
|
||||
m_simulation.apply(*command);
|
||||
if (m_recorder)
|
||||
{
|
||||
m_recorder->recordCommand(tick, *command, m_simulation.rngFingerprint());
|
||||
}
|
||||
}
|
||||
}
|
||||
m_queue.clear();
|
||||
}
|
||||
|
||||
bool CommandManager::hasPending() const
|
||||
{
|
||||
return !m_queue.empty();
|
||||
}
|
||||
|
||||
void CommandManager::setRecorder(std::unique_ptr<ReplayRecorder> recorder)
|
||||
{
|
||||
m_recorder = std::move(recorder);
|
||||
if (m_recorder)
|
||||
{
|
||||
m_recorder->startNewRun(m_simulation.getSeed(), m_simulation.rngFingerprint());
|
||||
}
|
||||
}
|
||||
|
||||
void CommandManager::setReplayMode(bool replayMode)
|
||||
{
|
||||
m_replayMode = replayMode;
|
||||
}
|
||||
|
||||
void CommandManager::recordTickCheckpoint()
|
||||
{
|
||||
if (m_recorder && (m_simulation.currentTick() % kChecksumIntervalTicks == 0))
|
||||
{
|
||||
m_recorder->recordChecksum(m_simulation.currentTick(), m_simulation.rngFingerprint());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user