| 1 |
#ifndef REVERSEMANGER_HH |
| 2 |
#define REVERSEMANGER_HH |
| 3 |
|
| 4 |
#include "Command.hh" |
| 5 |
#include "EmuTime.hh" |
| 6 |
#include "EventListener.hh" |
| 7 |
#include "Schedulable.hh" |
| 8 |
#include "StateChange.hh" |
| 9 |
|
| 10 |
#include "DeltaBlock.hh" |
| 11 |
#include "MemBuffer.hh" |
| 12 |
#include "outer.hh" |
| 13 |
|
| 14 |
#include <cstdint> |
| 15 |
#include <deque> |
| 16 |
#include <map> |
| 17 |
#include <memory> |
| 18 |
#include <ranges> |
| 19 |
#include <span> |
| 20 |
#include <string_view> |
| 21 |
#include <vector> |
| 22 |
|
| 23 |
namespace openmsx { |
| 24 |
|
| 25 |
class EventDelay; |
| 26 |
class EventDistributor; |
| 27 |
class Interpreter; |
| 28 |
class MSXMotherBoard; |
| 29 |
class TclObject; |
| 30 |
|
| 31 |
class ReverseManager final : private EventListener |
| 32 |
{ |
| 33 |
public: |
| 34 |
static constexpr std::string_view REPLAY_DIR = "replays"; |
| 35 |
static constexpr std::string_view REPLAY_EXTENSION = ".omr"; |
| 36 |
|
| 37 |
public: |
| 38 |
explicit ReverseManager(MSXMotherBoard& motherBoard); |
| 39 |
~ReverseManager(); |
| 40 |
|
| 41 |
|
| 42 |
|
| 43 |
void registerEventDelay(EventDelay& eventDelay_) { |
| 44 |
eventDelay = &eventDelay_; |
| 45 |
} |
| 46 |
|
| 47 |
|
| 48 |
|
| 49 |
|
| 50 |
void setReRecordCount(unsigned count) { |
| 51 |
reRecordCount = count; |
| 52 |
} |
| 53 |
|
| 54 |
[[nodiscard]] bool isReplaying() const; |
| 55 |
void stopReplay(EmuTime time) noexcept; |
| 56 |
|
| 57 |
template<typename T, typename... Args> |
| 58 |
StateChange& record(EmuTime time, Args&& ...args) { |
| 59 |
assert(!isReplaying()); |
| 60 |
++replayIndex; |
| 61 |
history.events.emplace_back(std::in_place_type_t<T>{}, time, std::forward<Args>(args)...); |
| 62 |
return history.events.back(); |
| 63 |
} |
| 64 |
|
| 65 |
[[nodiscard]] bool isCollecting() const { return collecting; } |
| 66 |
[[nodiscard]] bool isViewOnlyMode() const; |
| 67 |
void setViewOnlyMode(bool value); |
| 68 |
[[nodiscard]] double getBegin() const; |
| 69 |
[[nodiscard]] double getEnd() const; |
| 70 |
[[nodiscard]] double getCurrent() const; |
| 71 |
[[nodiscard]] auto getSnapshotTimes() const { |
| 72 |
return std::views::transform(history.chunks, [](auto& p) { |
| 73 |
return p.second.time.toDouble(); |
| 74 |
}); |
| 75 |
} |
| 76 |
|
| 77 |
private: |
| 78 |
struct ReverseChunk { |
| 79 |
EmuTime time = EmuTime::zero(); |
| 80 |
std::vector<std::shared_ptr<DeltaBlock>> deltaBlocks; |
| 81 |
MemBuffer<uint8_t> savestate; |
| 82 |
|
| 83 |
|
| 84 |
|
| 85 |
|
| 86 |
unsigned eventCount; |
| 87 |
}; |
| 88 |
using Chunks = std::map<unsigned, ReverseChunk>; |
| 89 |
using Events = std::deque<StateChange>; |
| 90 |
|
| 91 |
struct ReverseHistory { |
| 92 |
void swap(ReverseHistory& other) noexcept; |
| 93 |
void clear(); |
| 94 |
[[nodiscard]] unsigned getNextSeqNum(EmuTime time) const; |
| 95 |
|
| 96 |
Chunks chunks; |
| 97 |
Events events; |
| 98 |
LastDeltaBlocks lastDeltaBlocks; |
| 99 |
}; |
| 100 |
|
| 101 |
void start(); |
| 102 |
void stop(); |
| 103 |
void status(TclObject& result) const; |
| 104 |
void debugInfo(TclObject& result) const; |
| 105 |
void goBack(std::span<const TclObject> tokens); |
| 106 |
void goTo(std::span<const TclObject> tokens); |
| 107 |
void saveReplay(Interpreter& interp, |
| 108 |
std::span<const TclObject> tokens, TclObject& result); |
| 109 |
void loadReplay(Interpreter& interp, |
| 110 |
std::span<const TclObject> tokens, TclObject& result); |
| 111 |
|
| 112 |
void signalStopReplay(EmuTime time); |
| 113 |
[[nodiscard]] EmuTime getEndTime(const ReverseHistory& history) const; |
| 114 |
void goTo(EmuTime targetTime, bool noVideo); |
| 115 |
void goTo(EmuTime targetTime, bool noVideo, |
| 116 |
ReverseHistory& history, bool sameTimeLine); |
| 117 |
void transferHistory(ReverseHistory& oldHistory, |
| 118 |
unsigned oldEventCount); |
| 119 |
void transferState(MSXMotherBoard& newBoard); |
| 120 |
void takeSnapshot(EmuTime time); |
| 121 |
void schedule(EmuTime time); |
| 122 |
void replayNextEvent(); |
| 123 |
template<unsigned N> void dropOldSnapshots(unsigned count); |
| 124 |
|
| 125 |
|
| 126 |
struct SyncNewSnapshot final : Schedulable { |
| 127 |
friend class ReverseManager; |
| 128 |
explicit SyncNewSnapshot(Scheduler& s) : Schedulable(s) {} |
| 129 |
void executeUntil(EmuTime ) override { |
| 130 |
auto& rm = OUTER(ReverseManager, syncNewSnapshot); |
| 131 |
rm.execNewSnapshot(); |
| 132 |
} |
| 133 |
} syncNewSnapshot; |
| 134 |
struct SyncInputEvent final : Schedulable { |
| 135 |
friend class ReverseManager; |
| 136 |
explicit SyncInputEvent(Scheduler& s) : Schedulable(s) {} |
| 137 |
void executeUntil(EmuTime ) override { |
| 138 |
auto& rm = OUTER(ReverseManager, syncInputEvent); |
| 139 |
rm.execInputEvent(); |
| 140 |
} |
| 141 |
} syncInputEvent; |
| 142 |
|
| 143 |
void execNewSnapshot(); |
| 144 |
void execInputEvent(); |
| 145 |
[[nodiscard]] EmuTime getCurrentTime() const { return syncNewSnapshot.getCurrentTime(); } |
| 146 |
|
| 147 |
|
| 148 |
bool signalEvent(const Event& event) override; |
| 149 |
|
| 150 |
private: |
| 151 |
MSXMotherBoard& motherBoard; |
| 152 |
EventDistributor& eventDistributor; |
| 153 |
|
| 154 |
struct ReverseCmd final : Command { |
| 155 |
explicit ReverseCmd(CommandController& controller); |
| 156 |
void execute(std::span<const TclObject> tokens, TclObject& result) override; |
| 157 |
[[nodiscard]] std::string help(std::span<const TclObject> tokens) const override; |
| 158 |
void tabCompletion(std::vector<std::string>& tokens) const override; |
| 159 |
} reverseCmd; |
| 160 |
|
| 161 |
EventDelay* eventDelay = nullptr; |
| 162 |
ReverseHistory history; |
| 163 |
unsigned replayIndex = 0; |
| 164 |
bool collecting = false; |
| 165 |
bool pendingTakeSnapshot = false; |
| 166 |
|
| 167 |
unsigned reRecordCount = 0; |
| 168 |
|
| 169 |
friend struct Replay; |
| 170 |
}; |
| 171 |
|
| 172 |
} |
| 173 |
|
| 174 |
#endif |