3#include <tess/diagnostics/diagnostics.h>
4#include <tess/path/path_runtime.h>
5#include <tess/path/precheck.h>
6#include <tess/sim/movement.h>
26enum class PathAgentPhase : std::uint8_t {
39 std::size_t path_index = 0;
42 PathAgentPhase phase = PathAgentPhase::Idle;
43 bool has_goal =
false;
44 std::uint32_t blocked_retries = 0;
51 std::size_t submitted = 0;
52 std::size_t completed = 0;
53 std::size_t found = 0;
54 std::size_t invalid_start = 0;
55 std::size_t invalid_goal = 0;
56 std::size_t no_path = 0;
57 std::size_t not_computed = 0;
58 std::size_t no_candidate = 0;
62 std::size_t indeterminate = 0;
63 std::size_t cost_overflow = 0;
66 std::size_t precheck_ruled_out = 0;
70 std::size_t expanded_nodes = 0;
71 std::size_t advanced = 0;
72 std::size_t arrived = 0;
73 std::size_t blocked_waits = 0;
79 std::size_t max_steps = 1;
99enum class PathSubmitScope : std::uint8_t {
116 std::vector<std::vector<Coord3>> routes;
118 void ensure_size(std::size_t count) {
119 if (routes.size() < count) {
120 routes.resize(count);
131[[nodiscard]]
inline bool movement_block_can_retry_route(
132 MovementStatus status)
noexcept {
133 return status == MovementStatus::Occupied ||
134 status == MovementStatus::Reserved;
137inline void block_path_agent(PathAgentState& agent,
138 MovementStatus status)
noexcept {
139 agent.phase = PathAgentPhase::Blocked;
140 if (!movement_block_can_retry_route(status)) {
141 agent.last_result.reset();
145inline void resume_path_agent(PathAgentState& agent)
noexcept {
146 agent.phase = PathAgentPhase::Following;
147 agent.blocked_retries = 0;
150[[nodiscard]]
inline bool can_skip_scoped_path_submission(
151 const PathAgentState& agent)
noexcept {
152 return agent.phase == PathAgentPhase::Following ||
153 (agent.phase == PathAgentPhase::Blocked &&
154 agent.last_result == PathStatus::Found);
163[[nodiscard]]
constexpr bool has_next_step(std::size_t path_index,
164 std::size_t size)
noexcept {
165 return size > 0 && path_index < size - 1;
176 agent.path_index = 0;
177 agent.last_result.reset();
178 agent.phase = PathAgentPhase::NeedsPath;
179 agent.blocked_retries = 0;
180 agent.has_goal =
true;
187inline void clear_path_agent_goal(
PathAgentState& agent)
noexcept {
190 agent.path_index = 0;
191 agent.last_result.reset();
192 agent.phase = PathAgentPhase::Idle;
193 agent.blocked_retries = 0;
194 agent.has_goal =
false;
198[[nodiscard]]
inline auto path_agent_goal_outstanding(
200 return agent.has_goal && agent.phase != PathAgentPhase::Unreachable;
206inline void fail_path_agent_flow(
209 if (accounting !=
nullptr) {
210 ++accounting->counters.failed;
211 accounting->record_left_outstanding();
212 accounting->counters.residence_ticks_accumulated +=
213 accounting->last_observed_tick - agent.armed_tick;
218inline void arrive_path_agent(
220 if (accounting !=
nullptr) {
221 ++accounting->counters.completed;
222 accounting->record_left_outstanding();
223 accounting->counters.residence_ticks_accumulated +=
224 accounting->last_observed_tick - agent.armed_tick;
226 clear_path_agent_goal(agent);
230inline auto submit_path_agents(
232 PathSubmitScope scope = PathSubmitScope::All,
235 runtime.clear_requests();
237 for (
auto& agent : agents) {
238 if (scope == PathSubmitScope::NeedsOnly &&
239 detail::can_skip_scoped_path_submission(agent)) {
244 agent.path_index = 0;
245 if (!agent.has_goal || agent.phase == PathAgentPhase::Unreachable) {
248 if (agent.position == agent.goal) {
249 arrive_path_agent(agent, accounting);
253 agent.ticket = runtime.submit(
PathRequest{agent.position, agent.goal});
262 PathStatus status)
noexcept {
264 case PathStatus::NotComputed:
265 ++stats.not_computed;
267 case PathStatus::Found:
270 case PathStatus::InvalidStart:
271 ++stats.invalid_start;
273 case PathStatus::InvalidGoal:
274 ++stats.invalid_goal;
276 case PathStatus::NoPath:
279 case PathStatus::Indeterminate:
280 ++stats.indeterminate;
282 case PathStatus::CostOverflow:
283 ++stats.cost_overflow;
285 case PathStatus::NoCandidate:
286 ++stats.no_candidate;
293inline auto apply_path_agent_results(std::span<PathAgentState> agents,
295 PathSubmitScope scope,
299 if (routes !=
nullptr) {
302 routes->ensure_size(agents.size());
305 for (std::size_t i = 0; i < agents.size(); ++i) {
306 auto& agent = agents[i];
307 if (scope == PathSubmitScope::NeedsOnly &&
308 detail::can_skip_scoped_path_submission(agent)) {
313 if (!agent.has_goal || agent.position == agent.goal ||
314 agent.phase == PathAgentPhase::Unreachable) {
318 const auto was_blocked = agent.phase == PathAgentPhase::Blocked;
319 const auto result = runtime.result(agent.ticket);
320 agent.last_result = result.status;
321 agent.path_index = 0;
322 if (result.status == PathStatus::Found) {
323 agent.phase = PathAgentPhase::Following;
329 agent.blocked_retries = 0;
331 if (routes !=
nullptr) {
332 routes->routes[i].assign(result.path.begin(), result.path.end());
337 agent.phase = PathAgentPhase::Blocked;
338 if (routes !=
nullptr) {
339 routes->routes[i].clear();
343 stats.expanded_nodes += result.expanded_nodes;
344 record_path_agent_status(stats, result.status);
351inline auto apply_path_agent_results(std::span<PathAgentState> agents,
354 return apply_path_agent_results(agents, runtime, PathSubmitScope::All,
365inline auto advance_path_agents(
367 std::size_t max_steps = 1,
370 if (max_steps == 0) {
374 for (
auto& agent : agents) {
375 if (!agent.has_goal || agent.last_result != PathStatus::Found) {
379 const auto result = runtime.result(agent.ticket);
380 if (result.status != PathStatus::Found || result.path.empty()) {
384 for (std::size_t step = 0; step < max_steps; ++step) {
385 if (!detail::has_next_step(agent.path_index, result.path.size())) {
389 agent.position = result.path[agent.path_index];
390 detail::resume_path_agent(agent);
392 if (agent.position == agent.goal) {
393 arrive_path_agent(agent, accounting);
394 agent.last_result = PathStatus::Found;
410template <
typename World,
typename ClassOrTag,
typename OccupancyTag,
411 typename ReservationTag,
typename OnCommit>
412 requires std::invocable<OnCommit&, std::size_t, Coord3, Coord3>
419inline auto advance_path_agents_with_movement(
420 World& world, std::span<PathAgentState> agents,
425 if (options.max_steps == 0) {
429 for (std::size_t agent_index = 0; agent_index < agents.size();
431 auto& agent = agents[agent_index];
432 if (!agent.has_goal || agent.last_result != PathStatus::Found) {
436 const auto result = runtime.result(agent.ticket);
437 if (result.status != PathStatus::Found || result.path.empty()) {
441 for (std::size_t step = 0; step < options.max_steps; ++step) {
442 if (!detail::has_next_step(agent.path_index, result.path.size())) {
446 const auto from = agent.position;
447 const auto to = result.path[agent.path_index + 1];
448 const auto movement =
449 commit_movement_intent<
World, ClassOrTag, OccupancyTag,
451 world,
MovementIntent{from, to, {}}, options.movement_dirty_mask);
452 if (movement.status != MovementStatus::Moved) {
453 record_movement_failure(stats.movement_failures, movement.status);
454 if (is_transient_movement_failure(movement.status)) {
461 detail::block_path_agent(agent, movement.status);
462 ++stats.blocked_waits;
466 agent.last_result.reset();
467 agent.phase = PathAgentPhase::Unreachable;
468 fail_path_agent_flow(agent, accounting);
475 detail::resume_path_agent(agent);
476 on_commit(agent_index, from, to);
478 if (agent.position == agent.goal) {
479 arrive_path_agent(agent, accounting);
480 agent.last_result = PathStatus::Found;
490template <
typename World,
typename ClassOrTag,
typename OccupancyTag,
491 typename ReservationTag>
498inline auto advance_path_agents_with_movement(
499 World& world, std::span<PathAgentState> agents,
502 return advance_path_agents_with_movement<
World, ClassOrTag, OccupancyTag,
504 world, agents, runtime, options, [](std::size_t,
Coord3,
Coord3) {},
508template <
typename World,
typename ClassOrTag,
typename OccupancyTag,
509 typename ReservationTag,
typename Provider>
511inline auto advance_path_agents_with_movement(
512 World& world, std::span<PathAgentState> agents,
516 TESS_ASSERT(routes.routes.size() >= agents.size());
518 if (options.max_steps == 0) {
521 for (std::size_t agent_index = 0; agent_index < agents.size();
523 auto& agent = agents[agent_index];
524 if (!agent.has_goal || agent.last_result != PathStatus::Found) {
527 const auto& route = routes.routes[agent_index];
528 for (std::size_t step = 0;
529 step < options.max_steps &&
530 detail::has_next_step(agent.path_index, route.size());
532 const auto from = agent.position;
533 const auto to = route[agent.path_index + 1];
534 const auto movement =
535 commit_movement_intent<
World, ClassOrTag, OccupancyTag,
539 if (movement.status != MovementStatus::Moved) {
540 record_movement_failure(stats.movement_failures, movement.status);
541 if (is_transient_movement_failure(movement.status)) {
542 detail::block_path_agent(agent, movement.status);
543 ++stats.blocked_waits;
545 agent.last_result.reset();
546 agent.phase = PathAgentPhase::Unreachable;
547 fail_path_agent_flow(agent, accounting);
553 detail::resume_path_agent(agent);
555 if (agent.position == agent.goal) {
556 arrive_path_agent(agent, accounting);
557 agent.last_result = PathStatus::Found;
571inline auto advance_path_agents(
573 std::size_t max_steps = 1,
577 TESS_ASSERT(routes.routes.size() >= agents.size());
579 if (max_steps == 0) {
583 for (std::size_t i = 0; i < agents.size(); ++i) {
584 auto& agent = agents[i];
585 if (!agent.has_goal || agent.last_result != PathStatus::Found) {
589 const auto& route = routes.routes[i];
594 for (std::size_t step = 0; step < max_steps; ++step) {
595 if (!detail::has_next_step(agent.path_index, route.size())) {
599 agent.position = route[agent.path_index];
600 detail::resume_path_agent(agent);
602 if (agent.position == agent.goal) {
603 arrive_path_agent(agent, accounting);
604 agent.last_result = PathStatus::Found;
614template <
typename World,
typename ClassOrTag,
typename OccupancyTag,
615 typename ReservationTag,
typename OnCommit>
616 requires std::invocable<OnCommit&, std::size_t, Coord3, Coord3>
618inline auto advance_path_agents_with_movement(
619 World& world, std::span<PathAgentState> agents,
624 TESS_ASSERT(routes.routes.size() >= agents.size());
626 if (options.max_steps == 0) {
630 for (std::size_t agent_index = 0; agent_index < agents.size();
632 auto& agent = agents[agent_index];
633 if (!agent.has_goal || agent.last_result != PathStatus::Found) {
637 const auto& route = routes.routes[agent_index];
642 for (std::size_t step = 0; step < options.max_steps; ++step) {
643 if (!detail::has_next_step(agent.path_index, route.size())) {
647 const auto from = agent.position;
648 const auto to = route[agent.path_index + 1];
649 const auto movement =
650 commit_movement_intent<
World, ClassOrTag, OccupancyTag,
652 world,
MovementIntent{from, to, {}}, options.movement_dirty_mask);
653 if (movement.status != MovementStatus::Moved) {
654 record_movement_failure(stats.movement_failures, movement.status);
655 if (is_transient_movement_failure(movement.status)) {
658 detail::block_path_agent(agent, movement.status);
659 ++stats.blocked_waits;
661 agent.last_result.reset();
662 agent.phase = PathAgentPhase::Unreachable;
663 fail_path_agent_flow(agent, accounting);
670 detail::resume_path_agent(agent);
671 on_commit(agent_index, from, to);
673 if (agent.position == agent.goal) {
674 arrive_path_agent(agent, accounting);
675 agent.last_result = PathStatus::Found;
685template <
typename World,
typename ClassOrTag,
typename OccupancyTag,
686 typename ReservationTag>
688inline auto advance_path_agents_with_movement(
689 World& world, std::span<PathAgentState> agents,
692 return advance_path_agents_with_movement<
World, ClassOrTag, OccupancyTag,
694 world, agents, routes, options, [](std::size_t,
Coord3,
Coord3) {},
701 lhs.submitted += rhs.submitted;
702 lhs.completed += rhs.completed;
703 lhs.found += rhs.found;
704 lhs.invalid_start += rhs.invalid_start;
705 lhs.invalid_goal += rhs.invalid_goal;
706 lhs.no_path += rhs.no_path;
707 lhs.not_computed += rhs.not_computed;
708 lhs.no_candidate += rhs.no_candidate;
709 lhs.indeterminate += rhs.indeterminate;
710 lhs.cost_overflow += rhs.cost_overflow;
711 lhs.precheck_ruled_out += rhs.precheck_ruled_out;
712 lhs.expanded_nodes += rhs.expanded_nodes;
713 lhs.advanced += rhs.advanced;
714 lhs.arrived += rhs.arrived;
715 lhs.blocked_waits += rhs.blocked_waits;
716 lhs.movement_failures.invalid += rhs.movement_failures.invalid;
717 lhs.movement_failures.impassable += rhs.movement_failures.impassable;
718 lhs.movement_failures.blocked += rhs.movement_failures.blocked;
719 lhs.movement_failures.occupied += rhs.movement_failures.occupied;
720 lhs.movement_failures.reserved += rhs.movement_failures.reserved;
721 lhs.movement_failures.stale_content += rhs.movement_failures.stale_content;
722 lhs.movement_failures.stale_topology += rhs.movement_failures.stale_topology;
725template <
typename World,
typename ClassOrTag>
727[[nodiscard]]
auto process_unit_path_agents(
728 const World& world, std::span<PathAgentState> agents,
731 PathSubmitScope scope = PathSubmitScope::All,
734 auto stats = submit_path_agents(agents, runtime, scope, accounting);
735 (void)runtime.template process_unit_cached<World, ClassOrTag>(world, policy,
737 add_path_agent_stats(
738 stats, apply_path_agent_results(agents, runtime, scope, routes));
739 stats.precheck_ruled_out = runtime.stats().precheck_ruled_out;
743template <
typename World,
typename ClassOrTag,
typename Prov
ider>
745[[nodiscard]]
auto process_unit_path_agents(
746 const World& world, std::span<PathAgentState> agents,
749 PathSubmitScope scope,
PathAgentRoutes* routes,
const Provider& provider,
751 auto stats = submit_path_agents(agents, runtime, scope, accounting);
752 (void)runtime.template process_unit_cached<World, ClassOrTag>(
753 world, policy, graph, provider);
754 add_path_agent_stats(
755 stats, apply_path_agent_results(agents, runtime, scope, routes));
756 stats.precheck_ruled_out = runtime.stats().precheck_ruled_out;
760template <
typename World,
typename Class, std::u
int32_t MaxCost>
762[[nodiscard]]
auto process_weighted_path_agents(
763 const World& world, std::span<PathAgentState> agents,
766 PathSubmitScope scope = PathSubmitScope::All,
769 auto stats = submit_path_agents(agents, runtime, scope, accounting);
770 (void)runtime.template process_weighted_batch<World, Class, MaxCost>(
771 world, policy, graph);
772 add_path_agent_stats(
773 stats, apply_path_agent_results(agents, runtime, scope, routes));
774 stats.precheck_ruled_out = runtime.stats().precheck_ruled_out;
778template <
typename World,
typename Class, std::uint32_t MaxCost,
781[[nodiscard]]
auto process_weighted_path_agents(
782 const World& world, std::span<PathAgentState> agents,
785 PathSubmitScope scope,
PathAgentRoutes* routes,
const Provider& provider,
787 auto stats = submit_path_agents(agents, runtime, scope, accounting);
788 (void)runtime.template process_weighted_batch<World, Class, MaxCost>(
789 world, policy, graph, provider);
790 add_path_agent_stats(
791 stats, apply_path_agent_results(agents, runtime, scope, routes));
792 stats.precheck_ruled_out = runtime.stats().precheck_ruled_out;
Definition path_runtime.h:197
Region graph storage specialized by dense or sparse residency policy.
Definition topology.h:382
Definition metadata_types.h:12
Aggregates rejected movement attempts by retry-relevant category.
Definition movement.h:55
Describes an adjacent move and any versions it expects to remain current.
Definition movement.h:41
Configures bounded direct movement and the dirty bits it emits.
Definition path_agent.h:78
Summarizes path submission, results, movement, and failure outcomes.
Definition path_agent.h:50
Owns index-paired route copies retained across scoped processing passes.
Definition path_agent.h:115
Stores one agent's goal, route cursor, and retry lifecycle state.
Definition path_agent.h:35
std::optional< PathStatus > last_result
Most recent search conclusion, absent before search or after invalidation.
Definition path_agent.h:41
std::uint64_t armed_tick
Flow-accounting admission stamp (see the tick-state goal APIs).
Definition path_agent.h:46
Specifies inclusive start and goal coordinates for a path query.
Definition request.h:10
Definition path_runtime.h:70
Definition path_runtime.h:29
Definition diagnostics.h:505