JaffarPlus
High-performance best-first search optimizer for tool-assisted speedruns
Loading...
Searching...
No Matches
playback.hpp
Go to the documentation of this file.
1#pragma once
2
9#include "jaffarCommon/deserializers/contiguous.hpp"
10#include "jaffarCommon/hash.hpp"
11#include "jaffarCommon/serializers/contiguous.hpp"
12#include "runner.hpp"
13#include <algorithm>
14#include <string>
15#include <unordered_map>
16#include <vector>
17
18namespace jaffarPlus
19{
20
29class Playback final
30{
31public:
35 struct step_t
36 {
38 std::string inputString;
39
42
45
48
51
53 jaffarCommon::hash::hash_t stateHash;
54
56 std::vector<size_t> _repeatedHashSteps;
57 };
58
63 Playback(Runner& runner) : _runner(&runner)
64 {
65 // Getting game state size
67
68 // Getting renderer state size
70 };
71
81 void initialize(const std::vector<std::string>& inputSequence)
82 {
83 // For each input in the sequence, store the game's state
84 for (size_t i = 0; i <= inputSequence.size(); i++)
85 {
86 // Creating new step
87 step_t step;
88
89 // Checking if this is the end of the sequence
90 bool isEndOfSequence = i == inputSequence.size();
91
92 // Setting step input string
93 step.inputString = isEndOfSequence == false ? inputSequence[i] : "<End Of Sequence>";
94
95 // Checking if the input is allowed
96 bool isRegisteredInput = _runner->isInputRegistered(step.inputString);
97
98 // Getting input index
99 if (isEndOfSequence == true) step.inputIndex = 0;
100 if (isEndOfSequence == false && isRegisteredInput == true) step.inputIndex = _runner->getInputIndex(step.inputString);
101 if (isEndOfSequence == false && isRegisteredInput == false) step.inputIndex = _runner->registerInput(step.inputString);
102
103 // Checking if the input is allowed
104 step.isInputAllowed = false;
105 if (isRegisteredInput == true)
106 {
107 auto allowedInputs = _runner->getAllowedInputs();
108 step.isInputAllowed = std::find(allowedInputs.begin(), allowedInputs.end(), step.inputIndex) != allowedInputs.end();
109 }
110
111 // Getting state hash
112 step.stateHash = _runner->computeHash();
113
114 // Recording duplicate states: any earlier steps already filed under this step's hash are
115 // exactly the repeated states the engine would have pruned on encountering them. Looking them
116 // up in a hash map is O(1) amortized, versus the previous O(n^2) scan over every prior step,
117 // so this scales to long movies. The earlier steps are stored in ascending order, matching the
118 // previous behaviour.
119 auto& sameHashSteps = _hashOccurrences[step.stateHash];
120 step._repeatedHashSteps = sameHashSteps;
121 sameHashSteps.push_back(i);
122
123 // Allocating space for the game state data
124 step.gameStateData = malloc(_gameStateSize);
125
126 // Serializing game state
127 jaffarCommon::serializer::Contiguous sg(step.gameStateData, _gameStateSize);
129
130 // Allocating space for the renderer state data
132
133 // Updating renderer state
135
136 // Serializing renderer state
137 jaffarCommon::serializer::Contiguous sr(step.rendererStateData, _rendererStateSize);
139
140 // Advancing state
141 if (i < inputSequence.size()) _runner->advanceState(step.inputIndex);
142 if (i == inputSequence.size()) _runner->advanceState(_sequence.rbegin()->inputIndex);
143
144 // Evaluate game rules
146
147 // Determining new game state type
149
150 // Recording the first step at which the solution reaches a win/fail state. Only real applied
151 // inputs are considered (i < size); the i == size iteration re-applies the last input as a
152 // sentinel. The count is "inputs applied" (i + 1), matching the player's step convention.
153 if (i < inputSequence.size())
154 {
155 const auto stateType = _runner->getGame()->getStateType();
156 if (stateType == Game::stateType_t::win && _firstWinStep < 0) _firstWinStep = (ssize_t)i + 1;
157 if (stateType == Game::stateType_t::fail && _firstFailStep < 0) _firstFailStep = (ssize_t)i + 1;
158 }
159
160 // Updating game reward
162
163 // Adding step to the internal storage
164 _sequence.push_back(step);
165 }
166 }
167
172 {
173 // Freeing up memory reserved during initialization
174 for (const auto& step : _sequence)
175 {
176 free(step.gameStateData);
177 free(step.rendererStateData);
178 }
179 }
180
182 __INLINE__ std::string getStateInputString(const size_t currentStep) const { return getStep(currentStep).inputString; }
184 __INLINE__ jaffarPlus::InputSet::inputIndex_t getStateInputIndex(const size_t currentStep) const { return getStep(currentStep).inputIndex; }
186 __INLINE__ void* getStateData(const size_t currentStep) const { return getStep(currentStep).gameStateData; }
188 __INLINE__ const std::vector<size_t> getStateRepeatedHashSteps(const size_t currentStep) const { return getStep(currentStep)._repeatedHashSteps; }
190 __INLINE__ jaffarCommon::hash::hash_t getStateHash(const size_t currentStep) const { return getStep(currentStep).stateHash; }
192 __INLINE__ bool isInputAllowed(const size_t currentStep) const { return getStep(currentStep).isInputAllowed; }
193
199 __INLINE__ ssize_t getFirstWinStep() const { return _firstWinStep; }
205 __INLINE__ ssize_t getFirstFailStep() const { return _firstFailStep; }
206
211 __INLINE__ void renderFrame(const size_t currentStep)
212 {
213 const auto& step = getStep(currentStep);
214 jaffarCommon::deserializer::Contiguous d(step.rendererStateData, _rendererStateSize);
217 }
218
223 void loadStepData(const size_t stepId)
224 {
225 // Deserializing appropriate state
226 jaffarCommon::deserializer::Contiguous d(getStateData(stepId), _gameStateSize);
228 }
229
233 void printInfo() const
234 {
235 // Now printing information
236 jaffarCommon::logger::log("[J+] Runner Information: \n");
238 jaffarCommon::logger::log("[J+] Game Information: \n");
240 jaffarCommon::logger::log("[J+] Emulator Information: \n");
242 }
243
244private:
251 step_t getStep(const size_t stepId) const
252 {
253 if (stepId >= _sequence.size()) JAFFAR_THROW_RUNTIME("Requested step %lu which exceeds sequence size %lu", stepId, _sequence.size());
254 return _sequence.at(stepId);
255 }
256
262 {
268 size_t operator()(const jaffarCommon::hash::hash_t& h) const noexcept { return h.first ^ (h.second + 0x9E3779B97F4A7C15ULL + (h.first << 6) + (h.first >> 2)); }
269 };
270
273
276
279
281 std::vector<step_t> _sequence;
282
284 std::unordered_map<jaffarCommon::hash::hash_t, std::vector<size_t>, hashHasher_t> _hashOccurrences;
285
286 ssize_t _firstWinStep = -1;
287 ssize_t _firstFailStep = -1;
288};
289
290} // namespace jaffarPlus
virtual size_t getRendererStateSize() const =0
Returns the size of the renderer state.
virtual void printInfo() const =0
Prints core-specific debug information.
virtual void deserializeRendererState(jaffarCommon::deserializer::Base &deserializer)=0
Loads the renderer state for a given state/frame from the deserializer.
virtual void showRender()=0
Shows the contents of the emulator's renderer in the window.
virtual void serializeRendererState(jaffarCommon::serializer::Base &serializer) const =0
Gathers the data needed to render a given state/frame into the serializer.
virtual void updateRendererState(const size_t stepIdx, const std::string input)=0
Updates the internal state of the renderer with the current game state.
Emulator * getEmulator() const
Returns a pointer to the internal emulator.
Definition game.hpp:570
void updateReward()
Recomputes the current state's reward from the satisfied rules.
Definition game.hpp:447
void evaluateRules()
Evaluates the rule set against the current state.
Definition game.hpp:351
void printInfo() const
Prints the current game state to the logger.
Definition game.hpp:288
void updateGameStateType()
Recomputes the state type and checkpoint level from the satisfied rules.
Definition game.hpp:405
stateType_t getStateType() const
Returns the current state type (normal, win or fail).
Definition game.hpp:601
@ fail
A fail rule is currently satisfied.
Definition game.hpp:46
@ win
A win rule is currently satisfied.
Definition game.hpp:45
size_t inputIndex_t
Type used to index an input.
Definition inputSet.hpp:29
Replays a solution's input sequence and caches per-step state for navigation.
Definition playback.hpp:30
ssize_t getFirstWinStep() const
Returns the first step (number of inputs applied) at which the solution reaches a win state,...
Definition playback.hpp:199
Playback(Runner &runner)
Constructs the playback over a runner and caches state sizes.
Definition playback.hpp:63
std::vector< step_t > _sequence
The recorded sequence of playback steps.
Definition playback.hpp:281
step_t getStep(const size_t stepId) const
Returns the cached step with the given id.
Definition playback.hpp:251
void printInfo() const
Prints runner, game, and emulator information.
Definition playback.hpp:233
void initialize(const std::vector< std::string > &inputSequence)
Replays the input sequence, recording one cached step per input (plus a trailing end-of-sequence step...
Definition playback.hpp:81
~Playback()
Frees the game and renderer state memory allocated during initialization.
Definition playback.hpp:171
bool isInputAllowed(const size_t currentStep) const
Returns whether the input of the given step is allowed by the current move set.
Definition playback.hpp:192
ssize_t getFirstFailStep() const
Returns the first step (number of inputs applied) at which the solution reaches a fail state,...
Definition playback.hpp:205
size_t _gameStateSize
Size, in bytes, of a serialized game state.
Definition playback.hpp:275
size_t _rendererStateSize
Size, in bytes, of a serialized renderer state.
Definition playback.hpp:278
jaffarPlus::InputSet::inputIndex_t getStateInputIndex(const size_t currentStep) const
Returns the input index of the given step.
Definition playback.hpp:184
Runner * _runner
Pointer to the runner used for playback.
Definition playback.hpp:272
std::string getStateInputString(const size_t currentStep) const
Returns the input string of the given step.
Definition playback.hpp:182
std::unordered_map< jaffarCommon::hash::hash_t, std::vector< size_t >, hashHasher_t > _hashOccurrences
Maps each state hash to the steps (ascending) at which it occurred, used to detect the repeated state...
Definition playback.hpp:284
jaffarCommon::hash::hash_t getStateHash(const size_t currentStep) const
Returns the state hash of the given step.
Definition playback.hpp:190
ssize_t _firstFailStep
First step (inputs applied) reaching a fail state; -1 until/unless one is seen.
Definition playback.hpp:287
void * getStateData(const size_t currentStep) const
Returns the serialized game state data of the given step.
Definition playback.hpp:186
const std::vector< size_t > getStateRepeatedHashSteps(const size_t currentStep) const
Returns the earlier steps (ascending) sharing the given step's hash.
Definition playback.hpp:188
void loadStepData(const size_t stepId)
Loads the cached game state of the given step back into the runner.
Definition playback.hpp:223
ssize_t _firstWinStep
First step (inputs applied) reaching a win state; -1 until/unless one is seen.
Definition playback.hpp:286
void renderFrame(const size_t currentStep)
Renders the cached frame for the given step into the emulator window.
Definition playback.hpp:211
Owns a Game instance and advances it according to configured inputs.
Definition runner.hpp:38
InputSet::inputIndex_t registerInput(const std::string &input)
Registers an input string and returns its numeric index.
Definition runner.hpp:192
const auto getAllowedInputs() const
Returns the inputs currently allowed for the game's state.
Definition runner.hpp:251
size_t getStateSize() const
Computes the size in bytes of the serialized runner state.
Definition runner.hpp:384
void printInfo() const
Logs runner state information.
Definition runner.hpp:472
jaffarPlus::InputSet::inputIndex_t getInputIndex(const std::string &input) const
Looks up the index registered for an input string.
Definition runner.hpp:274
void advanceState(const InputSet::inputIndex_t inputIdx)
Advances the game by one input, then by the configured number of frameskip frames.
Definition runner.hpp:309
jaffarCommon::hash::hash_t computeHash() const
Computes a hash of the current runner state.
Definition runner.hpp:426
Game * getGame() const
Returns a pointer to the owned game instance.
Definition runner.hpp:516
void serializeState(jaffarCommon::serializer::Base &serializer) const
Serializes the runner state: the game state, the input history, and the input counter.
Definition runner.hpp:360
bool isInputRegistered(const std::string &inputString)
Reports whether an input string has been registered.
Definition runner.hpp:292
void deserializeState(jaffarCommon::deserializer::Base &deserializer)
Restores the runner state: the game state, the input history, and the input counter.
Definition runner.hpp:372
Drives a Game forward one input at a time, managing the allowed/candidate input sets,...
Hash functor for 128-bit state hashes (std::pair<uint64_t, uint64_t>).
Definition playback.hpp:262
size_t operator()(const jaffarCommon::hash::hash_t &h) const noexcept
Combines the two 64-bit halves of a state hash into a size_t.
Definition playback.hpp:268
A single recorded playback step.
Definition playback.hpp:36
void * rendererStateData
The step's serialized renderer state data.
Definition playback.hpp:50
bool isInputAllowed
Whether the move is allowed by the current move set.
Definition playback.hpp:44
jaffarCommon::hash::hash_t stateHash
The step's state hash.
Definition playback.hpp:53
void * gameStateData
The step's serialized game state data.
Definition playback.hpp:47
jaffarPlus::InputSet::inputIndex_t inputIndex
The step's input index.
Definition playback.hpp:41
std::vector< size_t > _repeatedHashSteps
Earlier steps (ascending) that shared this step's hash.
Definition playback.hpp:56
std::string inputString
The step's input string.
Definition playback.hpp:38