201 requests_.reserve(count);
202 results_.reserve(count);
203 offsets_.reserve(count);
204 sizes_.reserve(count);
205 processed_.reserve(count);
206 request_group_.reserve(count);
207 group_members_.reserve(count);
208 group_start_chunks_.reserve(count);
209 precheck_survivors_.reserve(count);
210 survivor_original_.reserve(count);
211 weighted_batch_.reserve_requests(count);
212 unit_field_goals_.reserve(1);
217 paths_.reserve(count);
218 unit_route_cache_.reserve_path_nodes(count);
219 unit_field_scratch_.reserve_nodes(count);
220 unit_field_product_.reserve_nodes(count);
221 weighted_batch_.reserve_path_nodes(count);
222 portal_segment_cache_.reserve_path_nodes(count);
223 portal_replan_product_.reserve_path_nodes(count);
228 unit_scratch_.reserve_nodes(count);
229 unit_field_scratch_.reserve_nodes(count);
230 unit_field_product_.reserve_nodes(count);
231 weighted_batch_.reserve_search_nodes(count);
236 unit_route_cache_.reserve_routes(count);
241 unit_field_product_cache_.reserve_entries(count);
251 unit_field_product_.reserve_dependencies(count);
260 unit_field_product_cache_.reserve_entries(count);
265 unit_field_product_.reserve_dependencies(count);
270 portal_segment_cache_.reserve_segments(count);
271 portal_replan_product_.reserve_waypoints(count);
286 unit_route_cache_.clear();
287 unit_field_product_cache_.clear();
288 portal_segment_cache_.clear();
289 world_changes_since_clear_ = 0;
290 bound_unit_class_ = 0;
300 const auto ticket =
PathTicket{requests_.size(), generation_};
301 requests_.push_back(request);
316 return results_published_ ? std::span<const PathResult>{results_}
317 : std::span<const PathResult>{};
330 -> std::optional<PathResult> {
331 if (ticket.generation != generation_ || !results_published_ ||
332 ticket.value >= results_.size()) {
335 return results_[ticket.value];
347 if (ticket.generation != generation_) {
349 "PathRequestRuntime::result received a stale PathTicket; use "
350 "try_result() for uncertain lookup");
352 if (!results_published_) {
354 "PathRequestRuntime::result has no published result batch; process "
355 "requests first or use try_result()");
357 if (ticket.value >= results_.size()) {
359 "PathRequestRuntime::result received an out-of-range PathTicket; "
360 "use try_result() for uncertain lookup");
362 return results_[ticket.value];
367 return unit_route_cache_;
372 return unit_route_cache_;
378 return portal_segment_cache_;
384 return portal_segment_cache_;
390 stats.submitted = requests_.size();
391 if (results_published_) {
392 stats.completed = results_.size();
393 stats.path_nodes = paths_.size();
397 stats.invalid_start = 0;
398 stats.invalid_goal = 0;
400 stats.not_computed = 0;
401 stats.no_candidate = 0;
402 stats.indeterminate = 0;
403 stats.cost_overflow = 0;
404 stats.precheck_ruled_out = 0;
405 stats.path_nodes = 0;
406 stats.field_product_candidate_groups = 0;
407 stats.field_product_used_groups = 0;
408 stats.field_product_skipped_groups = 0;
409 stats.portal_replan = {};
411 stats.route_cache = unit_route_cache_.stats();
412 stats.field_product_cache = unit_field_product_cache_.stats();
413 stats.weighted_batch =
415 stats.portal_segment_cache = portal_segment_cache_.stats();
416 stats.cache_clears = cache_clears_;
417 stats.class_cache_invalidations = class_cache_invalidations_;
431 template <
typename World,
typename ClassOrTag>
435 -> std::span<const PathResult> {
437 results_.resize(requests_.size());
438 offsets_.assign(requests_.size(), 0);
439 sizes_.assign(requests_.size(), 0);
440 processed_.assign(requests_.size(), 0);
446 prepare_process(world, policy);
448 detail::tag_identity<movement::movement_class_of<ClassOrTag>>());
449 if (graph !=
nullptr) {
450 precheck_prepass<ClassOrTag>(world, *graph, policy.missing_chunk_policy,
451 AdjacentTransitions{});
453 if constexpr (std::is_same_v<
typename World::residency_type,
460 if (policy.use_unit_field_product_cache) {
461 process_repeated_goal_fields<World, ClassOrTag>(world, policy);
465 for (std::size_t i = 0; i < requests_.size(); ++i) {
466 if (processed_[i] != 0) {
469 const auto result = cached_astar_path<World, ClassOrTag>(
470 world, requests_[i], unit_scratch_, unit_route_cache_,
471 policy.missing_chunk_policy);
473 record_status(
result.status);
475 refresh_result_spans();
476 results_published_ =
true;
481 template <
typename World,
typename ClassOrTag,
typename Prov
ider>
485 const Provider& provider) -> std::span<const PathResult> {
487 results_.resize(requests_.size());
488 offsets_.assign(requests_.size(), 0);
489 sizes_.assign(requests_.size(), 0);
490 processed_.assign(requests_.size(), 0);
492 prepare_process(world, policy);
494 detail::tag_identity<movement::movement_class_of<ClassOrTag>>());
495 if (graph !=
nullptr && graph->matches_provider(provider)) {
496 precheck_prepass<ClassOrTag>(world, *graph, policy.missing_chunk_policy,
503 for (std::size_t i = 0; i < requests_.size(); ++i) {
504 if (processed_[i] != 0) {
507 const auto result = cached_astar_path<World, ClassOrTag, Provider>(
508 world, requests_[i], unit_scratch_, unit_route_cache_, provider,
509 policy.missing_chunk_policy);
511 record_status(
result.status);
513 refresh_result_spans();
514 results_published_ =
true;
523 template <
typename World,
typename Class, std::u
int32_t MaxCost>
527 -> std::span<const PathResult> {
528 static_assert(std::derived_from<Class, movement::movement_class_tag>,
529 "process_weighted_batch<World, Class, MaxCost> requires a "
530 "MovementClass; pass a movement class such as "
531 "PositiveCostFieldMovement.");
532 return process_weighted_batch_impl<World, Class, MaxCost, Class>(
537 template <
typename World,
typename Class, std::uint32_t MaxCost,
542 const Provider& provider) -> std::span<const PathResult> {
543 static_assert(std::derived_from<Class, movement::movement_class_tag>,
544 "process_weighted_batch<World, Class, MaxCost> requires a "
545 "MovementClass; pass a movement class such as "
546 "PositiveCostFieldMovement.");
547 return process_weighted_batch_impl<World, Class, MaxCost, Class>(
548 world, policy, graph, provider);
552 template <
typename World,
typename BatchClass, std::uint32_t MaxCost,
553 typename PrecheckClassOrTag,
typename Provider>
554 [[nodiscard]]
auto process_weighted_batch_impl(
557 const Provider& provider) -> std::span<const PathResult> {
559 results_.resize(requests_.size());
560 offsets_.assign(requests_.size(), 0);
561 sizes_.assign(requests_.size(), 0);
562 processed_.assign(requests_.size(), 0);
564 prepare_process(world, policy);
566 if (graph !=
nullptr && !graph->matches_provider(provider)) {
569 if (graph !=
nullptr) {
570 precheck_prepass<PrecheckClassOrTag>(
571 world, *graph, policy.missing_chunk_policy, provider);
573 if constexpr (std::is_same_v<
typename World::residency_type,
575 if (policy.use_weighted_field_product_cache) {
576 process_repeated_goal_weighted_fields<World, BatchClass>(world, policy,
586 if constexpr (std::is_same_v<
typename World::residency_type,
588 std::is_same_v<
typename ShapeTraits<
589 typename World::shape_type>::lattice_type,
590 lattice::Orthogonal> &&
591 std::is_same_v<Provider, AdjacentTransitions> &&
592 detail::portal_replan_tags<BatchClass>::eligible) {
593 if (policy.weighted_replan_strategy ==
594 WeightedReplanStrategy::PortalFirst) {
595 process_portal_first_singletons<World, BatchClass>(world, policy);
601 if (policy.weighted_replan_strategy ==
602 WeightedReplanStrategy::PortalFirst) {
603 for (std::size_t i = 0; i < requests_.size(); ++i) {
604 if (processed_[i] == 0) {
605 ++stats_.portal_replan.ineligible_fallbacks;
613 precheck_survivors_.clear();
614 survivor_original_.clear();
615 for (std::size_t i = 0; i < requests_.size(); ++i) {
616 if (processed_[i] == 0) {
617 survivor_original_.push_back(i);
618 precheck_survivors_.push_back(requests_[i]);
622 weighted_path_batch<World, BatchClass, MaxCost, Provider>(
623 world, precheck_survivors_, weighted_batch_,
624 policy.missing_chunk_policy, provider);
625 for (std::size_t s = 0; s < batch.size(); ++s) {
626 const auto i = survivor_original_[s];
627 copy_result(i, batch[s]);
628 record_status(batch[s].status);
630 refresh_result_spans();
631 results_published_ =
true;
635 template <
typename World,
typename Class,
typename Prov
ider>
636 void process_repeated_goal_weighted_fields(
const World& world,
637 PathRuntimeCachePolicy policy,
638 const Provider& provider) {
639 using Shape =
typename World::shape_type;
640 if (policy.weighted_field_product_min_goal_reuse < 2) {
641 policy.weighted_field_product_min_goal_reuse = 2;
643 if (policy.weighted_field_product_min_start_chunks == 0) {
644 policy.weighted_field_product_min_start_chunks = 1;
646 unit_field_product_cache_.set_byte_budget(
647 policy.weighted_field_product_cache_byte_budget);
648 const auto product_distance_fits =
649 unit_field_product_cache_.can_fit_distance_storage(
650 detail::NodeIndexSpace<World>{world}.capacity_hint());
652 constexpr auto no_group = std::numeric_limits<std::uint32_t>::max();
653 group_goals_.clear();
654 group_counts_.clear();
655 request_group_.assign(requests_.size(), no_group);
656 auto slot_capacity = goal_group_slots_.size() < 16u
658 : goal_group_slots_.size();
659 while (slot_capacity < (requests_.size() + 1u) * 2u) {
662 goal_group_slots_.assign(slot_capacity, 0u);
663 const auto slot_mask = slot_capacity - 1u;
664 for (std::size_t i = 0; i < requests_.size(); ++i) {
665 if (processed_[i] != 0) {
668 if (!contains<Shape>(requests_[i].start)) {
669 auto invalid = PathResult{};
670 invalid.status = PathStatus::InvalidStart;
671 copy_result(i, invalid);
672 record_status(invalid.status);
676 const auto goal = requests_[i].goal;
678 static_cast<std::size_t
>(detail::coord_hash(goal)) & slot_mask;
679 auto group = no_group;
680 while (goal_group_slots_[slot] != 0u) {
681 const auto candidate = goal_group_slots_[slot] - 1u;
682 if (group_goals_[candidate] == goal) {
686 slot = (slot + 1u) & slot_mask;
688 if (group == no_group) {
689 group =
static_cast<std::uint32_t
>(group_goals_.size());
690 goal_group_slots_[slot] = group + 1u;
691 group_goals_.push_back(goal);
692 group_counts_.push_back(0u);
694 request_group_[i] = group;
695 ++group_counts_[group];
698 group_offsets_.assign(group_goals_.size() + 1u, 0u);
699 for (std::size_t i = 0; i < requests_.size(); ++i) {
700 if (request_group_[i] != no_group) {
701 ++group_offsets_[request_group_[i] + 1u];
704 for (std::size_t group = 1; group < group_offsets_.size(); ++group) {
705 group_offsets_[group] += group_offsets_[group - 1u];
707 group_cursors_.assign(group_offsets_.begin(), group_offsets_.end());
708 group_members_.assign(group_offsets_.back(), 0u);
709 for (std::size_t i = 0; i < requests_.size(); ++i) {
710 if (request_group_[i] != no_group) {
711 group_members_[group_cursors_[request_group_[i]]++] =
712 static_cast<std::uint32_t
>(i);
716 for (std::uint32_t group = 0; group < group_goals_.size(); ++group) {
717 if (group_counts_[group] < policy.weighted_field_product_min_goal_reuse) {
720 const auto members_begin = group_offsets_[group];
721 const auto members_end = group_offsets_[group + 1u];
722 group_start_chunks_.clear();
723 for (
auto member = members_begin; member < members_end; ++member) {
724 const auto& request = requests_[group_members_[member]];
725 group_start_chunks_.push_back(
726 chunk_key<Shape>(tile_key<Shape>(request.start)).value);
728 std::sort(group_start_chunks_.begin(), group_start_chunks_.end());
729 const auto start_chunk_count =
static_cast<std::size_t
>(
730 std::unique(group_start_chunks_.begin(), group_start_chunks_.end()) -
731 group_start_chunks_.begin());
732 ++stats_.field_product_candidate_groups;
733 if (start_chunk_count < policy.weighted_field_product_min_start_chunks) {
734 ++stats_.field_product_skipped_groups;
737 if (!product_distance_fits) {
741 ++stats_.field_product_skipped_groups;
745 unit_field_goals_.clear();
746 unit_field_goals_.add(group_goals_[group]);
747 auto* product = unit_field_product_cache_
748 .template lookup_weighted<World, Class, Provider>(
749 world, unit_field_goals_, provider);
750 if (product ==
nullptr) {
752 build_weighted_distance_field_product<World, Class, Provider>(
753 world, unit_field_goals_, unit_field_product_,
754 unit_field_scratch_, provider);
755 if (field.status == PathStatus::Found) {
760 unit_field_product_cache_
761 .template store_weighted_reusing<World, Class, Provider>(
762 unit_field_product_, provider);
765 if (product ==
nullptr) {
766 ++stats_.field_product_skipped_groups;
770 ++stats_.field_product_used_groups;
771 for (
auto member = members_begin; member < members_end; ++member) {
772 const auto index =
static_cast<std::size_t
>(group_members_[member]);
774 weighted_distance_field_product_path<World, Class, Provider>(
775 world, requests_[index].start, *product, unit_field_scratch_,
777 copy_result(index,
result);
778 record_status(
result.status);
779 processed_[index] = 1;
797 void bind_unit_class(std::uintptr_t identity)
noexcept {
798 if (bound_unit_class_ == identity) {
801 if (bound_unit_class_ != 0) {
802 unit_route_cache_.clear();
803 unit_field_product_cache_.clear();
804 ++class_cache_invalidations_;
806 bound_unit_class_ = identity;
809 void clear_results() noexcept {
810 results_published_ =
false;
819 template <
typename World>
820 void prepare_process(
const World& world, PathRuntimeCachePolicy policy) {
821 unit_route_cache_.set_caps(UnitRouteCacheLimits{
822 policy.max_route_entries, policy.max_route_path_nodes});
823 unit_route_cache_.set_dependency_cap(policy.max_route_dependency_pairs);
827 unit_route_cache_.set_staleness(policy.unit_route_staleness);
828 portal_segment_cache_.set_segment_budget(policy.portal_segment_budget);
829 if (!policy.invalidate_unit_route_cache_on_world_change) {
832 if (!unit_route_cache_.refresh_if_world_changed(world)) {
835 ++stats_.world_cache_invalidations;
840 if constexpr (std::is_same_v<
typename World::residency_type,
842 if (policy.unit_route_staleness == UnitRouteStaleness::ScopedFeasible) {
849 ++world_changes_since_clear_;
850 if (policy.clear_every_world_change != 0 &&
851 world_changes_since_clear_ >= policy.clear_every_world_change) {
862 template <
typename World,
typename PassableTag>
863 void process_repeated_goal_fields(
const World& world,
864 PathRuntimeCachePolicy policy) {
865 using Shape =
typename World::shape_type;
867 if (policy.unit_field_product_min_goal_reuse < 2) {
868 policy.unit_field_product_min_goal_reuse = 2;
870 if (policy.unit_field_product_min_start_chunks == 0) {
871 policy.unit_field_product_min_start_chunks = 1;
873 unit_field_product_cache_.set_byte_budget(
874 policy.unit_field_product_cache_byte_budget);
875 const auto product_distance_fits =
876 unit_field_product_cache_.can_fit_distance_storage(
877 detail::NodeIndexSpace<World>{world}.capacity_hint());
879 constexpr auto no_group = std::numeric_limits<std::uint32_t>::max();
880 group_goals_.clear();
881 group_counts_.clear();
882 request_group_.assign(requests_.size(), no_group);
883 auto slot_capacity = goal_group_slots_.size() < 16u
885 : goal_group_slots_.size();
886 while (slot_capacity < (requests_.size() + 1u) * 2u) {
889 goal_group_slots_.assign(slot_capacity, 0u);
890 const auto slot_mask = slot_capacity - 1u;
891 for (std::size_t i = 0; i < requests_.size(); ++i) {
892 if (processed_[i] != 0) {
899 if (!contains<Shape>(requests_[i].start)) {
900 auto invalid = PathResult{};
901 invalid.status = PathStatus::InvalidStart;
902 copy_result(i, invalid);
903 record_status(invalid.status);
907 const auto goal = requests_[i].goal;
909 static_cast<std::size_t
>(detail::coord_hash(goal)) & slot_mask;
910 auto group = no_group;
911 while (goal_group_slots_[slot] != 0u) {
912 const auto candidate = goal_group_slots_[slot] - 1u;
913 if (group_goals_[candidate] == goal) {
917 slot = (slot + 1u) & slot_mask;
919 if (group == no_group) {
920 group =
static_cast<std::uint32_t
>(group_goals_.size());
921 goal_group_slots_[slot] = group + 1u;
922 group_goals_.push_back(goal);
923 group_counts_.push_back(0u);
925 request_group_[i] = group;
926 ++group_counts_[group];
930 group_offsets_.assign(group_goals_.size() + 1u, 0u);
931 for (std::size_t i = 0; i < requests_.size(); ++i) {
932 if (request_group_[i] != no_group) {
933 ++group_offsets_[request_group_[i] + 1u];
936 for (std::size_t g = 1; g < group_offsets_.size(); ++g) {
937 group_offsets_[g] += group_offsets_[g - 1u];
939 group_cursors_.assign(group_offsets_.begin(), group_offsets_.end());
940 group_members_.assign(group_offsets_.back(), 0u);
941 for (std::size_t i = 0; i < requests_.size(); ++i) {
942 if (request_group_[i] != no_group) {
943 group_members_[group_cursors_[request_group_[i]]++] =
944 static_cast<std::uint32_t
>(i);
948 for (std::uint32_t g = 0; g < group_goals_.size(); ++g) {
949 if (group_counts_[g] < policy.unit_field_product_min_goal_reuse) {
952 const auto members_begin = group_offsets_[g];
953 const auto members_end = group_offsets_[g + 1u];
954 group_start_chunks_.clear();
955 for (
auto m = members_begin; m < members_end; ++m) {
956 const auto& request = requests_[group_members_[m]];
957 group_start_chunks_.push_back(
958 chunk_key<Shape>(tile_key<Shape>(request.start)).value);
960 std::sort(group_start_chunks_.begin(), group_start_chunks_.end());
961 const auto start_chunk_count =
static_cast<std::size_t
>(
962 std::unique(group_start_chunks_.begin(), group_start_chunks_.end()) -
963 group_start_chunks_.begin());
965 ++stats_.field_product_candidate_groups;
966 if (start_chunk_count < policy.unit_field_product_min_start_chunks) {
967 ++stats_.field_product_skipped_groups;
970 if (!product_distance_fits) {
974 ++stats_.field_product_skipped_groups;
978 unit_field_goals_.clear();
979 unit_field_goals_.add(group_goals_[g]);
981 unit_field_product_cache_.template lookup<World, PassableTag>(
982 world, unit_field_goals_);
983 if (product ==
nullptr) {
984 const auto field = build_distance_field_product<World, PassableTag>(
985 world, unit_field_goals_, unit_field_product_, unit_field_scratch_);
986 if (field.status == PathStatus::Found) {
993 product = unit_field_product_cache_
994 .template store_reusing<World, PassableTag>(
995 unit_field_product_);
999 if (product ==
nullptr) {
1000 ++stats_.field_product_skipped_groups;
1004 ++stats_.field_product_used_groups;
1005 for (
auto m = members_begin; m < members_end; ++m) {
1006 const auto j =
static_cast<std::size_t
>(group_members_[m]);
1007 const auto result = distance_field_product_path<World, PassableTag>(
1008 world, requests_[j].start, *product, unit_field_scratch_);
1010 record_status(
result.status);
1023 template <
typename ClassOrTag,
typename World,
typename Prov
ider>
1024 void precheck_prepass(
1026 const RegionGraphT<typename World::residency_type>& graph,
1027 MissingChunkPolicy missing_chunk_policy,
const Provider& provider) {
1028 for (std::size_t i = 0; i < requests_.size(); ++i) {
1029 if (processed_[i] != 0) {
1032 const auto status = precheck_path<ClassOrTag>(
1033 graph, world, requests_[i], precheck_scratch_, missing_chunk_policy,
1035 if (!precheck_rules_out_path(status)) {
1038 auto ruled_out = PathResult{};
1039 ruled_out.status = PathStatus::NoPath;
1040 copy_result(i, ruled_out);
1041 record_status(ruled_out.status);
1042 ++stats_.precheck_ruled_out;
1053 template <
typename World,
typename BatchClass>
1054 void process_portal_first_singletons(
const World& world,
1055 const PathRuntimeCachePolicy& policy) {
1056 using Shape =
typename World::shape_type;
1059 constexpr auto no_group = std::numeric_limits<std::uint32_t>::max();
1060 group_goals_.clear();
1061 group_counts_.clear();
1062 auto slot_capacity = goal_group_slots_.size() < 16u
1064 : goal_group_slots_.size();
1065 while (slot_capacity < (requests_.size() + 1u) * 2u) {
1066 slot_capacity *= 2u;
1068 goal_group_slots_.assign(slot_capacity, 0u);
1069 const auto slot_mask = slot_capacity - 1u;
1070 const auto group_of = [&](Coord3 goal,
bool insert) {
1072 static_cast<std::size_t
>(detail::coord_hash(goal)) & slot_mask;
1073 while (goal_group_slots_[slot] != 0u) {
1074 const auto candidate = goal_group_slots_[slot] - 1u;
1075 if (group_goals_[candidate] == goal) {
1078 slot = (slot + 1u) & slot_mask;
1083 const auto group =
static_cast<std::uint32_t
>(group_goals_.size());
1084 goal_group_slots_[slot] = group + 1u;
1085 group_goals_.push_back(goal);
1086 group_counts_.push_back(0u);
1089 for (std::size_t i = 0; i < requests_.size(); ++i) {
1090 if (processed_[i] != 0) {
1093 ++group_counts_[group_of(requests_[i].goal,
true)];
1096 auto& replan_stats = stats_.portal_replan;
1097 for (std::size_t i = 0; i < requests_.size(); ++i) {
1098 if (processed_[i] != 0) {
1101 const auto request = requests_[i];
1102 if (!contains<Shape>(request.start) || !contains<Shape>(request.goal)) {
1105 if (group_counts_[group_of(request.goal,
false)] != 1u) {
1108 ++replan_stats.attempts;
1110 build_weighted_chunk_portal_route_product_cached<World, BatchClass>(
1111 world, request, unit_scratch_, portal_segment_cache_,
1112 portal_replan_product_);
1113 if (
result.status != PathStatus::Found) {
1118 const auto same_chunk = chunk_coord<Shape>(request.start) ==
1119 chunk_coord<Shape>(request.goal);
1120 if (!same_chunk && portal_replan_product_.waypoints().empty()) {
1121 ++replan_stats.no_candidates;
1123 ++replan_stats.verification_failures;
1125 ++replan_stats.exact_fallbacks;
1133 const auto premium_den = policy.portal_premium_limit_den == 0
1135 : policy.portal_premium_limit_den;
1136 const auto lower_bound =
static_cast<std::uint64_t
>(
1137 detail::manhattan(request.start, request.goal));
1139 policy.portal_premium_limit_num == 0 ||
1140 static_cast<std::uint64_t
>(
result.cost) * premium_den <=
1141 lower_bound * policy.portal_premium_limit_num;
1143 ++replan_stats.premium_rejections;
1144 ++replan_stats.exact_fallbacks;
1151 record_status(
result.status);
1153 ++replan_stats.accepted;
1157 void copy_result(std::size_t index, PathResult
result) {
1158 offsets_[index] = paths_.size();
1159 sizes_[index] =
result.path.size();
1160 paths_.insert(paths_.end(),
result.path.begin(),
result.path.end());
1161 results_[index] = PathResult{
1167 void refresh_result_spans() noexcept {
1168 for (std::size_t i = 0; i < results_.size(); ++i) {
1169 if (sizes_[i] == 0) {
1170 results_[i].path = {};
1173 std::span<const Coord3>{paths_.data() + offsets_[i], sizes_[i]};
1178 void record_status(PathStatus status)
noexcept {
1180 case PathStatus::NotComputed:
1181 ++stats_.not_computed;
1183 case PathStatus::Found:
1186 case PathStatus::InvalidStart:
1187 ++stats_.invalid_start;
1189 case PathStatus::InvalidGoal:
1190 ++stats_.invalid_goal;
1192 case PathStatus::NoPath:
1195 case PathStatus::Indeterminate:
1196 ++stats_.indeterminate;
1198 case PathStatus::CostOverflow:
1199 ++stats_.cost_overflow;
1201 case PathStatus::NoCandidate:
1202 ++stats_.no_candidate;
1207 std::vector<PathRequest> requests_;
1208 std::vector<PathResult> results_;
1209 std::vector<std::size_t> offsets_;
1210 std::vector<std::size_t> sizes_;
1211 std::vector<std::uint8_t> processed_;
1212 std::vector<Coord3> paths_;
1213 bool results_published_ =
false;
1217 RegionGraphScratch precheck_scratch_;
1218 std::vector<PathRequest> precheck_survivors_;
1219 std::vector<std::size_t> survivor_original_;
1222 std::vector<std::uint32_t> goal_group_slots_;
1223 std::vector<Coord3> group_goals_;
1224 std::vector<std::uint32_t> group_counts_;
1225 std::vector<std::uint32_t> group_offsets_;
1226 std::vector<std::uint32_t> group_cursors_;
1227 std::vector<std::uint32_t> group_members_;
1228 std::vector<std::uint32_t> request_group_;
1229 std::vector<std::uint64_t> group_start_chunks_;
1230 PathScratch unit_scratch_;
1231 UnitRouteCache unit_route_cache_;
1232 DistanceFieldScratch unit_field_scratch_;
1233 GoalSet unit_field_goals_;
1234 DistanceFieldProduct unit_field_product_;
1237 FieldProductCache unit_field_product_cache_;
1238 WeightedPathBatchScratch weighted_batch_;
1239 WeightedPortalSegmentCache portal_segment_cache_;
1243 WeightedPortalRouteProduct portal_replan_product_;
1244 PathRuntimeStats stats_;
1245 std::size_t world_changes_since_clear_ = 0;
1246 std::size_t cache_clears_ = 0;
1249 std::uintptr_t bound_unit_class_ = 0;
1250 std::size_t class_cache_invalidations_ = 0;
1251 std::uint64_t generation_ = 0;