3#include <tess/core/assert.h>
4#include <tess/ecs/entity_handle.h>
5#include <tess/sim/delta_frame.h>
6#include <tess/sim/path_agent.h>
7#include <tess/sim/path_agent_tick.h>
37 const typename A::entity_type& entity) {
38 typename A::entity_type;
39 { adapter.to_handle(entity) }
noexcept -> std::same_as<EntityHandle>;
41 adapter.to_entity(handle)
42 }
noexcept -> std::same_as<typename A::entity_type>;
46template <
typename A,
typename Entity>
48 const Entity& entity,
Coord3 coord) {
49 { const_adapter.position(entity) } -> std::convertible_to<Coord3>;
50 adapter.set_position(entity, coord);
55 std::size_t count = 0;
60 bool pathing_dirty =
false;
71 void reserve(std::size_t agent_capacity) {
72 handles_.reserve(agent_capacity);
73 agents_.reserve(agent_capacity);
76 void clear()
noexcept {
82 handles_.push_back(handle);
83 agents_.push_back(agent);
86 [[nodiscard]]
auto size()
const noexcept -> std::size_t {
87 return agents_.size();
90 [[nodiscard]]
auto agents()
noexcept -> std::span<PathAgentState> {
94 [[nodiscard]]
auto agents()
const noexcept
95 -> std::span<const PathAgentState> {
99 [[nodiscard]]
auto handles()
const noexcept -> std::span<const EntityHandle> {
104 std::vector<EntityHandle> handles_;
105 std::vector<PathAgentState> agents_;
116 { source.collect(batch) } -> std::same_as<PathAgentCollectInfo>;
127 requires(S& sink,
const PathAgentBatch& batch) { sink.apply(batch); };
140 std::uint64_t value = 0;
180 void reserve(std::size_t entity_capacity) {
181 auto target = std::size_t{8};
182 while (target < entity_capacity * 2) {
185 if (target > slots_.size()) {
196 TESS_ASSERT_MSG(!entity.is_null(),
197 "TileOccupancyIndex cannot map a null entity");
200 TESS_ASSERT_MSG(tile.x >= 0 && tile.y >= 0 && tile.z >= 0,
201 "TileOccupancyIndex stores world tiles, which are "
203 if (slots_.empty() || (size_ + 1) * 2 > slots_.size()) {
204 rehash(slots_.empty() ? 8 : slots_.size() * 2);
206 auto index = probe_start(tile);
208 auto& slot = slots_[index];
209 if (slot.entity.is_null()) {
210 slot = Slot{tile, entity};
214 if (slot.tile == tile) {
215 return slot.entity == entity;
217 index = (index + 1) & mask();
223 if (slots_.empty()) {
224 return kNullEntityHandle;
226 auto index = probe_start(tile);
228 auto& slot = slots_[index];
229 if (slot.entity.is_null()) {
230 return kNullEntityHandle;
232 if (slot.tile == tile) {
235 index = (index + 1) & mask();
237 const auto erased = slots_[index].entity;
244 next = (next + 1) & mask();
245 const auto& candidate = slots_[next];
246 if (candidate.entity.is_null()) {
249 const auto ideal = probe_start(candidate.tile);
250 const auto in_gap = (next > hole) ? (ideal > hole && ideal <= next)
251 : (ideal > hole || ideal <= next);
253 slots_[hole] = candidate;
257 slots_[hole] = Slot{};
268 TESS_ASSERT_MSG(to.x >= 0 && to.y >= 0 && to.z >= 0,
269 "TileOccupancyIndex stores world tiles, which are "
271 const auto erased = erase(from);
272 TESS_ASSERT_MSG(erased == entity,
273 "TileOccupancyIndex::move source held another entity");
274 static_cast<void>(erased);
275 TESS_ASSERT_MSG(entity_at(to).is_null(),
276 "TileOccupancyIndex::move destination already mapped");
277 auto index = probe_start(to);
278 while (!slots_[index].entity.is_null()) {
279 index = (index + 1) & mask();
281 slots_[index] = Slot{to, entity};
286 if (slots_.empty()) {
287 return kNullEntityHandle;
289 auto index = probe_start(tile);
291 const auto& slot = slots_[index];
292 if (slot.entity.is_null()) {
293 return kNullEntityHandle;
295 if (slot.tile == tile) {
298 index = (index + 1) & mask();
302 [[nodiscard]]
auto size()
const noexcept -> std::size_t {
return size_; }
304 void clear()
noexcept {
305 for (
auto& slot : slots_) {
317 [[nodiscard]]
auto mask()
const noexcept -> std::size_t {
318 return slots_.size() - 1;
321 [[nodiscard]]
static auto mix(std::uint64_t value)
noexcept -> std::uint64_t {
322 value += 0x9E3779B97F4A7C15ULL;
323 value = (value ^ (value >> 30U)) * 0xBF58476D1CE4E5B9ULL;
324 value = (value ^ (value >> 27U)) * 0x94D049BB133111EBULL;
325 return value ^ (value >> 31U);
328 [[nodiscard]]
auto probe_start(
Coord3 tile)
const noexcept -> std::size_t {
341 mix(
static_cast<std::uint64_t
>(tile.x) * 0x9E3779B97F4A7C15ULL ^
342 static_cast<std::uint64_t
>(tile.y) * 0xC2B2AE3D27D4EB4FULL ^
343 static_cast<std::uint64_t
>(tile.z) * 0x165667B19E3779F9ULL);
344 return static_cast<std::size_t
>(hash) & mask();
347 void rehash(std::size_t new_capacity) {
348 auto old = std::vector<Slot>(new_capacity);
351 for (
const auto& slot : old) {
352 if (!slot.entity.is_null()) {
353 auto index = probe_start(slot.tile);
354 while (!slots_[index].entity.is_null()) {
355 index = (index + 1) & mask();
357 slots_[index] = slot;
363 std::vector<Slot> slots_;
364 std::size_t size_ = 0;
367template <
typename World,
typename ClassOrTag,
typename OccupancyTag,
368 typename ReservationTag>
375inline auto advance_path_agents_with_index(
378 std::uint32_t movement_dirty_mask = 0,
380 const auto handles = batch.handles();
381 return advance_path_agents_with_movement<
World, ClassOrTag, OccupancyTag,
383 world, batch.agents(), runtime, max_steps, movement_dirty_mask,
384 [&index, handles, render_deltas](std::size_t agent_index,
Coord3 from,
386 index.move(from, to, handles[agent_index]);
387 if (render_deltas !=
nullptr) {
388 render_deltas->record_move(handles[agent_index], from, to);
393template <
typename World,
typename ClassOrTag,
typename OccupancyTag,
402[[nodiscard]]
auto tick_ecs_unit_path_agents(
406 std::uint32_t movement_dirty_mask = 0,
409 const auto info = source.collect(batch);
410 if (info.pathing_dirty) {
411 mark_pathing_dirty(state);
415 stats.tick = advance_sim_tick(state.clock);
416 if (render_deltas !=
nullptr) {
418 render_deltas->begin_tick(stats.tick);
421 const bool repath_needed =
422 prepare_path_agent_processing(batch.agents(), options, stats);
423 if (state.pathing_dirty || repath_needed) {
424 stats.pathing = process_unit_path_agents<World, ClassOrTag>(
425 world, batch.agents(), runtime, options.cache_policy, graph);
426 stats.processed_paths =
true;
427 state.pathing_dirty =
false;
430 stats.movement = advance_path_agents_with_index<
World, ClassOrTag,
431 OccupancyTag, ReservationTag>(
432 world, batch, runtime, index, options.max_steps, movement_dirty_mask,
438template <
typename World,
typename Class, std::uint32_t MaxCost,
439 typename OccupancyTag,
typename ReservationTag,
447[[nodiscard]]
auto tick_ecs_path_agents(
451 std::uint32_t movement_dirty_mask = 0,
454 const auto info = source.collect(batch);
455 if (info.pathing_dirty) {
456 mark_pathing_dirty(state);
460 stats.tick = advance_sim_tick(state.clock);
461 if (render_deltas !=
nullptr) {
463 render_deltas->begin_tick(stats.tick);
466 const bool repath_needed =
467 prepare_path_agent_processing(batch.agents(), options, stats);
468 if (state.pathing_dirty || repath_needed) {
469 stats.pathing = process_weighted_path_agents<World, Class, MaxCost>(
470 world, batch.agents(), runtime, options.cache_policy, graph);
471 stats.processed_paths =
true;
472 state.pathing_dirty =
false;
475 stats.movement = advance_path_agents_with_index<
World, Class, OccupancyTag,
477 world, batch, runtime, index, options.max_steps, movement_dirty_mask,
483template <
typename World,
typename PassableTag,
typename CostTag,
484 std::uint32_t MaxCost,
typename OccupancyTag,
typename ReservationTag,
492[[nodiscard]]
auto tick_ecs_path_agents(
496 std::uint32_t movement_dirty_mask = 0,
499 const auto info = source.collect(batch);
500 if (info.pathing_dirty) {
501 mark_pathing_dirty(state);
505 stats.tick = advance_sim_tick(state.clock);
506 if (render_deltas !=
nullptr) {
508 render_deltas->begin_tick(stats.tick);
511 const bool repath_needed =
512 prepare_path_agent_processing(batch.agents(), options, stats);
513 if (state.pathing_dirty || repath_needed) {
515 process_weighted_path_agents<World, PassableTag, CostTag, MaxCost>(
516 world, batch.agents(), runtime, options.cache_policy, graph);
517 stats.processed_paths =
true;
518 state.pathing_dirty =
false;
521 stats.movement = advance_path_agents_with_index<
World, PassableTag,
522 OccupancyTag, ReservationTag>(
523 world, batch, runtime, index, options.max_steps, movement_dirty_mask,
Definition delta_frame.h:226
Definition path_runtime.h:92
Region graph storage specialized by dense or sparse residency policy.
Definition topology.h:309
Definition entity_handle.h:16
Summarizes path submission, results, movement, and failure outcomes.
Definition path_agent.h:44
Stores one agent's goal, route cursor, and retry lifecycle state.
Definition path_agent.h:32
Configures per-tick movement, caching, and blocked-agent retry limits.
Definition path_agent_tick.h:29
Owns the shared clock, world-dirty flag, and retained routes across ticks.
Definition path_agent_tick.h:13
Summarizes path planning and movement performed during one tick.
Definition path_agent_tick.h:46