#include "CommandManager.h" #include #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 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& 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.getRngFingerprint()); } } 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.getCurrentTick(); m_simulation.apply(*command); if (m_recorder) { m_recorder->recordCommand(tick, *command, m_simulation.getRngFingerprint()); } } } m_queue.clear(); } bool CommandManager::hasPending() const { return !m_queue.empty(); } void CommandManager::setRecorder(std::unique_ptr recorder) { m_recorder = std::move(recorder); if (m_recorder) { m_recorder->startNewRun(m_simulation.getSeed(), m_simulation.getRngFingerprint()); } } void CommandManager::setReplayMode(bool replayMode) { m_replayMode = replayMode; } void CommandManager::recordTickCheckpoint() { if (m_recorder && (m_simulation.getCurrentTick() % kChecksumIntervalTicks == 0)) { m_recorder->recordChecksum(m_simulation.getCurrentTick(), m_simulation.getRngFingerprint()); } }