JaffarPlus
High-performance best-first search optimizer for tool-assisted speedruns
Loading...
Searching...
No Matches
inputHistoryTrie.hpp
Go to the documentation of this file.
1#pragma once
2
24#include "inputHistory.hpp"
25#include <cstring>
26#include <jaffarCommon/bitwise.hpp>
27#include <jaffarCommon/exceptions.hpp>
28#include <jaffarCommon/sequenceTrie.hpp>
29#include <vector>
30
31namespace jaffarPlus
32{
33
39using SequenceInputTrie = jaffarCommon::sequenceTrie::SequenceTrie<uint16_t>;
40
43class InputHistoryTrie final : public InputHistory
44{
45public:
46 using nodeId_t = SequenceInputTrie::nodeId_t;
47
54 InputHistoryTrie(SequenceInputTrie* trie, const uint32_t shardId, const uint32_t managerShard, const uint32_t maxInputIndex, const uint32_t maxSize)
55 : _trie(trie), _shardId(shardId), _managerShard(managerShard), _maxSize(maxSize)
56 {
57 // The trie node stores the input index as a 16-bit element (see SequenceInputTrie). maxInputIndex is one
58 // past the highest input index, so it must fit in 16 bits. Real searches register a few hundred inputs
59 // at most; if a game ever exceeds this, use Type "Raw" instead.
60 if (maxInputIndex > 0x10000u) JAFFAR_THROW_RUNTIME("Trie input-history supports at most 65536 distinct inputs (got %u); use Store Input History Type \"Raw\".", maxInputIndex);
61 _bits = jaffarCommon::bitwise::getEncodingBitsForElementCount(maxInputIndex);
62 _bitpackBytes = (_maxSize * _bits + 7) / 8;
63 _scratch.resize(_bitpackBytes, 0);
64 }
65
66 void reset() override
67 {
68 if (_ownNode) _trie->release(_node, _shardId);
69 _node = SequenceInputTrie::ROOT;
70 _ownNode = false;
71 }
72
73 void pushInput(const size_t /*stepCount*/, const InputSet::inputIndex_t input) override
74 {
75 // Append as a new owned node; the prior owned node (e.g. a frameskip sub-frame) is kept alive by the
76 // new node's child edge, so drop this runner's transient reference to it. The trie is unbounded.
77 const nodeId_t next = _trie->extend(_node, (uint16_t)input, _shardId); // input < maxInputIndex <= 65536 (ctor-checked)
78 if (_ownNode) _trie->release(_node, _shardId);
79 _node = next;
80 _ownNode = true;
81 }
82
83 void serializeCold(jaffarCommon::serializer::Base& s) const override
84 {
85 // _node may be NONE if the trie hit its capacity ceiling (extend returned NONE); acquiring NONE would
86 // index out of bounds. Skip it -- releaseColdSlot() already skips NONE too, so the refcount stays
87 // balanced. (The run is stopped gracefully by the driver's capacity guard right after this anyway.)
88 if (_node != SequenceInputTrie::NONE) _trie->acquire(_node); // the destination slot becomes a holder of this path node
89 s.pushContiguous(&_node, sizeof(_node));
90 }
91 void deserializeCold(jaffarCommon::deserializer::Base& d) override
92 {
93 if (_ownNode) _trie->release(_node, _shardId); // drop a just-discarded candidate node, if any
94 d.popContiguous(&_node, sizeof(_node));
95 _ownNode = false; // borrowed from the slot (kept alive until the slot is freed)
96 }
97
98 void serializeFull(jaffarCommon::serializer::Base& s) const override
99 {
100 reconstructIntoBuffer(_node, _scratch.data()); // self-contained bit-packed copy (no trie reference)
101 s.pushContiguous(_scratch.data(), _bitpackBytes);
102 }
103 void deserializeFull(jaffarCommon::deserializer::Base& d, const size_t stepCount) override
104 {
105 d.popContiguous(_scratch.data(), _bitpackBytes);
106 rebuildNodeFromBuffer(_scratch.data(), stepCount); // so a restored runner (e.g. the player) can keep extending
107 }
108
109 std::string toString(const std::map<InputSet::inputIndex_t, std::string>& inputStringMap, const size_t /*stepCount*/) const override
110 {
111 std::vector<InputSet::inputIndex_t> seq;
112 _trie->reconstruct(_node, seq); // the node fully encodes its path, so seq.size() is the true step count
113 std::string out;
114 for (size_t i = 0; i < seq.size() && i < _maxSize; i++)
115 {
116 if (inputStringMap.contains(seq[i]) == false) JAFFAR_THROW_RUNTIME("Move Index %lu not found in runner\n", (unsigned long)seq[i]);
117 out += inputStringMap.at(seq[i]) + std::string("\n");
118 }
119 return out;
120 }
121
122 size_t getColdSize() const override { return sizeof(_node); }
123 size_t getFullSize() const override { return _bitpackBytes; }
124
125 void initColdSlot(void* cold) const override { *reinterpret_cast<nodeId_t*>(cold) = SequenceInputTrie::NONE; }
126
127 size_t getApproxMemoryBytes() const override { return _trie->getApproxMemoryBytes(); }
128
129 void releaseColdSlot(void* cold, const size_t shard) const override
130 {
131 auto* node = reinterpret_cast<nodeId_t*>(cold);
132 if (*node != SequenceInputTrie::NONE)
133 {
134 _trie->release(*node, (uint32_t)shard);
135 *node = SequenceInputTrie::NONE;
136 }
137 }
138
139 void captureColdToFull(const void* cold, void* full) const override
140 {
141 // Cold path = [node id]; full path = [bit-packed history]. Reconstruct the node's path into the full
142 // buffer so the snapshot is self-contained (no later reference into the trie). The runner's step-count
143 // prefix that precedes both is copied separately by the StateDb.
144 nodeId_t node;
145 std::memcpy(&node, cold, sizeof(node));
146 reconstructIntoBuffer(node, reinterpret_cast<uint8_t*>(full), /*pin=*/true);
147 }
148
149private:
153 void reconstructIntoBuffer(nodeId_t node, uint8_t* buffer, bool pin = false) const
154 {
155 std::memset(buffer, 0, _bitpackBytes);
156 if (node == SequenceInputTrie::NONE) return;
157 if (pin) _trie->acquire(node);
158 std::vector<InputSet::inputIndex_t> seq; // reconstruct widens the trie's 16-bit elements into these
159 _trie->reconstruct(node, seq);
160 if (pin) _trie->release(node, _managerShard);
161 for (size_t i = 0; i < seq.size() && i < _maxSize; i++) jaffarCommon::bitwise::bitcopy(buffer, _bitpackBytes, i, &seq[i], sizeof(InputSet::inputIndex_t), 0, 1, _bits);
162 }
163
166 void rebuildNodeFromBuffer(const uint8_t* buffer, const size_t stepCount)
167 {
168 if (_ownNode) _trie->release(_node, _shardId);
169 _node = SequenceInputTrie::ROOT;
170 _ownNode = false;
171 const size_t steps = (stepCount < _maxSize) ? stepCount : _maxSize;
172 for (size_t i = 0; i < steps; i++)
173 {
175 jaffarCommon::bitwise::bitcopy(&idx, sizeof(InputSet::inputIndex_t), 0, buffer, _bitpackBytes, i, 1, _bits);
176 const nodeId_t next = _trie->extend(_node, (uint16_t)idx, _shardId);
177 if (_ownNode) _trie->release(_node, _shardId);
178 _node = next;
179 _ownNode = true;
180 }
181 }
182
184 const uint32_t _shardId;
185 const uint32_t _managerShard;
186 const uint32_t _maxSize;
187 size_t _bits = 0;
188 size_t _bitpackBytes = 0;
189 nodeId_t _node = SequenceInputTrie::ROOT;
190 bool _ownNode = false;
191 mutable std::vector<uint8_t> _scratch;
192};
193
194} // namespace jaffarPlus
Stores each state's path as one node id into a shared, reference-counted prefix trie; sibling states ...
void serializeFull(jaffarCommon::serializer::Base &s) const override
Writes the self-contained "full" path representation (for standalone snapshot buffers),...
const uint32_t _shardId
This runner's free-list shard.
InputHistoryTrie(SequenceInputTrie *trie, const uint32_t shardId, const uint32_t managerShard, const uint32_t maxInputIndex, const uint32_t maxSize)
Binds this instance to the shared trie and sizes the bit-packed snapshot (full) form.
void deserializeFull(jaffarCommon::deserializer::Base &d, const size_t stepCount) override
Restores the cursor from a "full" representation.
void initColdSlot(void *cold) const override
Prepares a fresh/recycled cold slot (e.g. marks it as holding no trie node). Default: no-op.
size_t _bits
Bits per input index in the snapshot.
SequenceInputTrie *const _trie
The shared trie backing this instance.
void deserializeCold(jaffarCommon::deserializer::Base &d) override
Restores the cursor from a "cold" representation (the count is restored by the runner).
size_t getApproxMemoryBytes() const override
Approximate resident memory of any shared structure (e.g. the trie), in bytes. Default: 0.
void reconstructIntoBuffer(nodeId_t node, uint8_t *buffer, bool pin=false) const
Reconstructs node's path into a bit-packed buffer.
SequenceInputTrie::nodeId_t nodeId_t
Trie node identifier type.
std::string toString(const std::map< InputSet::inputIndex_t, std::string > &inputStringMap, const size_t) const override
Reconstructs the path as a newline-separated string of input strings (the solution).
size_t getFullSize() const override
Size, in bytes, of the full (self-contained) path representation in a snapshot buffer (EXCLUDING the ...
const uint32_t _managerShard
Shard for off-thread manager ops.
void rebuildNodeFromBuffer(const uint8_t *buffer, const size_t stepCount)
Rebuilds the cursor's owned trie node by re-extending the stepCount path elements stored in a bit-pac...
void pushInput(const size_t, const InputSet::inputIndex_t input) override
Records one applied input at path position stepCount (the runner's current step counter).
const uint32_t _maxSize
Max steps in the snapshot (full) form.
size_t _bitpackBytes
Size of the bit-packed snapshot buffer.
bool _ownNode
Whether this cursor holds a reference to _node.
nodeId_t _node
Cursor's current trie node.
std::vector< uint8_t > _scratch
Bit-pack scratch for the full (snapshot) form.
void releaseColdSlot(void *cold, const size_t shard) const override
Releases any shared resource a freed cold slot was holding (trie GC).
void captureColdToFull(const void *cold, void *full) const override
Converts a stored cold path into a self-contained full one (best/worst snapshot).
void serializeCold(jaffarCommon::serializer::Base &s) const override
Writes the compact "cold" path representation (stored in each StateDb slot), excluding the count.
size_t getColdSize() const override
Size, in bytes, of the cold path representation in a StateDb slot (EXCLUDING the runner's count).
void reset() override
Resets the cursor to the empty path.
Abstract strategy for remembering the input path that produced each search state.
size_t inputIndex_t
Type used to index an input.
Definition inputSet.hpp:29
jaffarCommon::sequenceTrie::SequenceTrie< uint16_t > SequenceInputTrie
The shared trie type backing every InputHistoryTrie instance of one search.
Abstract interface for how a search remembers the sequence of inputs ("path") that produced each stat...