96 static constexpr std::size_t default_max_entries = 512;
97 static constexpr std::size_t default_max_path_nodes = std::size_t{1} << 20u;
103 max_entries_ = limits.max_entries;
104 max_path_nodes_ = limits.max_path_nodes;
109 if (entries_.size() > max_entries_ || paths_.size() > max_path_nodes_) {
111 ++cap_invalidations_;
115 void reserve_routes(std::size_t route_count) {
116 entries_.reserve(route_count);
119 void reserve_path_nodes(std::size_t node_count) {
120 paths_.reserve(node_count);
123 void clear()
noexcept {
126 bound_provider_type_ = 0;
127 bound_provider_instance_ =
nullptr;
128 bound_provider_revision_ = 0;
132 policy_bound_ =
false;
133 bound_policy_ = MissingChunkPolicy::ReportIndeterminate;
137 cap_invalidations_ = 0;
138 oversized_skips_ = 0;
140 provider_rebinds_ = 0;
143 scoped_survivals_ = 0;
144 retired_entries_ = 0;
153 void bind_class(std::uintptr_t identity)
noexcept {
154 if (bound_class_ == identity) {
157 if (bound_class_ != 0) {
161 bound_class_ = identity;
164 void bind_provider(std::uintptr_t type_identity,
165 const void* instance_identity,
166 std::uint64_t revision)
noexcept {
167 if (bound_provider_type_ == type_identity &&
168 bound_provider_instance_ == instance_identity &&
169 bound_provider_revision_ == revision) {
172 if (bound_provider_type_ != 0) {
176 bound_provider_type_ = type_identity;
177 bound_provider_instance_ = instance_identity;
178 bound_provider_revision_ = revision;
194 void bind_missing_chunk_policy(MissingChunkPolicy policy)
noexcept {
195 if (policy_bound_ && bound_policy_ == policy) {
202 policy_bound_ =
true;
203 bound_policy_ = policy;
206 void invalidate()
noexcept {
210 exact_slots_.clear();
211 suffix_slots_.clear();
223 void set_staleness(UnitRouteStaleness staleness)
noexcept {
224 if (staleness_ == staleness) {
228 staleness_ = staleness;
229 has_world_fingerprint_ =
false;
230 world_fingerprint_ = 0;
231 content_version_snapshot_.clear();
234 [[nodiscard]]
auto staleness()
const noexcept -> UnitRouteStaleness {
245 void set_dependency_cap(std::size_t max_dependency_pairs)
noexcept {
246 max_dependency_pairs_ = max_dependency_pairs;
247 if (deps_.size() > max_dependency_pairs_) {
249 ++cap_invalidations_;
261 template <
typename World>
262 [[nodiscard]]
auto refresh_if_world_changed(
const World& world) ->
bool {
263 if constexpr (!std::is_same_v<
typename World::residency_type,
265 return invalidate_if_world_changed(world);
267 if (staleness_ != UnitRouteStaleness::ScopedFeasible) {
268 return invalidate_if_world_changed(world);
270 if (content_version_snapshot_.size() != World::chunk_count) {
271 content_version_snapshot_.resize(World::chunk_count);
272 for (std::uint64_t i = 0; i < World::chunk_count; ++i) {
273 content_version_snapshot_[i] =
274 world.meta(
ChunkKey{i}).content_version;
283 if (entries_.empty()) {
291 auto changed =
false;
292 for (std::uint64_t i = 0; i < World::chunk_count; ++i) {
293 const auto content_version = world.meta(
ChunkKey{i}).content_version;
294 if (content_version_snapshot_[i] != content_version) {
295 content_version_snapshot_[i] = content_version;
303 if (change_epoch_ == 0) {
311 void reset_stats()
noexcept {
315 cap_invalidations_ = 0;
316 oversized_skips_ = 0;
318 scoped_survivals_ = 0;
319 retired_entries_ = 0;
328 template <
typename World>
329 void capture_world_versions(
const World& world)
noexcept {
330 world_fingerprint_ = world_content_fingerprint(world);
331 has_world_fingerprint_ =
true;
334 template <
typename World>
335 [[nodiscard]]
auto invalidate_if_world_changed(
const World& world)
noexcept
337 if (!has_world_fingerprint_) {
338 capture_world_versions(world);
341 const auto current = world_content_fingerprint(world);
342 if (current == world_fingerprint_) {
346 world_fingerprint_ = current;
347 has_world_fingerprint_ =
true;
363 entries_.size() - dead_count_,
374 PathStatus status = PathStatus::NotComputed;
375 std::uint32_t cost = 0;
376 std::uint32_t cost_scale = 1;
377 std::size_t expanded_nodes = 0;
378 std::size_t reached_nodes = 0;
379 std::size_t path_offset = 0;
380 std::size_t path_size = 0;
385 std::size_t dep_offset = 0;
386 std::size_t dep_count = 0;
387 std::uint64_t validated_epoch = 0;
389 bool whole_world =
false;
395 std::uint64_t key = 0;
400 std::uint32_t entry_plus_one = 0;
401 std::uint32_t offset = 0;
404 template <
typename World,
typename Tag>
409 template <
typename World,
typename Tag,
typename Prov
ider>
412 const Provider& provider,
417 [[nodiscard]]
static auto hash_pair(
Coord3 first,
Coord3 second)
noexcept
419 auto hash = std::uint64_t{0xcbf29ce484222325ull};
420 hash = (hash ^
static_cast<std::uint64_t
>(first.x)) * 0x100000001b3ull;
421 hash = (hash ^
static_cast<std::uint64_t
>(first.y)) * 0x100000001b3ull;
422 hash = (hash ^
static_cast<std::uint64_t
>(first.z)) * 0x100000001b3ull;
423 hash = (hash ^
static_cast<std::uint64_t
>(second.x)) * 0x100000001b3ull;
424 hash = (hash ^
static_cast<std::uint64_t
>(second.y)) * 0x100000001b3ull;
425 hash = (hash ^
static_cast<std::uint64_t
>(second.z)) * 0x100000001b3ull;
426 hash = (hash ^ (hash >> 30u)) * 0xbf58476d1ce4e5b9ull;
427 hash = (hash ^ (hash >> 27u)) * 0x94d049bb133111ebull;
428 return hash ^ (hash >> 31u);
433 [[nodiscard]]
auto find(
PathRequest request)
noexcept -> Entry* {
434 if (exact_slots_.empty()) {
437 const auto mask = exact_slots_.size() - 1u;
439 static_cast<std::size_t
>(hash_pair(request.start, request.goal)) & mask;
440 while (exact_slots_[slot] != 0) {
441 auto& entry = entries_[exact_slots_[slot] - 1u];
442 if (entry.alive && entry.start == request.start &&
443 entry.goal == request.goal) {
446 slot = (slot + 1u) & mask;
451 [[nodiscard]]
auto find_suffix(
PathRequest request,
452 std::size_t& suffix_offset)
noexcept
454 if (suffix_slots_.empty()) {
457 const auto mask = suffix_slots_.size() - 1u;
459 static_cast<std::size_t
>(hash_pair(request.start, request.goal)) & mask;
460 while (suffix_slots_[slot].entry_plus_one != 0) {
461 const auto& candidate = suffix_slots_[slot];
462 auto& entry = entries_[candidate.entry_plus_one - 1u];
463 if (entry.alive && entry.goal == request.goal &&
464 paths_[entry.path_offset + candidate.offset] == request.start) {
465 suffix_offset = candidate.offset;
468 slot = (slot + 1u) & mask;
477 void sync_ineligible_epoch()
noexcept {
478 if (staleness_ != UnitRouteStaleness::ScopedFeasible) {
481 if (ineligible_synced_epoch_ == change_epoch_) {
484 if (!entries_.empty()) {
487 ineligible_synced_epoch_ = change_epoch_;
490 void retire(Entry& entry)
noexcept {
498 if (dead_count_ * 2u > max_entries_ && max_entries_ != 0) {
511 template <
typename World>
512 [[nodiscard]]
auto validate_for_serve(
const World& world,
513 Entry& entry)
noexcept ->
bool {
514 if (staleness_ != UnitRouteStaleness::ScopedFeasible) {
517 if (entry.validated_epoch == change_epoch_) {
520 if (entry.whole_world) {
525 for (std::size_t i = 0; i < entry.dep_count; ++i) {
526 const auto& dep = deps_[entry.dep_offset + i];
527 if (world.meta(
ChunkKey{dep.key}).content_version !=
528 dep.content_version) {
533 entry.validated_epoch = change_epoch_;
538 template <
typename World,
bool ScopeEligible>
541 using Shape =
typename World::shape_type;
544 if (max_entries_ == 0 || max_path_nodes_ == 0) {
549 if (result.path.
size() > max_path_nodes_) {
560 staleness_ == UnitRouteStaleness::ScopedFeasible &&
561 std::is_same_v<typename World::residency_type, AlwaysResident>;
562 const auto scoped_footprint =
563 scoped && ScopeEligible && result.status == PathStatus::Found;
564 dep_scratch_.clear();
565 if (scoped_footprint) {
566 auto previous = std::numeric_limits<std::uint64_t>::max();
567 for (
const auto node : result.path) {
568 const auto key = chunk_key<Shape>(tile_key<Shape>(node)).value;
569 if (key != previous) {
570 dep_scratch_.push_back(key);
574 if (dep_scratch_.size() > max_dependency_pairs_) {
579 if (entries_.size() + 1u > max_entries_ ||
580 paths_.size() + result.path.
size() > max_path_nodes_ ||
581 deps_.size() + dep_scratch_.size() > max_dependency_pairs_) {
583 ++cap_invalidations_;
585 const auto entry_index = entries_.size();
586 const auto path_offset = paths_.size();
587 const auto dep_offset = deps_.size();
588 paths_.insert(paths_.end(), result.path.
begin(), result.path.
end());
589 for (
const auto key : dep_scratch_) {
590 deps_.push_back(DepPair{key, world.meta(
ChunkKey{key}).content_version});
592 entries_.push_back(Entry{
598 result.expanded_nodes,
599 result.reached_nodes,
606 scoped && !scoped_footprint,
608 exact_insert(entry_index);
609 if (result.status == PathStatus::Found) {
610 suffix_insert(entry_index);
614 void exact_insert(std::size_t entry_index) {
615 if (exact_slots_.size() < (entries_.size() + 1u) * 2u) {
619 exact_place(entry_index);
622 void exact_place(std::size_t entry_index)
noexcept {
623 const auto mask = exact_slots_.size() - 1u;
624 const auto& entry = entries_[entry_index];
626 static_cast<std::size_t
>(hash_pair(entry.start, entry.goal)) & mask;
627 while (exact_slots_[slot] != 0) {
628 slot = (slot + 1u) & mask;
630 exact_slots_[slot] =
static_cast<std::uint32_t
>(entry_index + 1u);
636 void grow_exact_index() {
637 auto capacity = std::size_t{16};
638 while (capacity < (entries_.size() + 1u) * 2u) {
641 exact_slots_.assign(capacity, 0u);
642 for (std::size_t i = 0; i < entries_.size(); ++i) {
643 if (entries_[i].alive) {
653 void suffix_insert(std::size_t entry_index) {
654 const auto& entry = entries_[entry_index];
655 if (suffix_slots_.size() < (suffix_count_ + entry.path_size + 1u) * 2u) {
656 grow_suffix_index(entry.path_size);
658 for (std::size_t i = 0; i < entry.path_size; ++i) {
659 suffix_place(entry_index, i);
663 void suffix_place(std::size_t entry_index, std::size_t offset)
noexcept {
664 const auto mask = suffix_slots_.size() - 1u;
665 const auto& entry = entries_[entry_index];
666 const auto node = paths_[entry.path_offset + offset];
667 auto slot =
static_cast<std::size_t
>(hash_pair(node, entry.goal)) & mask;
668 while (suffix_slots_[slot].entry_plus_one != 0) {
669 const auto& occupant = suffix_slots_[slot];
670 const auto& occupant_entry = entries_[occupant.entry_plus_one - 1u];
671 if (occupant_entry.goal == entry.goal &&
672 paths_[occupant_entry.path_offset + occupant.offset] == node) {
673 if (occupant_entry.alive) {
678 suffix_slots_[slot] = SuffixSlot{
679 static_cast<std::uint32_t
>(entry_index + 1u),
680 static_cast<std::uint32_t
>(offset),
684 slot = (slot + 1u) & mask;
686 suffix_slots_[slot] = SuffixSlot{
687 static_cast<std::uint32_t
>(entry_index + 1u),
688 static_cast<std::uint32_t
>(offset),
693 void grow_suffix_index(std::size_t additional) {
694 auto capacity = std::size_t{16};
695 while (capacity < (suffix_count_ + additional + 1u) * 2u) {
701 const auto old_slots = std::move(suffix_slots_);
702 suffix_slots_.assign(capacity, SuffixSlot{});
704 for (
const auto slot : old_slots) {
705 if (slot.entry_plus_one != 0 &&
706 entries_[slot.entry_plus_one - 1u].alive) {
707 suffix_place(slot.entry_plus_one - 1u, slot.offset);
712 [[nodiscard]]
auto path_span(
const Entry& entry,
713 std::size_t offset = 0)
const noexcept
714 -> std::span<const Coord3> {
715 if (entry.path_size <= offset) {
718 return std::span<const Coord3>{paths_.data() + entry.path_offset + offset,
719 entry.path_size - offset};
722 std::vector<Entry> entries_;
723 std::vector<Coord3> paths_;
724 std::vector<DepPair> deps_;
725 std::vector<std::uint64_t> dep_scratch_;
726 std::vector<std::uint32_t> exact_slots_;
727 std::vector<SuffixSlot> suffix_slots_;
730 std::vector<ContentVersion> content_version_snapshot_;
731 std::uint64_t change_epoch_ = 1;
732 std::uint64_t ineligible_synced_epoch_ = 0;
733 std::size_t dead_count_ = 0;
734 std::size_t revalidations_ = 0;
735 std::size_t scoped_survivals_ = 0;
736 std::size_t retired_entries_ = 0;
737 UnitRouteStaleness staleness_ = UnitRouteStaleness::WholeWorldExact;
738 std::size_t suffix_count_ = 0;
739 std::size_t max_entries_ = default_max_entries;
740 std::size_t max_path_nodes_ = default_max_path_nodes;
741 std::size_t max_dependency_pairs_ = default_max_path_nodes / 8u;
742 std::size_t hits_ = 0;
743 std::size_t suffix_hits_ = 0;
744 std::size_t misses_ = 0;
745 std::size_t cap_invalidations_ = 0;
746 std::size_t oversized_skips_ = 0;
747 std::size_t class_rebinds_ = 0;
748 std::size_t provider_rebinds_ = 0;
749 std::size_t policy_rebinds_ = 0;
752 bool policy_bound_ =
false;
753 MissingChunkPolicy bound_policy_ = MissingChunkPolicy::ReportIndeterminate;
756 std::uintptr_t bound_class_ = 0;
757 std::uintptr_t bound_provider_type_ = 0;
758 const void* bound_provider_instance_ =
nullptr;
759 std::uint64_t bound_provider_revision_ = 0;
760 std::uint64_t world_fingerprint_ = 0;
761 bool has_world_fingerprint_ =
false;
763 template <
typename World>
764 [[nodiscard]]
static auto world_content_fingerprint(
765 const World& world)
noexcept -> std::uint64_t {
766 if constexpr (std::is_same_v<
typename World::residency_type,
769 auto fingerprint = std::uint64_t{0xcbf29ce484222325ull};
770 for (std::uint64_t i = 0; i < World::chunk_count; ++i) {
771 const auto content_version = world.meta(
ChunkKey{i}).content_version;
772 fingerprint ^= i + 0x9e3779b97f4a7c15ull + (fingerprint << 6u) +
774 fingerprint ^= content_version.value;
775 fingerprint *= 0x100000001b3ull;
792 const auto mix = [](std::uint64_t x)
noexcept -> std::uint64_t {
793 x = (x ^ (x >> 30u)) * 0xbf58476d1ce4e5b9ull;
794 x = (x ^ (x >> 27u)) * 0x94d049bb133111ebull;
795 return x ^ (x >> 31u);
797 auto acc = std::uint64_t{0};
798 for (
const auto key : world.resident_chunk_keys()) {
799 auto h = mix(key.value);
800 h ^= mix(h + world.residency_generation(key).value);
801 h ^= mix(h + world.meta(key).content_version.value);
804 return mix(acc +
static_cast<std::uint64_t
>(world.resident_count()) +
805 0x9e3779b97f4a7c15ull);