3#include <tess/path/path_runtime.h>
4#include <tess/path/precheck.h>
5#include <tess/sim/movement.h>
23enum class PathAgentPhase : std::uint8_t {
36 std::size_t path_index = 0;
37 PathStatus status = PathStatus::NoPath;
38 PathAgentPhase phase = PathAgentPhase::Idle;
39 bool has_goal =
false;
40 std::uint32_t blocked_retries = 0;
45 std::size_t submitted = 0;
46 std::size_t completed = 0;
47 std::size_t found = 0;
48 std::size_t invalid_start = 0;
49 std::size_t invalid_goal = 0;
50 std::size_t no_path = 0;
54 std::size_t indeterminate = 0;
57 std::size_t precheck_ruled_out = 0;
58 std::size_t advanced = 0;
59 std::size_t arrived = 0;
60 std::size_t blocked_waits = 0;
72enum class PathSubmitScope : std::uint8_t {
89 std::vector<std::vector<Coord3>> routes;
91 void ensure_size(std::size_t count) {
92 if (routes.size() < count) {
101 agent.path_index = 0;
102 agent.status = PathStatus::NoPath;
103 agent.phase = PathAgentPhase::NeedsPath;
104 agent.blocked_retries = 0;
105 agent.has_goal =
true;
109inline void clear_path_agent_goal(PathAgentState& agent)
noexcept {
112 agent.path_index = 0;
113 agent.status = PathStatus::NoPath;
114 agent.phase = PathAgentPhase::Idle;
115 agent.blocked_retries = 0;
116 agent.has_goal =
false;
120inline auto submit_path_agents(std::span<PathAgentState> agents,
122 PathSubmitScope scope = PathSubmitScope::All)
125 runtime.clear_requests();
127 for (
auto& agent : agents) {
128 if (scope == PathSubmitScope::NeedsOnly &&
129 agent.phase == PathAgentPhase::Following) {
134 agent.path_index = 0;
135 if (!agent.has_goal || agent.phase == PathAgentPhase::Unreachable) {
138 if (agent.position == agent.goal) {
139 clear_path_agent_goal(agent);
143 agent.ticket = runtime.submit(
PathRequest{agent.position, agent.goal});
152 PathStatus status)
noexcept {
154 case PathStatus::Found:
157 case PathStatus::InvalidStart:
158 ++stats.invalid_start;
160 case PathStatus::InvalidGoal:
161 ++stats.invalid_goal;
163 case PathStatus::NoPath:
166 case PathStatus::Indeterminate:
167 ++stats.indeterminate;
174inline auto apply_path_agent_results(std::span<PathAgentState> agents,
176 PathSubmitScope scope,
180 if (routes !=
nullptr) {
183 routes->ensure_size(agents.size());
186 for (std::size_t i = 0; i < agents.size(); ++i) {
187 auto& agent = agents[i];
188 if (scope == PathSubmitScope::NeedsOnly &&
189 agent.phase == PathAgentPhase::Following) {
194 if (!agent.has_goal || agent.position == agent.goal ||
195 agent.phase == PathAgentPhase::Unreachable) {
199 const auto result = runtime.result(agent.ticket);
200 agent.status = result.status;
201 agent.path_index = 0;
202 if (result.status == PathStatus::Found) {
203 agent.phase = PathAgentPhase::Following;
204 agent.blocked_retries = 0;
205 if (routes !=
nullptr) {
206 routes->routes[i].assign(result.path.begin(), result.path.end());
211 agent.phase = PathAgentPhase::Blocked;
212 if (routes !=
nullptr) {
213 routes->routes[i].clear();
217 record_path_agent_status(stats, result.status);
224inline auto apply_path_agent_results(std::span<PathAgentState> agents,
227 return apply_path_agent_results(agents, runtime, PathSubmitScope::All,
232inline auto advance_path_agents(std::span<PathAgentState> agents,
234 std::size_t max_steps = 1)
237 if (max_steps == 0) {
241 for (
auto& agent : agents) {
242 if (!agent.has_goal || agent.status != PathStatus::Found) {
246 const auto result = runtime.result(agent.ticket);
247 if (result.status != PathStatus::Found || result.path.empty()) {
251 for (std::size_t step = 0; step < max_steps; ++step) {
252 if (agent.path_index + 1 >= result.path.size()) {
256 agent.position = result.path[agent.path_index];
258 if (agent.position == agent.goal) {
259 clear_path_agent_goal(agent);
260 agent.status = PathStatus::Found;
276template <
typename World,
typename ClassOrTag,
typename OccupancyTag,
277 typename ReservationTag,
typename OnCommit>
278 requires std::invocable<OnCommit&, std::size_t, Coord3, Coord3>
280inline auto advance_path_agents_with_movement(
World& world,
281 std::span<PathAgentState> agents,
283 std::size_t max_steps,
284 std::uint32_t movement_dirty_mask,
285 OnCommit&& on_commit)
288 if (max_steps == 0) {
292 for (std::size_t agent_index = 0; agent_index < agents.size();
294 auto& agent = agents[agent_index];
295 if (!agent.has_goal || agent.status != PathStatus::Found) {
299 const auto result = runtime.result(agent.ticket);
300 if (result.status != PathStatus::Found || result.path.empty()) {
304 for (std::size_t step = 0; step < max_steps; ++step) {
305 if (agent.path_index + 1 >= result.path.size()) {
309 const auto from = agent.position;
310 const auto to = result.path[agent.path_index + 1];
311 const auto movement =
312 commit_movement_intent<
World, ClassOrTag, OccupancyTag,
315 if (movement.status != MovementStatus::Moved) {
316 record_movement_failure(stats.movement_failures, movement.status);
317 if (is_transient_movement_failure(movement.status)) {
326 agent.phase = PathAgentPhase::Blocked;
327 ++stats.blocked_waits;
331 agent.status = PathStatus::NoPath;
332 agent.phase = PathAgentPhase::Unreachable;
339 on_commit(agent_index, from, to);
341 if (agent.position == agent.goal) {
342 clear_path_agent_goal(agent);
343 agent.status = PathStatus::Found;
353template <
typename World,
typename ClassOrTag,
typename OccupancyTag,
354 typename ReservationTag>
356inline auto advance_path_agents_with_movement(
357 World& world, std::span<PathAgentState> agents,
360 return advance_path_agents_with_movement<
World, ClassOrTag, OccupancyTag,
362 world, agents, runtime, max_steps, movement_dirty_mask,
371inline auto advance_path_agents(std::span<PathAgentState> agents,
373 std::size_t max_steps = 1)
377 TESS_ASSERT(routes.routes.size() >= agents.size());
379 if (max_steps == 0) {
383 for (std::size_t i = 0; i < agents.size(); ++i) {
384 auto& agent = agents[i];
385 if (!agent.has_goal || agent.status != PathStatus::Found) {
389 const auto& route = routes.routes[i];
394 for (std::size_t step = 0; step < max_steps; ++step) {
395 if (agent.path_index + 1 >= route.size()) {
399 agent.position = route[agent.path_index];
401 if (agent.position == agent.goal) {
402 clear_path_agent_goal(agent);
403 agent.status = PathStatus::Found;
413template <
typename World,
typename ClassOrTag,
typename OccupancyTag,
414 typename ReservationTag,
typename OnCommit>
415 requires std::invocable<OnCommit&, std::size_t, Coord3, Coord3>
417inline auto advance_path_agents_with_movement(
World& world,
418 std::span<PathAgentState> agents,
420 std::size_t max_steps,
421 std::uint32_t movement_dirty_mask,
422 OnCommit&& on_commit)
425 TESS_ASSERT(routes.routes.size() >= agents.size());
427 if (max_steps == 0) {
431 for (std::size_t agent_index = 0; agent_index < agents.size();
433 auto& agent = agents[agent_index];
434 if (!agent.has_goal || agent.status != PathStatus::Found) {
438 const auto& route = routes.routes[agent_index];
443 for (std::size_t step = 0; step < max_steps; ++step) {
444 if (agent.path_index + 1 >= route.size()) {
448 const auto from = agent.position;
449 const auto to = route[agent.path_index + 1];
450 const auto movement =
451 commit_movement_intent<
World, ClassOrTag, OccupancyTag,
454 if (movement.status != MovementStatus::Moved) {
455 record_movement_failure(stats.movement_failures, movement.status);
456 if (is_transient_movement_failure(movement.status)) {
459 agent.phase = PathAgentPhase::Blocked;
460 ++stats.blocked_waits;
462 agent.status = PathStatus::NoPath;
463 agent.phase = PathAgentPhase::Unreachable;
470 on_commit(agent_index, from, to);
472 if (agent.position == agent.goal) {
473 clear_path_agent_goal(agent);
474 agent.status = PathStatus::Found;
484template <
typename World,
typename ClassOrTag,
typename OccupancyTag,
485 typename ReservationTag>
487inline auto advance_path_agents_with_movement(
488 World& world, std::span<PathAgentState> agents,
491 return advance_path_agents_with_movement<
World, ClassOrTag, OccupancyTag,
493 world, agents, routes, max_steps, movement_dirty_mask,
500 lhs.submitted += rhs.submitted;
501 lhs.completed += rhs.completed;
502 lhs.found += rhs.found;
503 lhs.invalid_start += rhs.invalid_start;
504 lhs.invalid_goal += rhs.invalid_goal;
505 lhs.no_path += rhs.no_path;
506 lhs.indeterminate += rhs.indeterminate;
507 lhs.precheck_ruled_out += rhs.precheck_ruled_out;
508 lhs.advanced += rhs.advanced;
509 lhs.arrived += rhs.arrived;
510 lhs.blocked_waits += rhs.blocked_waits;
511 lhs.movement_failures.invalid += rhs.movement_failures.invalid;
512 lhs.movement_failures.blocked += rhs.movement_failures.blocked;
513 lhs.movement_failures.occupied += rhs.movement_failures.occupied;
514 lhs.movement_failures.reserved += rhs.movement_failures.reserved;
515 lhs.movement_failures.stale_version += rhs.movement_failures.stale_version;
516 lhs.movement_failures.stale_topology += rhs.movement_failures.stale_topology;
519template <
typename World,
typename ClassOrTag>
521[[nodiscard]]
auto process_unit_path_agents(
522 const World& world, std::span<PathAgentState> agents,
525 PathSubmitScope scope = PathSubmitScope::All,
527 auto stats = submit_path_agents(agents, runtime, scope);
528 (void)runtime.template process_unit_cached<World, ClassOrTag>(world, policy,
530 add_path_agent_stats(
531 stats, apply_path_agent_results(agents, runtime, scope, routes));
532 stats.precheck_ruled_out = runtime.stats().precheck_ruled_out;
536template <
typename World,
typename Class, std::u
int32_t MaxCost>
538[[nodiscard]]
auto process_weighted_path_agents(
539 const World& world, std::span<PathAgentState> agents,
542 PathSubmitScope scope = PathSubmitScope::All,
544 auto stats = submit_path_agents(agents, runtime, scope);
545 (void)runtime.template process_weighted_batch<World, Class, MaxCost>(
546 world, policy, graph);
547 add_path_agent_stats(
548 stats, apply_path_agent_results(agents, runtime, scope, routes));
549 stats.precheck_ruled_out = runtime.stats().precheck_ruled_out;
553template <
typename World,
typename PassableTag,
typename CostTag,
554 std::uint32_t MaxCost>
556[[nodiscard]]
auto process_weighted_path_agents(
557 const World& world, std::span<PathAgentState> agents,
560 PathSubmitScope scope = PathSubmitScope::All,
562 auto stats = submit_path_agents(agents, runtime, scope);
564 .
template process_weighted_batch<World, PassableTag, CostTag, MaxCost>(
565 world, policy, graph);
566 add_path_agent_stats(
567 stats, apply_path_agent_results(agents, runtime, scope, routes));
568 stats.precheck_ruled_out = runtime.stats().precheck_ruled_out;
Definition path_runtime.h:92
Region graph storage specialized by dense or sparse residency policy.
Definition topology.h:309
Aggregates rejected movement attempts by retry-relevant category.
Definition movement.h:53
Describes an adjacent move and any versions it expects to remain current.
Definition movement.h:39
Summarizes path submission, results, movement, and failure outcomes.
Definition path_agent.h:44
Owns index-paired route copies retained across scoped processing passes.
Definition path_agent.h:88
Stores one agent's goal, route cursor, and retry lifecycle state.
Definition path_agent.h:32
Specifies inclusive start and goal coordinates for a path query.
Definition path.h:54
Definition path_runtime.h:32
Definition path_runtime.h:26