44 static constexpr std::size_t default_max_entries = 512;
45 static constexpr std::size_t default_max_path_nodes = std::size_t{1} << 20u;
50 void set_caps(std::size_t max_entries, std::size_t max_path_nodes)
noexcept {
51 max_entries_ = max_entries;
52 max_path_nodes_ = max_path_nodes;
57 if (entries_.size() > max_entries_ || paths_.size() > max_path_nodes_) {
63 void reserve_routes(std::size_t route_count) {
64 entries_.reserve(route_count);
67 void reserve_path_nodes(std::size_t node_count) {
68 paths_.reserve(node_count);
71 void clear()
noexcept {
77 cap_invalidations_ = 0;
86 void bind_class(std::uintptr_t identity)
noexcept {
87 if (bound_class_ == identity) {
90 if (bound_class_ != 0) {
94 bound_class_ = identity;
97 void invalidate()
noexcept {
100 exact_slots_.clear();
101 suffix_slots_.clear();
105 void reset_stats()
noexcept {
109 cap_invalidations_ = 0;
110 oversized_skips_ = 0;
119 template <
typename World>
120 void capture_world_versions(
const World& world)
noexcept {
121 world_fingerprint_ = world_version_fingerprint(world);
122 has_world_fingerprint_ =
true;
125 template <
typename World>
126 [[nodiscard]]
auto invalidate_if_world_changed(
const World& world)
noexcept
128 if (!has_world_fingerprint_) {
129 capture_world_versions(world);
132 const auto current = world_version_fingerprint(world);
133 if (current == world_fingerprint_) {
137 world_fingerprint_ = current;
138 has_world_fingerprint_ =
true;
144 entries_.size(), hits_, suffix_hits_,
145 misses_, paths_.size(), cap_invalidations_,
146 oversized_skips_, class_rebinds_,
154 PathStatus status = PathStatus::NoPath;
155 std::uint32_t cost = 0;
156 std::size_t expanded_nodes = 0;
157 std::size_t reached_nodes = 0;
158 std::size_t path_offset = 0;
159 std::size_t path_size = 0;
163 std::uint32_t entry_plus_one = 0;
164 std::uint32_t offset = 0;
167 template <
typename World,
typename Tag>
174 [[nodiscard]]
static auto hash_pair(
Coord3 first,
Coord3 second)
noexcept
176 auto hash = std::uint64_t{0xcbf29ce484222325ull};
177 hash = (hash ^
static_cast<std::uint64_t
>(first.x)) * 0x100000001b3ull;
178 hash = (hash ^
static_cast<std::uint64_t
>(first.y)) * 0x100000001b3ull;
179 hash = (hash ^
static_cast<std::uint64_t
>(first.z)) * 0x100000001b3ull;
180 hash = (hash ^
static_cast<std::uint64_t
>(second.x)) * 0x100000001b3ull;
181 hash = (hash ^
static_cast<std::uint64_t
>(second.y)) * 0x100000001b3ull;
182 hash = (hash ^
static_cast<std::uint64_t
>(second.z)) * 0x100000001b3ull;
183 hash = (hash ^ (hash >> 30u)) * 0xbf58476d1ce4e5b9ull;
184 hash = (hash ^ (hash >> 27u)) * 0x94d049bb133111ebull;
185 return hash ^ (hash >> 31u);
188 [[nodiscard]]
auto find(
PathRequest request)
const noexcept ->
const Entry* {
189 if (exact_slots_.empty()) {
192 const auto mask = exact_slots_.size() - 1u;
194 static_cast<std::size_t
>(hash_pair(request.start, request.goal)) & mask;
195 while (exact_slots_[slot] != 0) {
196 const auto& entry = entries_[exact_slots_[slot] - 1u];
197 if (entry.start == request.start && entry.goal == request.goal) {
200 slot = (slot + 1u) & mask;
205 [[nodiscard]]
auto find_suffix(
PathRequest request,
206 std::size_t& suffix_offset)
const noexcept
208 if (suffix_slots_.empty()) {
211 const auto mask = suffix_slots_.size() - 1u;
213 static_cast<std::size_t
>(hash_pair(request.start, request.goal)) & mask;
214 while (suffix_slots_[slot].entry_plus_one != 0) {
215 const auto& candidate = suffix_slots_[slot];
216 const auto& entry = entries_[candidate.entry_plus_one - 1u];
217 if (entry.goal == request.goal &&
218 paths_[entry.path_offset + candidate.offset] == request.start) {
219 suffix_offset = candidate.offset;
222 slot = (slot + 1u) & mask;
230 if (max_entries_ == 0 || max_path_nodes_ == 0) {
235 if (result.path.
size() > max_path_nodes_) {
239 if (entries_.size() + 1u > max_entries_ ||
240 paths_.size() + result.path.
size() > max_path_nodes_) {
242 ++cap_invalidations_;
244 const auto entry_index = entries_.size();
245 const auto path_offset = paths_.size();
246 paths_.insert(paths_.end(), result.path.
begin(), result.path.
end());
247 entries_.push_back(Entry{
252 result.expanded_nodes,
253 result.reached_nodes,
257 exact_insert(entry_index);
258 if (result.status == PathStatus::Found) {
259 suffix_insert(entry_index);
263 void exact_insert(std::size_t entry_index) {
264 if (exact_slots_.size() < (entries_.size() + 1u) * 2u) {
268 exact_place(entry_index);
271 void exact_place(std::size_t entry_index)
noexcept {
272 const auto mask = exact_slots_.size() - 1u;
273 const auto& entry = entries_[entry_index];
275 static_cast<std::size_t
>(hash_pair(entry.start, entry.goal)) & mask;
276 while (exact_slots_[slot] != 0) {
277 slot = (slot + 1u) & mask;
279 exact_slots_[slot] =
static_cast<std::uint32_t
>(entry_index + 1u);
282 void grow_exact_index() {
283 auto capacity = std::size_t{16};
284 while (capacity < (entries_.size() + 1u) * 2u) {
287 exact_slots_.assign(capacity, 0u);
288 for (std::size_t i = 0; i < entries_.size(); ++i) {
296 void suffix_insert(std::size_t entry_index) {
297 const auto& entry = entries_[entry_index];
298 if (suffix_slots_.size() < (suffix_count_ + entry.path_size + 1u) * 2u) {
299 grow_suffix_index(entry.path_size);
301 for (std::size_t i = 0; i < entry.path_size; ++i) {
302 suffix_place(entry_index, i);
306 void suffix_place(std::size_t entry_index, std::size_t offset)
noexcept {
307 const auto mask = suffix_slots_.size() - 1u;
308 const auto& entry = entries_[entry_index];
309 const auto node = paths_[entry.path_offset + offset];
310 auto slot =
static_cast<std::size_t
>(hash_pair(node, entry.goal)) & mask;
311 while (suffix_slots_[slot].entry_plus_one != 0) {
312 const auto& occupant = suffix_slots_[slot];
313 const auto& occupant_entry = entries_[occupant.entry_plus_one - 1u];
314 if (occupant_entry.goal == entry.goal &&
315 paths_[occupant_entry.path_offset + occupant.offset] == node) {
318 slot = (slot + 1u) & mask;
320 suffix_slots_[slot] = SuffixSlot{
321 static_cast<std::uint32_t
>(entry_index + 1u),
322 static_cast<std::uint32_t
>(offset),
327 void grow_suffix_index(std::size_t additional) {
328 auto capacity = std::size_t{16};
329 while (capacity < (suffix_count_ + additional + 1u) * 2u) {
332 const auto old_slots = suffix_slots_;
333 suffix_slots_.assign(capacity, SuffixSlot{});
335 for (
const auto slot : old_slots) {
336 if (slot.entry_plus_one != 0) {
337 suffix_place(slot.entry_plus_one - 1u, slot.offset);
342 [[nodiscard]]
auto path_span(
const Entry& entry,
343 std::size_t offset = 0)
const noexcept
344 -> std::span<const Coord3> {
345 if (entry.path_size <= offset) {
348 return std::span<const Coord3>{paths_.data() + entry.path_offset + offset,
349 entry.path_size - offset};
352 std::vector<Entry> entries_;
353 std::vector<Coord3> paths_;
354 std::vector<std::uint32_t> exact_slots_;
355 std::vector<SuffixSlot> suffix_slots_;
356 std::size_t suffix_count_ = 0;
357 std::size_t max_entries_ = default_max_entries;
358 std::size_t max_path_nodes_ = default_max_path_nodes;
359 std::size_t hits_ = 0;
360 std::size_t suffix_hits_ = 0;
361 std::size_t misses_ = 0;
362 std::size_t cap_invalidations_ = 0;
363 std::size_t oversized_skips_ = 0;
364 std::size_t class_rebinds_ = 0;
367 std::uintptr_t bound_class_ = 0;
368 std::uint64_t world_fingerprint_ = 0;
369 bool has_world_fingerprint_ =
false;
371 template <
typename World>
372 [[nodiscard]]
static auto world_version_fingerprint(
373 const World& world)
noexcept -> std::uint64_t {
374 if constexpr (std::is_same_v<
typename World::residency_type,
377 auto fingerprint = std::uint64_t{0xcbf29ce484222325ull};
378 for (std::uint64_t i = 0; i < World::chunk_count; ++i) {
380 fingerprint ^= i + 0x9e3779b97f4a7c15ull + (fingerprint << 6u) +
383 fingerprint *= 0x100000001b3ull;
398 const auto mix = [](std::uint64_t x)
noexcept -> std::uint64_t {
399 x = (x ^ (x >> 30u)) * 0xbf58476d1ce4e5b9ull;
400 x = (x ^ (x >> 27u)) * 0x94d049bb133111ebull;
401 return x ^ (x >> 31u);
403 auto acc = std::uint64_t{0};
404 for (
const auto key : world.resident_chunk_keys()) {
405 auto h = mix(key.value);
406 h ^= mix(h + world.residency_generation(key));
407 h ^= mix(h +
static_cast<std::uint64_t
>(world.meta(key).version));
410 return mix(acc +
static_cast<std::uint64_t
>(world.resident_count()) +
411 0x9e3779b97f4a7c15ull);