3#define TESS_PATH_PATH_H_INCLUDED 1
5#include <tess/core/shape.h>
6#include <tess/core/tag_identity.h>
7#include <tess/diagnostics/diagnostics.h>
8#include <tess/path/detail/portal_memo.h>
9#include <tess/path/node_index_space.h>
10#include <tess/path/path_view.h>
11#include <tess/path/request.h>
12#include <tess/topology/movement_class.h>
13#include <tess/topology/transition_model.h>
31enum class PathStatus : std::uint8_t {
54static_assert(
sizeof(PathStatus) ==
sizeof(std::uint8_t));
61 PathStatus status = PathStatus::NotComputed;
62 std::uint32_t cost = 0;
63 std::size_t expanded_nodes = 0;
64 std::size_t reached_nodes = 0;
66 std::uint32_t cost_scale = 1;
71 PathStatus status = PathStatus::NotComputed;
72 std::size_t expanded_nodes = 0;
73 std::size_t reached_nodes = 0;
79 std::size_t requests = 0;
80 std::size_t unique_goals = 0;
81 std::size_t field_builds = 0;
82 std::size_t astar_fallbacks = 0;
83 std::size_t path_nodes = 0;
102template <
typename World,
typename Class, std::u
int32_t MaxCost>
103[[nodiscard]]
auto weighted_path_batch(
104 const World& world, std::span<const PathRequest> requests,
106 MissingChunkPolicy policy = MissingChunkPolicy::ReportIndeterminate)
107 -> std::span<const PathResult>;
110template <
typename World,
typename Class, std::uint32_t MaxCost,
112[[nodiscard]]
auto weighted_path_batch(
const World& world,
113 std::span<const PathRequest> requests,
115 MissingChunkPolicy policy,
116 const Provider& provider)
117 -> std::span<const PathResult>;
123template <
typename World,
typename Class>
124[[nodiscard]]
auto weighted_distance_field_path_core(
128template <
typename World,
typename Class,
typename Prov
ider>
129[[nodiscard]]
auto weighted_distance_field_path_core(
131 bool verify_residency,
const Provider& provider) ->
PathResult;
138template <
typename World,
typename Class, std::u
int32_t MaxCost>
139[[nodiscard]]
auto build_bounded_weighted_distance_field_core(
141 MissingChunkPolicy policy, std::span<const std::uint64_t> settle_targets)
151template <
typename World,
typename Tag>
152[[nodiscard]]
auto astar_path(
153 const World& world, PathRequest request, PathScratch& scratch,
154 MissingChunkPolicy policy = MissingChunkPolicy::ReportIndeterminate)
158template <
typename World,
typename Tag,
typename Prov
ider>
159[[nodiscard]]
auto astar_path(
const World& world, PathRequest request,
160 PathScratch& scratch, MissingChunkPolicy policy,
161 const Provider& provider) -> PathResult;
166template <
typename World,
typename Class>
167[[nodiscard]]
auto weighted_astar_path(
168 const World& world, PathRequest request, PathScratch& scratch,
169 MissingChunkPolicy policy = MissingChunkPolicy::ReportIndeterminate)
173template <
typename World,
typename Class>
174[[nodiscard]]
auto weighted_astar_path(
175 const World& world, PathRequest request, PathScratch& scratch,
176 PathTieBreak tie_break,
177 MissingChunkPolicy policy = MissingChunkPolicy::ReportIndeterminate)
181template <
typename World,
typename Class,
typename Prov
ider>
182[[nodiscard]]
auto weighted_astar_path(
const World& world, PathRequest request,
183 PathScratch& scratch,
184 MissingChunkPolicy policy,
185 const Provider& provider) -> PathResult;
188template <
typename World,
typename Class,
typename Prov
ider>
189[[nodiscard]]
auto weighted_astar_path(
const World& world, PathRequest request,
190 PathScratch& scratch,
191 MissingChunkPolicy policy,
192 const Provider& provider,
193 PathTieBreak tie_break) -> PathResult;
196template <
typename World,
typename Tag>
197[[nodiscard]]
auto build_distance_field_product(
const World& world,
198 const GoalSet& goals,
199 DistanceFieldProduct& product,
200 DistanceFieldScratch& scratch)
201 -> DistanceFieldResult;
204template <
typename World,
typename Tag,
typename Prov
ider>
205[[nodiscard]]
auto build_distance_field_product(
const World& world,
206 const GoalSet& goals,
207 DistanceFieldProduct& product,
208 DistanceFieldScratch& scratch,
209 const Provider& provider)
210 -> DistanceFieldResult;
213template <
typename World,
typename Tag>
214[[nodiscard]]
auto distance_field_product_path(
215 const World& world, Coord3 start,
const DistanceFieldProduct& product,
216 DistanceFieldScratch& scratch) -> PathResult;
219template <
typename World,
typename Tag,
typename Prov
ider>
220[[nodiscard]]
auto distance_field_product_path(
221 const World& world, Coord3 start,
const DistanceFieldProduct& product,
222 DistanceFieldScratch& scratch,
const Provider& provider) -> PathResult;
225template <
typename World,
typename Tag>
226[[nodiscard]]
auto nearest_target(
const World& world, Coord3 start,
227 const DistanceFieldProduct& product,
228 DistanceFieldScratch& scratch)
229 -> NearestTargetResult;
235template <
typename WorldType,
typename Tag>
236[[nodiscard]]
auto build_distance_field(
237 const WorldType& world, Coord3 goal, DistanceFieldScratch& scratch,
238 MissingChunkPolicy policy = MissingChunkPolicy::ReportIndeterminate)
239 -> DistanceFieldResult;
242template <
typename WorldType,
typename Tag,
typename Prov
ider>
243[[nodiscard]]
auto build_distance_field(
const WorldType& world, Coord3 goal,
244 DistanceFieldScratch& scratch,
245 MissingChunkPolicy policy,
246 const Provider& provider)
247 -> DistanceFieldResult;
253template <
typename WorldType,
typename Class>
254[[nodiscard]]
auto build_weighted_distance_field(
255 const WorldType& world, Coord3 goal, DistanceFieldScratch& scratch,
256 MissingChunkPolicy policy = MissingChunkPolicy::ReportIndeterminate)
257 -> DistanceFieldResult;
260template <
typename WorldType,
typename Class,
typename Prov
ider>
261[[nodiscard]]
auto build_weighted_distance_field(
const WorldType& world,
263 DistanceFieldScratch& scratch,
264 MissingChunkPolicy policy,
265 const Provider& provider)
266 -> DistanceFieldResult;
269template <
typename World,
typename Class>
270[[nodiscard]]
auto build_weighted_distance_field_in_box(
271 const World& world, Coord3 goal, Box3 domain, DistanceFieldScratch& scratch,
272 MissingChunkPolicy policy = MissingChunkPolicy::ReportIndeterminate)
273 -> DistanceFieldResult;
276template <
typename World,
typename Class,
typename Prov
ider>
277[[nodiscard]]
auto build_weighted_distance_field_in_box(
278 const World& world, Coord3 goal, Box3 domain, DistanceFieldScratch& scratch,
279 MissingChunkPolicy policy,
const Provider& provider) -> DistanceFieldResult;
282template <
typename World,
typename Class, std::u
int32_t MaxCost>
283[[nodiscard]]
auto build_bounded_weighted_distance_field(
284 const World& world, Coord3 goal, DistanceFieldScratch& scratch,
285 MissingChunkPolicy policy = MissingChunkPolicy::ReportIndeterminate)
286 -> DistanceFieldResult;
289template <
typename World,
typename Class, std::uint32_t MaxCost,
291[[nodiscard]]
auto build_bounded_weighted_distance_field(
292 const World& world, Coord3 goal, DistanceFieldScratch& scratch,
293 MissingChunkPolicy policy,
const Provider& provider) -> DistanceFieldResult;
305 void reserve(std::size_t count) { chunks_.reserve(count); }
307 void clear() noexcept { chunks_.clear(); }
309 template <
typename World>
310 void capture_all(
const World& world) {
312 chunks_.reserve(
static_cast<std::size_t
>(World::chunk_count));
315 for (std::uint64_t i = 0; i < World::chunk_count; ++i) {
316 chunks_.push_back(ContentVersionDependency{
317 ChunkKey{i}, world.meta(ChunkKey{i}).content_version});
323 template <
typename World>
324 void add_chunk_unique(
const World& world, ChunkKey key) {
329 template <
typename World>
330 void add_chunk(
const World& world, ChunkKey key) {
331 const auto content_version = world.meta(key).content_version;
334 if (!chunks_.empty() && chunks_.back().key == key) {
335 chunks_.back().content_version = content_version;
338 for (
auto& chunk : chunks_) {
339 if (chunk.key == key) {
340 chunk.content_version = content_version;
347 template <
typename World>
348 [[nodiscard]]
auto is_valid(
const World& world)
const noexcept ->
bool {
349 for (
const auto chunk : chunks_) {
350 if (world.meta(chunk.key).content_version != chunk.content_version) {
357 [[nodiscard]]
auto size() const noexcept -> std::
size_t {
358 return chunks_.size();
361 [[nodiscard]]
auto empty() const noexcept ->
bool {
return chunks_.empty(); }
363 [[nodiscard]]
auto chunks() const noexcept
369 std::vector<ContentVersionDependency> chunks_;
385template <
typename Shape,
typename World>
386void capture_failure_dependencies(
const World& world, PathRequest request,
388 ContentVersionDependencies& dependencies) {
389 if (status == PathStatus::NoPath || status == PathStatus::NoCandidate ||
390 status == PathStatus::CostOverflow) {
391 dependencies.capture_all(world);
394 if (contains<Shape>(request.start)) {
395 dependencies.add_chunk(world,
396 chunk_key<Shape>(tile_key<Shape>(request.start)));
398 if (contains<Shape>(request.goal)) {
399 dependencies.add_chunk(world,
400 chunk_key<Shape>(tile_key<Shape>(request.goal)));
411 void reserve_path_nodes(std::size_t node_count) { path_.reserve(node_count); }
416 void reserve_dependencies(std::size_t count) { dependencies_.reserve(count); }
418 void clear()
noexcept {
420 status_ = PathStatus::NotComputed;
425 dependencies_.clear();
432 template <
typename World>
433 [[nodiscard]]
auto is_valid(
const World& world)
const noexcept ->
bool {
434 return !dependencies_.empty() && dependencies_.is_valid(world);
437 [[nodiscard]]
auto request()
const noexcept ->
PathRequest {
441 [[nodiscard]]
auto dependencies()
const noexcept
442 -> std::span<const ContentVersionDependencies::ContentVersionDependency> {
443 return dependencies_.chunks();
447 template <
typename World,
typename Class>
454 template <
typename World>
460 PathStatus status_ = PathStatus::NotComputed;
461 std::uint32_t cost_ = 0;
462 std::size_t expanded_nodes_ = 0;
463 std::size_t reached_nodes_ = 0;
464 std::vector<Coord3> path_;
475template <
typename World,
typename PassableTag>
476[[nodiscard]]
auto select_chunk_portal_waypoints(
488 void reserve_waypoints(std::size_t count) {
489 waypoints_.reserve(count);
490 candidate_waypoints_.reserve(count);
491 best_waypoints_.reserve(count);
494 void reserve_path_nodes(std::size_t node_count) {
495 path_.reserve(node_count);
496 segment_.reserve(node_count);
502 void reserve_dependencies(std::size_t count) { dependencies_.reserve(count); }
504 void clear()
noexcept {
506 status_ = PathStatus::NotComputed;
510 route_candidates_ = 0;
511 portal_scan_tiles_ = 0;
513 candidate_waypoints_.clear();
514 best_waypoints_.clear();
517 dependencies_.clear();
522 template <
typename World>
523 [[nodiscard]]
auto is_valid(
const World& world)
const noexcept ->
bool {
524 return !dependencies_.empty() && dependencies_.is_valid(world);
527 [[nodiscard]]
auto request()
const noexcept ->
PathRequest {
531 [[nodiscard]]
auto waypoints()
const noexcept -> std::span<const Coord3> {
535 [[nodiscard]]
auto dependencies()
const noexcept
536 -> std::span<const ContentVersionDependencies::ContentVersionDependency> {
537 return dependencies_.chunks();
540 [[nodiscard]]
auto route_candidates()
const noexcept -> std::size_t {
541 return route_candidates_;
544 [[nodiscard]]
auto portal_scan_tiles()
const noexcept -> std::size_t {
545 return portal_scan_tiles_;
549 template <
typename World,
typename Class>
552 std::span<const Coord3> waypoints,
PathScratch& scratch,
555 template <
typename World,
typename Class>
558 std::span<const Coord3> waypoints,
PathScratch& scratch,
562 template <
typename World,
typename Class>
567 template <
typename World,
typename Class>
573 template <
typename World,
typename PassableTag>
574 friend auto detail::select_chunk_portal_waypoints(
578 template <
typename World>
587 [[nodiscard]]
auto stash_if_owned(std::span<const Coord3> input,
588 std::vector<Coord3>& stash)
const
589 -> std::span<const Coord3> {
590 const auto owned = [&](
const std::vector<Coord3>& storage) {
591 const auto* begin = storage.data();
592 const auto* end = begin + storage.size();
593 return !input.empty() &&
594 !std::less<const Coord3*>{}(input.data(), begin) &&
595 std::less<const Coord3*>{}(input.data(), end);
597 if (owned(waypoints_) || owned(path_) || owned(segment_) ||
598 owned(candidate_waypoints_) || owned(best_waypoints_)) {
599 stash.assign(input.begin(), input.end());
600 return std::span<const Coord3>{stash};
606 PathStatus status_ = PathStatus::NotComputed;
607 std::uint32_t cost_ = 0;
608 std::size_t expanded_nodes_ = 0;
609 std::size_t reached_nodes_ = 0;
610 std::size_t route_candidates_ = 0;
611 std::size_t portal_scan_tiles_ = 0;
612 std::vector<Coord3> waypoints_;
613 std::vector<Coord3> candidate_waypoints_;
614 std::vector<Coord3> best_waypoints_;
615 std::vector<Coord3> path_;
616 std::vector<Coord3> segment_;
648template <
typename World,
typename Tag>
649[[nodiscard]]
auto cached_astar_path(
652 MissingChunkPolicy policy = MissingChunkPolicy::ReportIndeterminate)
655template <
typename World,
typename Tag,
typename Prov
ider>
656[[nodiscard]]
auto cached_astar_path(
659 MissingChunkPolicy policy = MissingChunkPolicy::ReportIndeterminate)
672struct PackedOpenNode {
673 std::uint64_t key = 0;
674 std::uint64_t index = 0;
676 [[nodiscard]]
static constexpr auto mix_tie(std::uint64_t value)
noexcept
678 value = (value ^ (value >> 30U)) * 0xbf58476d1ce4e5b9ULL;
679 value = (value ^ (value >> 27U)) * 0x94d049bb133111ebULL;
680 return value ^ (value >> 31U);
683 [[nodiscard]]
static constexpr auto unmix_tie(std::uint64_t value)
noexcept
685 value ^= value >> 31U;
686 value ^= value >> 62U;
687 value *= 0x319642b2d24d8ec3ULL;
688 value ^= value >> 27U;
689 value ^= value >> 54U;
690 value *= 0x96de1b173f119089ULL;
691 value ^= value >> 30U;
692 return value ^ (value >> 60U);
695 [[nodiscard]]
static constexpr auto make(std::uint64_t index, std::uint32_t g,
697 PathTieBreak tie_break = {})
noexcept
699 return PackedOpenNode{
700 (
static_cast<std::uint64_t
>(f) << 32u) |
701 (std::numeric_limits<std::uint32_t>::max() - g),
702 tie_break.seed == 0 ? index : mix_tie(index ^ tie_break.seed)};
705 [[nodiscard]]
constexpr auto node_index(
706 PathTieBreak tie_break = {})
const noexcept -> std::uint64_t {
707 return tie_break.seed == 0 ? index : unmix_tie(index) ^ tie_break.seed;
710 [[nodiscard]]
constexpr auto g() const noexcept -> std::uint32_t {
711 return std::numeric_limits<std::uint32_t>::max() -
712 static_cast<std::uint32_t
>(key);
715 [[nodiscard]]
constexpr auto f() const noexcept -> std::uint32_t {
716 return static_cast<std::uint32_t
>(key >> 32u);
720[[nodiscard]]
constexpr bool packed_open_node_less(
721 PackedOpenNode lhs, PackedOpenNode rhs)
noexcept {
722 if (lhs.key != rhs.key) {
723 return lhs.key > rhs.key;
725 return lhs.index > rhs.index;
728static_assert(PackedOpenNode::unmix_tie(PackedOpenNode::mix_tie(0)) == 0);
729static_assert(PackedOpenNode::unmix_tie(PackedOpenNode::mix_tie(1)) == 1);
730static_assert(PackedOpenNode::unmix_tie(PackedOpenNode::mix_tie(
731 std::numeric_limits<std::uint64_t>::max())) ==
732 std::numeric_limits<std::uint64_t>::max());
733static_assert(
sizeof(PackedOpenNode) == 2 *
sizeof(std::uint64_t));
744 std::uint64_t index = 0;
749 void reserve_nodes(std::size_t node_count) {
750 open_.reserve(node_count);
751 open_next_.reserve(node_count);
752 generation_.reserve(node_count);
753 state_.reserve(node_count);
754 g_.reserve(node_count);
755 parent_.reserve(node_count);
756 path_.reserve(node_count);
759 void clear() noexcept {
767 [[nodiscard]]
auto capacity_nodes() const noexcept -> std::
size_t {
768 return state_.capacity();
772 template <
typename World,
typename Tag>
777 template <
typename World,
typename Tag,
typename Prov
ider>
782 template <
typename World,
typename Class>
787 template <
typename World,
typename Class,
typename Prov
ider>
790 MissingChunkPolicy policy,
793 template <
typename World,
typename Class,
typename Prov
ider>
796 MissingChunkPolicy policy,
797 const Provider& provider,
800 template <
typename World,
typename Class>
805 template <
typename World,
typename Tag>
810 template <
typename World,
typename Tag,
typename Prov
ider>
813 const Provider& provider,
816 void advance_epoch() noexcept {
819 std::fill(generation_.begin(), generation_.end(), 0);
824 [[nodiscard]]
auto is_current(std::size_t offset)
const noexcept ->
bool {
825 return generation_[offset] == epoch_;
828 [[nodiscard]]
auto state_at(std::size_t offset,
829 std::uint8_t unseen)
const noexcept
831 return is_current(offset) ? state_[offset] : unseen;
834 [[nodiscard]]
auto g_at(std::size_t offset,
835 std::uint32_t infinite_cost)
const noexcept
837 return is_current(offset) ? g_[offset] : infinite_cost;
842 void touch_node(std::size_t offset) {
843 generation_[offset] = epoch_;
847 std::vector<detail::PackedOpenNode> open_;
848 std::vector<detail::PackedOpenNode> open_next_;
854 std::vector<std::uint32_t> generation_;
855 std::uint32_t epoch_ = 1;
856 std::vector<std::uint8_t> state_;
857 std::vector<std::uint32_t> g_;
858 std::vector<std::uint64_t> parent_;
862 std::size_t touched_count_ = 0;
863 std::vector<Coord3> path_;
874 void reserve_nodes(std::size_t node_count) {
875 frontier_.reserve(node_count);
876 weighted_frontier_.reserve(node_count);
877 weighted_bucket_capacity_ = node_count / 8u + 1u;
878 for (
auto& bucket : weighted_buckets_) {
879 bucket.reserve(weighted_bucket_capacity_);
881 generation_.reserve(node_count);
882 distance_.reserve(node_count);
883 target_generation_.reserve(node_count);
884 touched_.reserve(node_count);
885 path_.reserve(node_count);
888 [[nodiscard]]
auto capacity_nodes()
const noexcept -> std::size_t {
889 return distance_.capacity();
893 template <
typename WorldType,
typename Tag>
896 MissingChunkPolicy policy)
899 template <
typename World,
typename Class,
typename Prov
ider>
905 template <
typename World,
typename Tag>
910 template <
typename WorldType,
typename Class>
913 MissingChunkPolicy policy)
916 template <
typename WorldType,
typename Class,
typename Prov
ider>
919 MissingChunkPolicy policy,
920 const Provider& provider)
923 template <
typename World,
typename Class>
929 template <
typename World,
typename Class, std::u
int32_t MaxCost>
930 friend auto detail::build_bounded_weighted_distance_field_core(
932 MissingChunkPolicy policy, std::span<const std::uint64_t> settle_targets)
937 template <
typename World,
typename Class>
938 friend auto detail::weighted_distance_field_path_core(
942 template <
typename World,
typename Class,
typename Prov
ider>
943 friend auto detail::weighted_distance_field_path_core(
945 bool verify_residency,
const Provider& provider) ->
PathResult;
949 template <
typename World,
typename Class, std::u
int32_t MaxCost>
951 std::span<const PathRequest> requests,
953 MissingChunkPolicy policy)
954 -> std::span<const PathResult>;
956 template <
typename World,
typename Class, std::uint32_t MaxCost,
959 std::span<const PathRequest> requests,
961 MissingChunkPolicy policy,
962 const Provider& provider)
963 -> std::span<const PathResult>;
965 template <
typename World,
typename Tag>
972 template <
typename World,
typename Tag,
typename Prov
ider>
977 const Provider& provider)
980 template <
typename World,
typename Tag>
986 template <
typename World,
typename Tag,
typename Prov
ider>
990 const Provider& provider)
993 template <
typename World,
typename Tag>
999 template <
typename World,
typename Tag,
typename Prov
ider>
1005 template <
typename World,
typename Class,
typename Prov
ider>
1011 template <
typename World,
typename Class,
typename Prov
ider>
1016 void clear_build() noexcept {
1019 weighted_frontier_.clear();
1020 for (
auto& bucket : weighted_buckets_) {
1029 build_status_ = PathStatus::NotComputed;
1030 model_class_identity_ = 0;
1031 model_provider_identity_ = 0;
1032 model_provider_instance_identity_ =
nullptr;
1033 model_provider_revision_ = 0;
1040 void discard_build_result() noexcept {
1042 build_status_ = PathStatus::NotComputed;
1043 model_class_identity_ = 0;
1044 model_provider_identity_ = 0;
1045 model_provider_instance_identity_ =
nullptr;
1046 model_provider_revision_ = 0;
1049 void clear_path() noexcept { path_.clear(); }
1051 void publish_build_status(PathStatus status)
noexcept {
1052 build_status_ = status;
1055 [[nodiscard]]
auto unresolved_path_status() const noexcept -> PathStatus {
1056 if (build_status_ == PathStatus::Indeterminate) {
1057 return PathStatus::Indeterminate;
1059 return build_status_ == PathStatus::Found ? PathStatus::NoPath
1060 : PathStatus::NotComputed;
1063 void advance_epoch() noexcept {
1066 std::fill(generation_.begin(), generation_.end(), 0);
1067 std::fill(target_generation_.begin(), target_generation_.end(), 0);
1072 [[nodiscard]]
auto is_current(std::size_t offset)
const noexcept ->
bool {
1073 return generation_[offset] == epoch_;
1076 [[nodiscard]]
auto distance_at(std::size_t offset,
1077 std::uint32_t infinite_distance)
const noexcept
1079 return is_current(offset) ? distance_[offset] : infinite_distance;
1082 void touch_node(std::size_t offset, std::uint64_t index) {
1083 generation_[offset] = epoch_;
1084 touched_.push_back(index);
1089 void touch_node(std::uint64_t index) {
1090 touch_node(
static_cast<std::size_t
>(index), index);
1096 void mark_settle_target(std::size_t offset) {
1097 target_generation_[offset] = epoch_;
1100 [[nodiscard]]
auto is_settle_target(std::size_t offset)
const noexcept
1102 return target_generation_[offset] == epoch_;
1105 std::vector<std::uint64_t> frontier_;
1106 std::vector<detail::PackedOpenNode> weighted_frontier_;
1107 std::vector<std::vector<std::uint64_t>> weighted_buckets_;
1108 std::size_t weighted_bucket_capacity_ = 0;
1109 std::vector<std::uint32_t> generation_;
1110 std::uint32_t epoch_ = 1;
1111 std::vector<std::uint32_t> distance_;
1112 std::vector<std::uint32_t> target_generation_;
1113 std::vector<std::uint64_t> touched_;
1114 std::vector<Coord3> path_;
1117 std::vector<std::uint8_t> chunk_seen_;
1119 bool has_goal_ =
false;
1120 PathStatus build_status_ = PathStatus::NotComputed;
1121 std::uint64_t residency_fingerprint_ = 0;
1134 template <
typename World>
1135 void stamp_residency(
const World& world)
noexcept {
1136 if constexpr (!std::is_same_v<
typename World::residency_type,
1138 residency_fingerprint_ = world.residency_fingerprint();
1141 template <
typename World>
1142 [[nodiscard]]
auto residency_matches(
const World& world)
const noexcept
1144 if constexpr (std::is_same_v<
typename World::residency_type,
1148 return residency_fingerprint_ == world.residency_fingerprint();
1152 template <
typename Model>
1153 void stamp_model(
const Model& model = Model{},
1154 const void* provider_instance =
nullptr)
noexcept {
1155 model_class_identity_ = detail::tag_identity<typename Model::class_type>();
1156 model_lattice_identity_ =
1157 static_cast<std::uint32_t
>(Model::lattice_identity);
1158 model_lattice_version_ = Model::lattice_version;
1159 model_step_identity_ =
1160 static_cast<std::uint32_t
>(Model::step_policy_identity);
1161 model_cost_scale_ = Model::cost_scale;
1162 model_provider_identity_ =
1163 detail::tag_identity<typename Model::provider_type>();
1164 model_provider_instance_identity_ = provider_instance;
1165 model_provider_revision_ = model.revision();
1168 template <
typename Model>
1169 [[nodiscard]]
auto model_matches(
1170 const Model& model = Model{},
1171 const void* provider_instance =
nullptr)
const noexcept ->
bool {
1172 return model_class_identity_ ==
1173 detail::tag_identity<typename Model::class_type>() &&
1174 model_lattice_identity_ ==
1175 static_cast<std::uint32_t
>(Model::lattice_identity) &&
1176 model_lattice_version_ == Model::lattice_version &&
1177 model_step_identity_ ==
1178 static_cast<std::uint32_t
>(Model::step_policy_identity) &&
1179 model_cost_scale_ == Model::cost_scale &&
1180 model_provider_identity_ ==
1181 detail::tag_identity<typename Model::provider_type>() &&
1182 model_provider_instance_identity_ == provider_instance &&
1183 model_provider_revision_ == model.revision();
1186 std::uintptr_t model_class_identity_ = 0;
1187 std::uint32_t model_lattice_identity_ = 0;
1188 std::uint32_t model_lattice_version_ = 0;
1189 std::uint32_t model_step_identity_ = 0;
1190 std::uint32_t model_cost_scale_ = 0;
1191 std::uintptr_t model_provider_identity_ = 0;
1192 const void* model_provider_instance_identity_ =
nullptr;
1193 std::uint64_t model_provider_revision_ = 0;
1202 void reserve_requests(std::size_t request_count) {
1203 results_.reserve(request_count);
1204 offsets_.reserve(request_count);
1205 sizes_.reserve(request_count);
1206 processed_.reserve(request_count);
1207 request_goal_.reserve(request_count);
1208 goal_coords_.reserve(request_count);
1209 goal_counts_.reserve(request_count);
1212 void reserve_path_nodes(std::size_t node_count) {
1213 paths_.reserve(node_count);
1216 void reserve_search_nodes(std::size_t node_count) {
1217 field_scratch_.reserve_nodes(node_count);
1218 astar_scratch_.reserve_nodes(node_count);
1221 void clear()
noexcept {
1235 template <
typename World,
typename Class, std::u
int32_t MaxCost>
1237 std::span<const PathRequest> requests,
1239 MissingChunkPolicy policy)
1240 -> std::span<const PathResult>;
1242 template <
typename World,
typename Class, std::uint32_t MaxCost,
1245 std::span<const PathRequest> requests,
1247 MissingChunkPolicy policy,
1248 const Provider& provider)
1249 -> std::span<const PathResult>;
1253 std::vector<PathResult> results_;
1254 std::vector<std::size_t> offsets_;
1255 std::vector<std::size_t> sizes_;
1256 std::vector<std::uint8_t> processed_;
1257 std::vector<Coord3> paths_;
1260 std::vector<std::uint32_t> goal_slots_;
1261 std::vector<Coord3> goal_coords_;
1262 std::vector<std::uint32_t> goal_counts_;
1263 std::vector<std::uint32_t> request_goal_;
1268 std::vector<std::uint32_t> group_offsets_;
1269 std::vector<std::uint32_t> group_cursors_;
1270 std::vector<std::uint32_t> group_members_;
1273 std::vector<std::uint64_t> settle_targets_;
1279enum class Axis : std::uint8_t {
1285struct PortalRouteCandidate {
1287 std::uint32_t score = 0;
1288 std::size_t scan_tiles = 0;
1295[[nodiscard]]
constexpr auto coord_hash(Coord3 coord)
noexcept
1297 auto hash = std::uint64_t{0xcbf29ce484222325ull};
1298 hash = (hash ^
static_cast<std::uint64_t
>(coord.x)) * 0x100000001b3ull;
1299 hash = (hash ^
static_cast<std::uint64_t
>(coord.y)) * 0x100000001b3ull;
1300 hash = (hash ^
static_cast<std::uint64_t
>(coord.z)) * 0x100000001b3ull;
1301 hash = (hash ^ (hash >> 30u)) * 0xbf58476d1ce4e5b9ull;
1302 hash = (hash ^ (hash >> 27u)) * 0x94d049bb133111ebull;
1303 return hash ^ (hash >> 31u);
1306[[nodiscard]]
constexpr auto manhattan(Coord3 lhs, Coord3 rhs)
noexcept
1308 const auto distance = manhattan_distance(lhs, rhs);
1309 if (distance > std::numeric_limits<std::uint32_t>::max()) {
1310 return std::numeric_limits<std::uint32_t>::max();
1312 return static_cast<std::uint32_t
>(distance);
1315template <
typename World>
1316[[nodiscard]]
constexpr auto tile_count() noexcept -> std::
size_t {
1317 static_assert(World::chunk_count <= std::numeric_limits<std::size_t>::max() /
1318 World::local_tile_count);
1319 return static_cast<std::size_t
>(World::chunk_count * World::local_tile_count);
1322template <
typename Shape>
1323[[nodiscard]]
constexpr auto tile_index(Coord3 coord)
noexcept
1325 static_assert(ShapeTraits<Shape>::tile_key_bits <= 64,
1326 "pathfinding requires shapes with at most 64-bit tile keys");
1327 return static_cast<std::uint64_t
>(tile_key<Shape>(coord).value);
1330template <
typename Shape>
1331[[nodiscard]]
constexpr auto tile_coord(std::uint64_t index)
noexcept
1333 using Storage =
typename ShapeTraits<Shape>::TileKeyStorage;
1334 return coord<Shape>(TileKey<Shape>{
static_cast<Storage
>(index)});
1341template <
typename World,
typename ClassOrTag>
1342[[nodiscard]]
auto is_passable(
const World& world, Coord3 coord)
noexcept
1344 using Class = movement::movement_class_of<ClassOrTag>;
1345 const auto resolved = world.try_resolve(coord);
1346 if (!resolved.has_value()) {
1349 if constexpr (std::is_same_v<
typename World::residency_type,
1351 const auto* page = world.try_chunk(resolved->chunk_key);
1352 return page !=
nullptr && Class::passable(*page, resolved->local_tile_id);
1354 return Class::passable(world.chunk(resolved->chunk_key),
1355 resolved->local_tile_id);
1359template <
typename World,
typename ClassOrTag>
1360[[nodiscard]]
auto is_passable_index(
const World& world,
1361 std::uint64_t index)
noexcept ->
bool {
1362 using Class = movement::movement_class_of<ClassOrTag>;
1363 using Shape =
typename World::shape_type;
1364 using Storage =
typename ShapeTraits<Shape>::TileKeyStorage;
1365 const auto key = TileKey<Shape>{
static_cast<Storage
>(index)};
1366 if constexpr (std::is_same_v<
typename World::residency_type,
1371 const auto* page = world.try_chunk(chunk_key<Shape>(key));
1372 if (page ==
nullptr) {
1375 return Class::passable(*page, local_tile_id<Shape>(key));
1377 return Class::passable(world.chunk(chunk_key<Shape>(key)),
1378 local_tile_id<Shape>(key));
1384template <
typename World,
typename Class>
1385[[nodiscard]]
auto tile_entry_cost_index(
const World& world,
1386 std::uint64_t index)
noexcept
1388 static_assert(std::derived_from<Class, movement::movement_class_tag>,
1389 "tile_entry_cost_index requires a MovementClass; pass an "
1390 "explicit MovementClass.");
1391 using Shape =
typename World::shape_type;
1392 using Storage =
typename ShapeTraits<Shape>::TileKeyStorage;
1393 const auto key = TileKey<Shape>{
static_cast<Storage
>(index)};
1394 TESS_DIAG_EVENT(path_cost_read);
1395 return Class::entry_cost(world.chunk(chunk_key<Shape>(key)),
1396 local_tile_id<Shape>(key));
1399[[nodiscard]]
constexpr auto saturating_add(std::uint32_t lhs,
1400 std::uint32_t rhs)
noexcept
1402 if (rhs > std::numeric_limits<std::uint32_t>::max() - lhs) {
1403 return std::numeric_limits<std::uint32_t>::max();
1408template <
typename World,
typename Tag>
1409[[nodiscard]]
auto is_full_axis_barrier(
const World& world, Coord3 blocked,
1410 Axis axis)
noexcept ->
bool {
1411 using Shape =
typename World::shape_type;
1412 constexpr auto size = ShapeTraits<Shape>::size;
1414 if (axis == Axis::X) {
1415 for (std::int64_t z = 0; z < static_cast<std::int64_t>(size.z); ++z) {
1416 for (std::int64_t y = 0; y < static_cast<std::int64_t>(size.y); ++y) {
1417 const auto coord = Coord3{blocked.x, y, z};
1418 TESS_DIAG_EVENT(path_passability_check);
1419 if (is_passable_index<World, Tag>(world, tile_index<Shape>(coord))) {
1427 if (axis == Axis::Y) {
1428 for (std::int64_t z = 0; z < static_cast<std::int64_t>(size.z); ++z) {
1429 for (std::int64_t x = 0; x < static_cast<std::int64_t>(size.x); ++x) {
1430 const auto coord = Coord3{x, blocked.y, z};
1431 TESS_DIAG_EVENT(path_passability_check);
1432 if (is_passable_index<World, Tag>(world, tile_index<Shape>(coord))) {
1440 for (std::int64_t y = 0; y < static_cast<std::int64_t>(size.y); ++y) {
1441 for (std::int64_t x = 0; x < static_cast<std::int64_t>(size.x); ++x) {
1442 const auto coord = Coord3{x, y, blocked.z};
1443 TESS_DIAG_EVENT(path_passability_check);
1444 if (is_passable_index<World, Tag>(world, tile_index<Shape>(coord))) {
1452template <
typename Shape>
1453[[nodiscard]]
constexpr auto chunk_origin(ChunkCoord3 chunk)
noexcept
1455 constexpr auto size = ShapeTraits<Shape>::chunk;
1457 static_cast<std::int64_t
>(chunk.x * size.x),
1458 static_cast<std::int64_t
>(chunk.y * size.y),
1459 static_cast<std::int64_t
>(chunk.z * size.z),
1463template <
typename Shape>
1464[[nodiscard]]
constexpr auto adjacent_chunk(ChunkCoord3 from,
1465 ChunkCoord3 to)
noexcept ->
bool {
1466 const auto dx = from.x > to.x ? from.x - to.x : to.x - from.x;
1467 const auto dy = from.y > to.y ? from.y - to.y : to.y - from.y;
1468 const auto dz = from.z > to.z ? from.z - to.z : to.z - from.z;
1469 return dx + dy + dz == 1;
1472template <
typename World,
typename PassableTag>
1473[[nodiscard]]
auto best_chunk_portal(
const World& world, ChunkCoord3 from,
1474 ChunkCoord3 to, Coord3 current,
1475 Coord3 goal, Coord3& portal,
1476 std::size_t* scan_tiles =
nullptr) noexcept
1478 using Shape =
typename World::shape_type;
1479 using Class = movement::movement_class_of<PassableTag>;
1480 using Traits = ShapeTraits<Shape>;
1481 constexpr auto chunk = Traits::chunk;
1482 const auto origin = chunk_origin<Shape>(from);
1484 if (!adjacent_chunk<Shape>(from, to)) {
1489 auto best_score = std::numeric_limits<std::uint32_t>::max();
1490 const auto score_target = [&](Coord3 target) {
1492 saturating_add(manhattan(current, target), manhattan(target, goal));
1493 if (!found || score < best_score) {
1506 const auto in_grid = [](ChunkCoord3 coord) {
1507 return coord.x < Traits::chunk_count_x && coord.y < Traits::chunk_count_y &&
1508 coord.z < Traits::chunk_count_z;
1510 if (in_grid(from) && in_grid(to)) {
1511 const auto acquire = [&](ChunkCoord3 coord) {
1512 if constexpr (std::is_same_v<
typename World::residency_type,
1514 return world.try_chunk(chunk_key<Shape>(coord));
1516 return &world.chunk(chunk_key<Shape>(coord));
1519 const auto* from_page = acquire(from);
1520 const auto* to_page = acquire(to);
1521 if (from_page !=
nullptr && to_page !=
nullptr) {
1522 const auto consider_local = [&](LocalCoord3 source_local,
1523 LocalCoord3 target_local, Coord3 target) {
1524 if (scan_tiles !=
nullptr) {
1527 TESS_DIAG_EVENT(path_passability_check);
1528 if (!Class::passable(*from_page, local_tile_id<Shape>(source_local))) {
1531 TESS_DIAG_EVENT(path_passability_check);
1532 if (!Class::passable(*to_page, local_tile_id<Shape>(target_local))) {
1535 score_target(target);
1538 if (from.x != to.x) {
1539 const auto step = from.x < to.x ? std::int64_t{1} : std::int64_t{-1};
1540 const auto source_x =
1541 step > 0 ?
static_cast<std::int64_t
>(chunk.x) - 1 : std::int64_t{0};
1542 const auto target_x =
1543 step > 0 ? std::int64_t{0} :
static_cast<std::int64_t
>(chunk.x) - 1;
1544 const auto world_target_x = origin.x + source_x + step;
1545 for (std::int64_t z = 0; z < static_cast<std::int64_t>(chunk.z); ++z) {
1546 for (std::int64_t y = 0; y < static_cast<std::int64_t>(chunk.y);
1548 consider_local(LocalCoord3{
static_cast<std::uint64_t
>(source_x),
1549 static_cast<std::uint64_t
>(y),
1550 static_cast<std::uint64_t
>(z)},
1551 LocalCoord3{
static_cast<std::uint64_t
>(target_x),
1552 static_cast<std::uint64_t
>(y),
1553 static_cast<std::uint64_t
>(z)},
1554 Coord3{world_target_x, origin.y + y, origin.z + z});
1559 if (from.y != to.y) {
1560 const auto step = from.y < to.y ? std::int64_t{1} : std::int64_t{-1};
1561 const auto source_y =
1562 step > 0 ?
static_cast<std::int64_t
>(chunk.y) - 1 : std::int64_t{0};
1563 const auto target_y =
1564 step > 0 ? std::int64_t{0} :
static_cast<std::int64_t
>(chunk.y) - 1;
1565 const auto world_target_y = origin.y + source_y + step;
1566 for (std::int64_t z = 0; z < static_cast<std::int64_t>(chunk.z); ++z) {
1567 for (std::int64_t x = 0; x < static_cast<std::int64_t>(chunk.x);
1569 consider_local(LocalCoord3{
static_cast<std::uint64_t
>(x),
1570 static_cast<std::uint64_t
>(source_y),
1571 static_cast<std::uint64_t
>(z)},
1572 LocalCoord3{
static_cast<std::uint64_t
>(x),
1573 static_cast<std::uint64_t
>(target_y),
1574 static_cast<std::uint64_t
>(z)},
1575 Coord3{origin.x + x, world_target_y, origin.z + z});
1580 const auto step = from.z < to.z ? std::int64_t{1} : std::int64_t{-1};
1581 const auto source_z =
1582 step > 0 ?
static_cast<std::int64_t
>(chunk.z) - 1 : std::int64_t{0};
1583 const auto target_z =
1584 step > 0 ? std::int64_t{0} :
static_cast<std::int64_t
>(chunk.z) - 1;
1585 const auto world_target_z = origin.z + source_z + step;
1586 for (std::int64_t y = 0; y < static_cast<std::int64_t>(chunk.y); ++y) {
1587 for (std::int64_t x = 0; x < static_cast<std::int64_t>(chunk.x); ++x) {
1588 consider_local(LocalCoord3{
static_cast<std::uint64_t
>(x),
1589 static_cast<std::uint64_t
>(y),
1590 static_cast<std::uint64_t
>(source_z)},
1591 LocalCoord3{
static_cast<std::uint64_t
>(x),
1592 static_cast<std::uint64_t
>(y),
1593 static_cast<std::uint64_t
>(target_z)},
1594 Coord3{origin.x + x, origin.y + y, world_target_z});
1601 const auto consider = [&](Coord3 source, Coord3 target) {
1602 if (scan_tiles !=
nullptr) {
1605 TESS_DIAG_EVENT(path_passability_check);
1606 if (!is_passable<World, PassableTag>(world, source)) {
1609 TESS_DIAG_EVENT(path_passability_check);
1610 if (!is_passable<World, PassableTag>(world, target)) {
1613 score_target(target);
1616 if (from.x != to.x) {
1617 const auto step = from.x < to.x ? std::int64_t{1} : std::int64_t{-1};
1618 const auto source_x =
1619 step > 0 ? origin.x +
static_cast<std::int64_t
>(chunk.x) - 1 : origin.x;
1620 for (std::int64_t z = origin.z;
1621 z < origin.z +
static_cast<std::int64_t
>(chunk.z); ++z) {
1622 for (std::int64_t y = origin.y;
1623 y < origin.y +
static_cast<std::int64_t
>(chunk.y); ++y) {
1624 consider(Coord3{source_x, y, z}, Coord3{source_x + step, y, z});
1630 if (from.y != to.y) {
1631 const auto step = from.y < to.y ? std::int64_t{1} : std::int64_t{-1};
1632 const auto source_y =
1633 step > 0 ? origin.y +
static_cast<std::int64_t
>(chunk.y) - 1 : origin.y;
1634 for (std::int64_t z = origin.z;
1635 z < origin.z +
static_cast<std::int64_t
>(chunk.z); ++z) {
1636 for (std::int64_t x = origin.x;
1637 x < origin.x +
static_cast<std::int64_t
>(chunk.x); ++x) {
1638 consider(Coord3{x, source_y, z}, Coord3{x, source_y + step, z});
1644 const auto step = from.z < to.z ? std::int64_t{1} : std::int64_t{-1};
1645 const auto source_z =
1646 step > 0 ? origin.z +
static_cast<std::int64_t
>(chunk.z) - 1 : origin.z;
1647 for (std::int64_t y = origin.y;
1648 y < origin.y +
static_cast<std::int64_t
>(chunk.y); ++y) {
1649 for (std::int64_t x = origin.x;
1650 x < origin.x +
static_cast<std::int64_t
>(chunk.x); ++x) {
1651 consider(Coord3{x, y, source_z}, Coord3{x, y, source_z + step});
1676template <
typename World,
typename PassableTag>
1677[[nodiscard]]
auto memoized_chunk_portal(
const World& world, ChunkCoord3 from,
1678 ChunkCoord3 to, Coord3 current,
1679 Coord3 goal, Coord3& portal,
1680 std::size_t* scan_tiles) ->
bool {
1681 using Shape =
typename World::shape_type;
1682 auto& memo = active_portal_memo();
1683 const auto step = portal_step_code(from, to);
1684 const auto containing = chunk_coord<Shape>(current);
1685 const auto keyable = step != 0 && containing.x == from.x &&
1686 containing.y == from.y && containing.z == from.z;
1688 return best_chunk_portal<World, PassableTag>(world, from, to, current, goal,
1689 portal, scan_tiles);
1692 const auto current_index = tile_index<Shape>(current);
1693 const auto lookup = memo.probe(current_index, step);
1696 portal = tile_coord<Shape>(lookup.portal_index);
1698 return lookup.found;
1700 const auto found = best_chunk_portal<World, PassableTag>(
1701 world, from, to, current, goal, portal, scan_tiles);
1702 memo.store(lookup, current_index, step,
1703 found ? tile_index<Shape>(portal) : std::uint64_t{0}, found);
1707template <
typename World,
typename PassableTag>
1708[[nodiscard]]
auto build_chunk_portal_candidate(
const World& world,
1709 PathRequest request,
1710 std::span<const Axis> order,
1711 std::vector<Coord3>& waypoints)
1712 -> PortalRouteCandidate {
1713 using Shape =
typename World::shape_type;
1716 auto current = request.start;
1717 auto current_chunk = chunk_coord<Shape>(request.start);
1718 const auto goal_chunk = chunk_coord<Shape>(request.goal);
1719 auto result = PortalRouteCandidate{
true, 0, 0};
1721 const auto append_portal = [&](ChunkCoord3 next_chunk) {
1722 auto portal = Coord3{};
1723 if (!memoized_chunk_portal<World, PassableTag>(
1724 world, current_chunk, next_chunk, current, request.goal, portal,
1725 &result.scan_tiles)) {
1726 result.found =
false;
1729 result.score = saturating_add(result.score, manhattan(current, portal));
1730 waypoints.push_back(portal);
1732 current_chunk = next_chunk;
1736 for (
const auto axis : order) {
1737 if (axis == Axis::X) {
1738 while (current_chunk.x != goal_chunk.x) {
1739 auto next = current_chunk;
1740 if (current_chunk.x < goal_chunk.x) {
1745 if (!append_portal(next)) {
1749 }
else if (axis == Axis::Y) {
1750 while (current_chunk.y != goal_chunk.y) {
1751 auto next = current_chunk;
1752 if (current_chunk.y < goal_chunk.y) {
1757 if (!append_portal(next)) {
1762 while (current_chunk.z != goal_chunk.z) {
1763 auto next = current_chunk;
1764 if (current_chunk.z < goal_chunk.z) {
1769 if (!append_portal(next)) {
1776 result.score = saturating_add(result.score, manhattan(current, request.goal));
1780template <
typename Fn>
1781void for_each_axis_neighbor(Coord3 coord, Fn&& fn) {
1782 fn(Coord3{coord.x + 1, coord.y, coord.z});
1783 fn(Coord3{coord.x - 1, coord.y, coord.z});
1784 fn(Coord3{coord.x, coord.y + 1, coord.z});
1785 fn(Coord3{coord.x, coord.y - 1, coord.z});
1786 fn(Coord3{coord.x, coord.y, coord.z + 1});
1787 fn(Coord3{coord.x, coord.y, coord.z - 1});
1790template <
typename Shape,
typename Fn>
1796#if defined(_MSC_VER)
1798#elif defined(__GNUC__) || defined(__clang__)
1799__attribute__((always_inline))
inline void
1803for_each_indexed_axis_neighbor(Coord3 coord, std::uint64_t index, Fn&& fn) {
1804 using Traits = ShapeTraits<Shape>;
1805 constexpr auto size = Traits::size;
1806 constexpr auto chunk = Traits::chunk;
1807 constexpr auto local_bits = Traits::local_bits;
1808 constexpr auto chunk_index_stride =
1809 local_bits >= 64 ? std::uint64_t{0} : (std::uint64_t{1} << local_bits);
1810 constexpr auto chunk_y_stride = Traits::chunk_count_x * chunk_index_stride;
1812 const auto local_x =
static_cast<std::uint64_t
>(coord.x) & (chunk.x - 1);
1813 const auto local_y =
static_cast<std::uint64_t
>(coord.y) & (chunk.y - 1);
1815 if constexpr (!Traits::degenerate_x) {
1816 if (
static_cast<std::uint64_t
>(coord.x) + 1 < size.x) {
1817 const auto next_index = local_x + 1 < chunk.x
1819 : index + chunk_index_stride - local_x;
1820 fn(Coord3{coord.x + 1, coord.y, coord.z}, next_index);
1823 const auto next_index =
1824 local_x > 0 ? index - 1 : index - chunk_index_stride + (chunk.x - 1);
1825 fn(Coord3{coord.x - 1, coord.y, coord.z}, next_index);
1829 if constexpr (!Traits::degenerate_y) {
1830 if (
static_cast<std::uint64_t
>(coord.y) + 1 < size.y) {
1831 const auto next_index = local_y + 1 < chunk.y
1833 : index + chunk_y_stride - local_y * chunk.x;
1834 fn(Coord3{coord.x, coord.y + 1, coord.z}, next_index);
1837 const auto next_index =
1838 local_y > 0 ? index - chunk.x
1839 : index - chunk_y_stride + (chunk.y - 1) * chunk.x;
1840 fn(Coord3{coord.x, coord.y - 1, coord.z}, next_index);
1844 if constexpr (!Traits::degenerate_z) {
1845 constexpr auto chunk_z_stride =
1846 Traits::chunk_count_x * Traits::chunk_count_y * chunk_index_stride;
1847 const auto local_xy = chunk.x * chunk.y;
1848 const auto local_z =
static_cast<std::uint64_t
>(coord.z) & (chunk.z - 1);
1849 if (
static_cast<std::uint64_t
>(coord.z) + 1 < size.z) {
1850 const auto next_index = local_z + 1 < chunk.z
1852 : index + chunk_z_stride - local_z * local_xy;
1853 fn(Coord3{coord.x, coord.y, coord.z + 1}, next_index);
1856 const auto next_index =
1857 local_z > 0 ? index - local_xy
1858 : index - chunk_z_stride + (chunk.z - 1) * local_xy;
1859 fn(Coord3{coord.x, coord.y, coord.z - 1}, next_index);
1864[[nodiscard]]
constexpr bool open_node_less(
1865 PathScratch::OpenNode lhs, PathScratch::OpenNode rhs)
noexcept {
1866 if (lhs.f != rhs.f) {
1867 return lhs.f > rhs.f;
1869 if (lhs.g != rhs.g) {
1870 return lhs.g < rhs.g;
1872 return lhs.index > rhs.index;
1877#include <tess/path/detail/astar.h>
1882template <
typename World,
typename Class>
1888 using Shape =
typename World::shape_type;
1894 std::is_same_v<typename World::residency_type, AlwaysResident>,
1895 "build_weighted_route_product is dense-only; call weighted_astar_path "
1896 "directly for sparse worlds.");
1900 weighted_astar_path<World, Class>(world, request, scratch);
1901 product.request_ = request;
1902 product.status_ = result.status;
1903 product.cost_ = result.cost;
1904 product.expanded_nodes_ = result.expanded_nodes;
1905 product.reached_nodes_ = result.reached_nodes;
1906 product.path_.assign(result.path.begin(), result.path.end());
1907 if (result.status == PathStatus::Found) {
1908 for (
const auto coord : product.path_) {
1909 const auto key = tile_key<Shape>(coord);
1910 product.dependencies_.add_chunk(world, chunk_key<Shape>(key));
1915 detail::capture_failure_dependencies<Shape>(world, request, result.status,
1916 product.dependencies_);
1919 return PathResult{product.status_, product.cost_, product.expanded_nodes_,
1920 product.reached_nodes_, product.path_};
1924template <
typename World>
1927 if (!product.is_valid(world)) {
1928 return PathResult{PathStatus::NotComputed, 0, 0, 0, {}};
1930 return PathResult{product.status_, product.cost_, 0, 0, product.path_};
1937template <
typename World,
typename Class>
1939 const World& world,
PathRequest request, std::span<const Coord3> waypoints,
1941 using Shape =
typename World::shape_type;
1945 static_assert(std::is_same_v<typename World::residency_type, AlwaysResident>,
1946 "build_weighted_portal_route_product is dense-only; chain "
1947 "weighted_astar_path directly for sparse worlds.");
1949 std::vector<Coord3> stash;
1950 const auto source = product.stash_if_owned(waypoints, stash);
1953 product.request_ = request;
1954 product.waypoints_.assign(source.begin(), source.end());
1956 auto from = request.start;
1957 auto total_cost = std::uint64_t{0};
1958 auto total_expanded = std::size_t{0};
1959 auto total_reached = std::size_t{0};
1960 auto append_segment = [&](
PathRequest segment_request) {
1962 weighted_astar_path<World, Class>(world, segment_request, scratch);
1963 total_expanded += result.expanded_nodes;
1964 total_reached += result.reached_nodes;
1965 if (result.status != PathStatus::Found) {
1966 product.path_.clear();
1967 product.status_ = result.status;
1968 product.expanded_nodes_ = total_expanded;
1969 product.reached_nodes_ = total_reached;
1972 detail::capture_failure_dependencies<Shape>(
1973 world, segment_request, result.status, product.dependencies_);
1976 total_cost += result.cost;
1977 if (total_cost >= std::numeric_limits<std::uint32_t>::max()) {
1978 product.path_.clear();
1979 product.status_ = PathStatus::CostOverflow;
1980 product.expanded_nodes_ = total_expanded;
1981 product.reached_nodes_ = total_reached;
1982 detail::capture_failure_dependencies<Shape>(
1983 world, request, product.status_, product.dependencies_);
1986 product.segment_.assign(result.path.begin(), result.path.end());
1987 for (std::size_t i = product.path_.empty() ? 0u : 1u;
1988 i < product.segment_.size(); ++i) {
1989 product.path_.push_back(product.segment_[i]);
1994 for (
const auto waypoint : source) {
1995 if (!append_segment(
PathRequest{from, waypoint})) {
1996 return PathResult{product.status_, 0, total_expanded, total_reached,
2001 if (!append_segment(
PathRequest{from, request.goal})) {
2002 return PathResult{product.status_, 0, total_expanded, total_reached,
2006 product.status_ = PathStatus::Found;
2007 product.cost_ =
static_cast<std::uint32_t
>(total_cost);
2008 product.expanded_nodes_ = total_expanded;
2009 product.reached_nodes_ = total_reached;
2010 for (
const auto coord : product.path_) {
2011 const auto key = tile_key<Shape>(coord);
2012 product.dependencies_.add_chunk(world, chunk_key<Shape>(key));
2014 return PathResult{product.status_, product.cost_, product.expanded_nodes_,
2015 product.reached_nodes_, product.path_};
2019template <
typename World>
2023 if (!product.is_valid(world)) {
2024 return PathResult{PathStatus::NotComputed, 0, 0, 0, {}};
2026 return PathResult{product.status_, product.cost_, 0, 0, product.path_};
2030template <
typename WorldType,
typename Tag>
2034 using Shape =
typename WorldType::shape_type;
2035 using Space = detail::NodeIndexSpace<WorldType>;
2036 using Class = movement::movement_class_of<Tag>;
2037 using UnitClass = movement::detail::UnitMovementClass<Class>;
2039 constexpr auto infinite_distance = std::numeric_limits<std::uint32_t>::max();
2041 if constexpr (Model::cost_scale != 1) {
2046 TESS_DIAG_EVENT_VALUE(path_clear, scratch.touched_.size());
2047 scratch.clear_build();
2048 if (!contains<Shape>(goal)) {
2051 if constexpr (!Space::is_dense) {
2054 const Space residency{world};
2055 if (!residency.is_resident_index(detail::tile_index<Shape>(goal))) {
2057 policy == MissingChunkPolicy::ReportIndeterminate
2058 ? PathStatus::Indeterminate
2059 : PathStatus::InvalidGoal,
2063 TESS_DIAG_EVENT(path_goal_passability_check);
2064 if (!detail::is_passable<WorldType, Tag>(world, goal)) {
2068 const Space space{world};
2069 const auto node_count = space.capacity_hint();
2070 if (scratch.distance_.size() != node_count) {
2071 TESS_DIAG_EVENT(path_initialize);
2072 scratch.generation_.assign(node_count, 0);
2073 scratch.distance_.assign(node_count, infinite_distance);
2076 const auto goal_index = detail::tile_index<Shape>(goal);
2077 const auto goal_offset = space.offset(goal_index);
2078 scratch.goal_ = goal;
2079 scratch.has_goal_ =
true;
2080 scratch.template stamp_model<Model>();
2081 scratch.stamp_residency(world);
2082 scratch.distance_[goal_offset] = 0;
2083 scratch.touch_node(goal_offset, goal_index);
2084 TESS_DIAG_EVENT(path_touch_node);
2085 scratch.frontier_.push_back(goal_index);
2086 TESS_DIAG_EVENT(path_heap_push);
2088 std::size_t expanded_nodes = 0;
2089 std::size_t head = 0;
2092 [[maybe_unused]]
bool crossed_missing =
false;
2093 const auto model = Model{};
2094 while (head < scratch.frontier_.size()) {
2095 const auto current = scratch.frontier_[head];
2097 TESS_DIAG_EVENT(path_heap_pop);
2100 const auto current_offset = space.offset(current);
2101 const auto current_distance =
2102 scratch.distance_at(current_offset, infinite_distance);
2103 const auto current_coord = detail::tile_coord<Shape>(current);
2104 const auto visit_neighbor = [&](std::uint64_t neighbor_index) {
2105 if constexpr (!Space::is_dense) {
2108 if (!space.is_resident_index(neighbor_index)) {
2109 crossed_missing =
true;
2113 const auto neighbor_offset = space.offset(neighbor_index);
2114 if (scratch.is_current(neighbor_offset)) {
2115 TESS_DIAG_EVENT(path_neighbor_closed);
2118 scratch.distance_[neighbor_offset] = current_distance + 1;
2119 scratch.touch_node(neighbor_offset, neighbor_index);
2120 TESS_DIAG_EVENT(path_touch_node);
2121 scratch.frontier_.push_back(neighbor_index);
2122 TESS_DIAG_EVENT(path_heap_push);
2124 if constexpr (Model::preserves_default_connectivity &&
2125 std::is_same_v<
typename Model::step_policy,
2127 detail::for_each_indexed_axis_neighbor<Shape>(
2128 current_coord, current, [&](
Coord3, std::uint64_t neighbor_index) {
2129 TESS_DIAG_EVENT(path_neighbor_candidate);
2130 if constexpr (!Space::is_dense) {
2131 if (!space.is_resident_index(neighbor_index)) {
2132 crossed_missing =
true;
2136 TESS_DIAG_EVENT(path_passability_check);
2137 if (!detail::is_passable_index<WorldType, Tag>(world,
2139 TESS_DIAG_EVENT(path_neighbor_blocked);
2142 visit_neighbor(neighbor_index);
2145 model.for_each_reverse(world, current_coord, current, [&](
auto probe) {
2146 TESS_DIAG_EVENT(path_neighbor_candidate);
2147 if (probe.availability == TransitionAvailability::MissingTopology) {
2148 crossed_missing =
true;
2151 visit_neighbor(probe.to_index);
2156 if constexpr (!Space::is_dense) {
2157 if (crossed_missing && policy == MissingChunkPolicy::ReportIndeterminate) {
2158 scratch.publish_build_status(PathStatus::Indeterminate);
2160 scratch.touched_.size()};
2163 scratch.publish_build_status(PathStatus::Found);
2165 scratch.touched_.size()};
2172template <
typename World,
typename Tag>
2176 using Shape =
typename World::shape_type;
2177 using Space = detail::NodeIndexSpace<World>;
2178 using Class = movement::movement_class_of<Tag>;
2179 using UnitClass = movement::detail::UnitMovementClass<Class>;
2181 constexpr auto infinite_distance = std::numeric_limits<std::uint32_t>::max();
2183 if constexpr (Model::cost_scale != 1) {
2184 return detail::weighted_distance_field_path_core<World, UnitClass>(
2185 world, request, scratch,
true);
2188 scratch.clear_path();
2189 if (!contains<Shape>(request.start)) {
2190 return PathResult{PathStatus::InvalidStart, 0, 0, 0, scratch.path_};
2192 if (!contains<Shape>(request.goal)) {
2193 return PathResult{PathStatus::InvalidGoal, 0, 0, 0, scratch.path_};
2195 if (!scratch.has_goal_ || scratch.goal_ != request.goal ||
2196 !scratch.template model_matches<Model>() ||
2197 !scratch.residency_matches(world)) {
2198 return PathResult{PathStatus::NotComputed, 0, 0, 0, scratch.path_};
2200 if constexpr (!Space::is_dense) {
2201 const Space residency{world};
2202 if (!residency.is_resident_index(
2203 detail::tile_index<Shape>(request.start))) {
2204 const auto status = scratch.build_status_ == PathStatus::Indeterminate
2205 ? PathStatus::Indeterminate
2206 : PathStatus::InvalidStart;
2207 return PathResult{status, 0, 0, 0, scratch.path_};
2210 TESS_DIAG_EVENT(path_start_passability_check);
2211 if (!detail::is_passable<World, Tag>(world, request.start)) {
2212 return PathResult{PathStatus::InvalidStart, 0, 0, 0, scratch.path_};
2215 const Space space{world};
2216 const auto start_index = detail::tile_index<Shape>(request.start);
2217 auto current = start_index;
2218 auto current_offset = space.offset(current);
2219 auto current_distance =
2220 scratch.distance_at(current_offset, infinite_distance);
2221 if (current_distance == infinite_distance) {
2222 return PathResult{scratch.unresolved_path_status(), 0, 0,
2223 scratch.touched_.size(), scratch.path_};
2226 scratch.path_.push_back(request.start);
2227 TESS_DIAG_EVENT(path_reconstruct_node);
2228 const auto model = Model{};
2229 while (current_distance > 0) {
2230 const auto current_coord = detail::tile_coord<Shape>(current);
2231 auto next = current;
2232 auto next_distance = current_distance;
2233 const auto consider_neighbor = [&](std::uint64_t neighbor_index) {
2234 if constexpr (!Space::is_dense) {
2238 if (!space.is_resident_index(neighbor_index)) {
2242 if (!detail::is_passable_index<World, Tag>(world, neighbor_index)) {
2245 const auto neighbor_offset = space.offset(neighbor_index);
2246 const auto neighbor_distance =
2247 scratch.distance_at(neighbor_offset, infinite_distance);
2248 if (neighbor_distance < next_distance) {
2249 next = neighbor_index;
2250 next_distance = neighbor_distance;
2253 if constexpr (Model::preserves_default_connectivity &&
2254 std::is_same_v<
typename Model::step_policy,
2256 detail::for_each_indexed_axis_neighbor<Shape>(
2257 current_coord, current, [&](
Coord3, std::uint64_t neighbor_index) {
2258 consider_neighbor(neighbor_index);
2261 model.for_each_forward(world, current_coord, current, [&](
auto probe) {
2262 if (probe.availability == TransitionAvailability::Legal) {
2263 consider_neighbor(probe.to_index);
2268 if (next == current || next_distance + 1 != current_distance) {
2269 scratch.path_.clear();
2270 return PathResult{PathStatus::NotComputed, 0, 0, scratch.touched_.size(),
2275 current_offset = space.offset(current);
2276 current_distance = scratch.distance_at(current_offset, infinite_distance);
2277 scratch.path_.push_back(detail::tile_coord<Shape>(current));
2278 TESS_DIAG_EVENT(path_reconstruct_node);
2282 PathStatus::Found, scratch.distance_[space.offset(start_index)],
2283 scratch.path_.size(), scratch.touched_.size(), scratch.path_};
2287template <
typename WorldType,
typename Tag,
typename Prov
ider>
2288[[nodiscard]]
auto build_distance_field(
const WorldType& world,
Coord3 goal,
2290 MissingChunkPolicy policy,
2291 const Provider& provider)
2293 using Class = movement::movement_class_of<Tag>;
2294 using UnitClass = movement::detail::UnitMovementClass<Class>;
2295 return build_weighted_distance_field<WorldType, UnitClass, Provider>(
2296 world, goal, scratch, policy, provider);
2300template <
typename World,
typename Tag,
typename Prov
ider>
2301[[nodiscard]]
auto distance_field_path(
const World& world, PathRequest request,
2302 DistanceFieldScratch& scratch,
2303 const Provider& provider) -> PathResult {
2304 using Class = movement::movement_class_of<Tag>;
2305 using UnitClass = movement::detail::UnitMovementClass<Class>;
2306 return detail::weighted_distance_field_path_core<World, UnitClass, Provider>(
2307 world, request, scratch,
true, provider);
2311template <
typename WorldType,
typename Class,
typename Prov
ider>
2314 [[maybe_unused]] MissingChunkPolicy policy,
const Provider& provider)
2316 static_assert(std::derived_from<Class, movement::movement_class_tag>,
2317 "build_weighted_distance_field<World, Class> requires a "
2318 "MovementClass; pass a movement class such as "
2319 "PositiveCostFieldMovement.");
2320 using Shape =
typename WorldType::shape_type;
2321 using Space = detail::NodeIndexSpace<WorldType>;
2323 constexpr auto infinite_distance = std::numeric_limits<std::uint32_t>::max();
2325 TESS_DIAG_EVENT_VALUE(path_clear, scratch.touched_.size());
2326 scratch.clear_build();
2327 if (!contains<Shape>(goal)) {
2330 if constexpr (!Space::is_dense) {
2334 const Space residency{world};
2335 if (!residency.is_resident_index(detail::tile_index<Shape>(goal))) {
2337 policy == MissingChunkPolicy::ReportIndeterminate
2338 ? PathStatus::Indeterminate
2339 : PathStatus::InvalidGoal,
2343 TESS_DIAG_EVENT(path_goal_passability_check);
2344 if (!detail::is_passable<WorldType, Class>(world, goal)) {
2348 const auto goal_index = detail::tile_index<Shape>(goal);
2349 if (detail::tile_entry_cost_index<WorldType, Class>(world, goal_index) == 0) {
2353 const Space space{world};
2354 const auto node_count = space.capacity_hint();
2355 if (scratch.distance_.size() != node_count) {
2356 TESS_DIAG_EVENT(path_initialize);
2357 scratch.generation_.assign(node_count, 0);
2358 scratch.distance_.assign(node_count, infinite_distance);
2361 const auto goal_offset = space.offset(goal_index);
2362 const auto model = Model{provider};
2363 scratch.goal_ = goal;
2364 scratch.has_goal_ =
true;
2365 scratch.template stamp_model<Model>(
2366 model, detail::transition_provider_instance_identity(provider));
2367 scratch.stamp_residency(world);
2368 scratch.distance_[goal_offset] = 0;
2369 scratch.touch_node(goal_offset, goal_index);
2370 TESS_DIAG_EVENT(path_touch_node);
2371 scratch.weighted_frontier_.push_back(
2372 detail::PackedOpenNode::make(goal_index, 0, 0));
2373 std::push_heap(scratch.weighted_frontier_.begin(),
2374 scratch.weighted_frontier_.end(),
2375 detail::packed_open_node_less);
2376 TESS_DIAG_EVENT(path_heap_push);
2378 std::size_t expanded_nodes = 0;
2379 [[maybe_unused]]
bool crossed_missing =
false;
2380 auto cost_overflow =
false;
2381 while (!scratch.weighted_frontier_.empty()) {
2382 TESS_DIAG_EVENT(path_heap_pop);
2383 std::pop_heap(scratch.weighted_frontier_.begin(),
2384 scratch.weighted_frontier_.end(),
2385 detail::packed_open_node_less);
2386 const auto current = scratch.weighted_frontier_.back();
2387 scratch.weighted_frontier_.pop_back();
2389 const auto current_offset = space.offset(current.index);
2390 const auto current_distance =
2391 scratch.distance_at(current_offset, infinite_distance);
2392 if (current.g() != current_distance) {
2393 TESS_DIAG_EVENT_VALUE(path_skip_pop,
false);
2398 const auto current_coord = detail::tile_coord<Shape>(current.index);
2399 model.for_each_reverse(
2400 world, current_coord, current.index, [&](
auto probe) {
2401 TESS_DIAG_EVENT(path_neighbor_candidate);
2402 if (probe.availability == TransitionAvailability::MissingTopology) {
2403 crossed_missing = true;
2406 if (probe.cost_overflow) {
2407 cost_overflow = true;
2410 const auto neighbor_index = probe.to_index;
2411 if constexpr (!Space::is_dense) {
2412 if (!space.is_resident_index(neighbor_index)) {
2413 crossed_missing =
true;
2417 const auto neighbor_offset = space.offset(neighbor_index);
2418 TESS_DIAG_EVENT(path_relax_attempt);
2419 if (!scratch.is_current(neighbor_offset)) {
2420 scratch.distance_[neighbor_offset] = infinite_distance;
2421 scratch.touch_node(neighbor_offset, neighbor_index);
2422 TESS_DIAG_EVENT(path_touch_node);
2425 const auto next_distance =
2426 detail::saturating_add(current_distance, probe.cost);
2427 if (next_distance == infinite_distance) {
2428 cost_overflow =
true;
2432 scratch.distance_at(neighbor_offset, infinite_distance)) {
2433 TESS_DIAG_EVENT(path_relax_success);
2434 scratch.distance_[neighbor_offset] = next_distance;
2435 scratch.weighted_frontier_.push_back(detail::PackedOpenNode::make(
2436 neighbor_index, next_distance, next_distance));
2437 std::push_heap(scratch.weighted_frontier_.begin(),
2438 scratch.weighted_frontier_.end(),
2439 detail::packed_open_node_less);
2440 TESS_DIAG_EVENT(path_heap_push);
2445 if constexpr (!Space::is_dense) {
2446 if (crossed_missing && policy == MissingChunkPolicy::ReportIndeterminate) {
2447 scratch.publish_build_status(PathStatus::Indeterminate);
2449 scratch.touched_.size()};
2452 if (cost_overflow) {
2453 scratch.discard_build_result();
2455 scratch.touched_.size()};
2457 scratch.publish_build_status(PathStatus::Found);
2459 scratch.touched_.size()};
2462template <
typename WorldType,
typename Class>
2466 MissingChunkPolicy policy)
2472#include <tess/path/detail/weighted_batch.h>
2476#include <tess/path/route_cache.h>
Definition field_product_cache.h:60
friend auto build_distance_field(const WorldType &world, Coord3 goal, DistanceFieldScratch &scratch, MissingChunkPolicy policy) -> DistanceFieldResult
Builds an unweighted goal-rooted field into caller-owned scratch.
Definition path.h:2031
friend auto build_weighted_distance_field(const WorldType &world, Coord3 goal, DistanceFieldScratch &scratch, MissingChunkPolicy policy) -> DistanceFieldResult
Definition path.h:2463
friend auto weighted_path_batch(const World &world, std::span< const PathRequest > requests, WeightedPathBatchScratch &scratch, MissingChunkPolicy policy, const Provider &provider) -> std::span< const PathResult >
Solves a provider-aware bounded weighted batch.
friend auto weighted_path_batch(const World &world, std::span< const PathRequest > requests, WeightedPathBatchScratch &scratch, MissingChunkPolicy policy) -> std::span< const PathResult >
Solves a bounded weighted batch without special transitions.
friend auto distance_field_product_path(const World &world, Coord3 start, const DistanceFieldProduct &product, DistanceFieldScratch &scratch) -> PathResult
Reconstructs a borrowed path from a valid multi-goal product.
Definition field_product_cache.h:1281
friend auto build_weighted_distance_field_product(const World &world, const GoalSet &goals, DistanceFieldProduct &product, DistanceFieldScratch &scratch, const Provider &provider) -> DistanceFieldResult
Builds a dense multi-goal weighted field into a reusable product.
Definition field_product_cache.h:1016
friend auto weighted_distance_field_product_path(const World &world, Coord3 start, const DistanceFieldProduct &product, DistanceFieldScratch &scratch, const Provider &provider) -> PathResult
Reconstructs an exact weighted path through a valid reusable product.
Definition field_product_cache.h:1290
friend auto nearest_target(const World &world, Coord3 start, const DistanceFieldProduct &product, DistanceFieldScratch &scratch) -> NearestTargetResult
Finds the nearest reachable goal represented by a valid product.
Definition field_product_cache.h:1413
friend auto build_weighted_distance_field_in_box(const World &world, Coord3 goal, Box3 domain, DistanceFieldScratch &scratch, MissingChunkPolicy policy, const Provider &provider) -> DistanceFieldResult
Builds a boxed weighted field composed with a special provider.
Definition distance_field_box.h:18
friend auto build_distance_field_product(const World &world, const GoalSet &goals, DistanceFieldProduct &product, DistanceFieldScratch &scratch) -> DistanceFieldResult
Builds a dense multi-goal field into caller-owned reusable storage.
Definition field_product_cache.h:1005
friend auto distance_field_path(const World &world, PathRequest request, DistanceFieldScratch &scratch) -> PathResult
Definition path.h:2173
Owns an ordered set of goals used to build a reusable distance product.
Definition field_product_cache.h:19
friend auto weighted_astar_path(const World &world, PathRequest request, PathScratch &scratch, MissingChunkPolicy policy) -> PathResult
friend auto weighted_astar_path(const World &world, PathRequest request, PathScratch &scratch, MissingChunkPolicy policy, const Provider &provider, PathTieBreak tie_break) -> PathResult
Finds a provider-aware weighted path with seeded equal-cost tie-breaking.
friend auto astar_path(const World &world, PathRequest request, PathScratch &scratch, MissingChunkPolicy policy, const Provider &provider) -> PathResult
Finds a minimum-step path composed with a special-transition provider.
friend auto astar_path(const World &world, PathRequest request, PathScratch &scratch, MissingChunkPolicy policy) -> PathResult
friend auto cached_astar_path(const World &world, PathRequest request, PathScratch &scratch, UnitRouteCache &cache, MissingChunkPolicy policy) -> PathResult
Finds a cached empty-provider route or computes and stores one.
Definition route_cache.h:921
friend auto weighted_astar_path(const World &world, PathRequest request, PathScratch &scratch, PathTieBreak tie_break, MissingChunkPolicy policy) -> PathResult
Finds an optimal weighted path with seeded equal-cost tie-breaking.
friend auto weighted_astar_path(const World &world, PathRequest request, PathScratch &scratch, MissingChunkPolicy policy, const Provider &provider) -> PathResult
Finds a weighted path composed with a special-transition provider.
Definition path_view.h:21
Definition transition_model.h:380
Definition route_cache.h:94
friend auto weighted_path_batch(const World &world, std::span< const PathRequest > requests, WeightedPathBatchScratch &scratch, MissingChunkPolicy policy, const Provider &provider) -> std::span< const PathResult >
Solves a provider-aware bounded weighted batch.
friend auto weighted_path_batch(const World &world, std::span< const PathRequest > requests, WeightedPathBatchScratch &scratch, MissingChunkPolicy policy) -> std::span< const PathResult >
Solves a bounded weighted batch without special transitions.
friend auto build_weighted_portal_route_product(const World &world, PathRequest request, std::span< const Coord3 > waypoints, PathScratch &scratch, WeightedPortalRouteProduct &product) -> PathResult
Definition path.h:1938
friend auto build_weighted_chunk_portal_route_product_cached(const World &world, PathRequest request, PathScratch &scratch, WeightedPortalSegmentCache &cache, WeightedPortalRouteProduct &product) -> PathResult
Builds a chunk-portal weighted route through the segment cache.
Definition portal_route.h:299
friend auto weighted_portal_route_product_path(const World &world, const WeightedPortalRouteProduct &product) -> PathResult
Replays a portal-route product when its dependencies remain current.
Definition path.h:2020
friend auto build_weighted_chunk_portal_route_product(const World &world, PathRequest request, PathScratch &scratch, WeightedPortalRouteProduct &product) -> PathResult
Definition portal_route.h:193
Definition portal_segment_cache.h:61
friend auto weighted_route_product_path(const World &world, const WeightedRouteProduct &product) -> PathResult
Replays a route product when all captured content versions still match.
Definition path.h:1925
friend auto build_weighted_route_product(const World &world, PathRequest request, PathScratch &scratch, WeightedRouteProduct &product) -> PathResult
Definition path.h:1883
Supplies no special transitions beyond ordinary face adjacency.
Definition transition_provider.h:132
Definition metadata_types.h:86
Reports distance-field construction status and search work.
Definition path.h:70
Reports the closest reachable goal and a scratch-owned path to it.
Definition field_product_cache.h:42
Specifies inclusive start and goal coordinates for a path query.
Definition request.h:10
Definition step_policy.h:26