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>
38 const typename A::entity_type& entity) {
39 typename A::entity_type;
40 { adapter.to_handle(entity) }
noexcept -> std::same_as<EntityHandle>;
42 adapter.to_entity(handle)
43 }
noexcept -> std::same_as<typename A::entity_type>;
47template <
typename A,
typename Entity>
49 const Entity& entity,
Coord3 coord) {
50 { const_adapter.position(entity) } -> std::convertible_to<Coord3>;
51 adapter.set_position(entity, coord);
56 std::size_t count = 0;
61 bool pathing_dirty =
false;
72 void reserve(std::size_t agent_capacity) {
73 handles_.reserve(agent_capacity);
74 agents_.reserve(agent_capacity);
77 void clear()
noexcept {
83 handles_.push_back(handle);
84 agents_.push_back(agent);
87 [[nodiscard]]
auto size()
const noexcept -> std::size_t {
88 return agents_.size();
91 [[nodiscard]]
auto agents()
noexcept -> std::span<PathAgentState> {
95 [[nodiscard]]
auto agents()
const noexcept
96 -> std::span<const PathAgentState> {
100 [[nodiscard]]
auto handles()
const noexcept -> std::span<const EntityHandle> {
105 std::vector<EntityHandle> handles_;
106 std::vector<PathAgentState> agents_;
117 { source.collect(batch) } -> std::same_as<PathAgentCollectInfo>;
128 requires(S& sink,
const PathAgentBatch& batch) { sink.apply(batch); };
141 std::uint64_t value = 0;
181 void reserve(std::size_t entity_capacity) {
182 auto target = std::size_t{8};
183 while (target < entity_capacity * 2) {
186 if (target > slots_.size()) {
198 if (entity.is_null()) {
203 TESS_ASSERT_MSG(tile.x >= 0 && tile.y >= 0 && tile.z >= 0,
204 "TileOccupancyIndex stores world tiles, which are "
206 if (slots_.empty() || (size_ + 1) * 2 > slots_.size()) {
207 rehash(slots_.empty() ? 8 : slots_.size() * 2);
209 auto index = probe_start(tile);
211 auto& slot = slots_[index];
212 if (slot.entity.is_null()) {
213 slot = Slot{tile, entity};
217 if (slot.tile == tile) {
218 return slot.entity == entity;
220 index = (index + 1) & mask();
226 if (slots_.empty()) {
227 return kNullEntityHandle;
229 auto index = probe_start(tile);
231 auto& slot = slots_[index];
232 if (slot.entity.is_null()) {
233 return kNullEntityHandle;
235 if (slot.tile == tile) {
238 index = (index + 1) & mask();
240 const auto erased = slots_[index].entity;
247 next = (next + 1) & mask();
248 const auto& candidate = slots_[next];
249 if (candidate.entity.is_null()) {
252 const auto ideal = probe_start(candidate.tile);
253 const auto in_gap = (next > hole) ? (ideal > hole && ideal <= next)
254 : (ideal > hole || ideal <= next);
256 slots_[hole] = candidate;
260 slots_[hole] = Slot{};
271 TESS_ASSERT_MSG(to.x >= 0 && to.y >= 0 && to.z >= 0,
272 "TileOccupancyIndex stores world tiles, which are "
274 const auto erased = erase(from);
275 TESS_ASSERT_MSG(erased == entity,
276 "TileOccupancyIndex::move source held another entity");
277 static_cast<void>(erased);
278 TESS_ASSERT_MSG(entity_at(to).is_null(),
279 "TileOccupancyIndex::move destination already mapped");
280 auto index = probe_start(to);
281 while (!slots_[index].entity.is_null()) {
282 index = (index + 1) & mask();
284 slots_[index] = Slot{to, entity};
289 if (slots_.empty()) {
290 return kNullEntityHandle;
292 auto index = probe_start(tile);
294 const auto& slot = slots_[index];
295 if (slot.entity.is_null()) {
296 return kNullEntityHandle;
298 if (slot.tile == tile) {
301 index = (index + 1) & mask();
305 [[nodiscard]]
auto size()
const noexcept -> std::size_t {
return size_; }
307 void clear()
noexcept {
308 for (
auto& slot : slots_) {
320 [[nodiscard]]
auto mask()
const noexcept -> std::size_t {
321 return slots_.size() - 1;
324 [[nodiscard]]
static auto mix(std::uint64_t value)
noexcept -> std::uint64_t {
325 value += 0x9E3779B97F4A7C15ULL;
326 value = (value ^ (value >> 30U)) * 0xBF58476D1CE4E5B9ULL;
327 value = (value ^ (value >> 27U)) * 0x94D049BB133111EBULL;
328 return value ^ (value >> 31U);
331 [[nodiscard]]
auto probe_start(
Coord3 tile)
const noexcept -> std::size_t {
344 mix(
static_cast<std::uint64_t
>(tile.x) * 0x9E3779B97F4A7C15ULL ^
345 static_cast<std::uint64_t
>(tile.y) * 0xC2B2AE3D27D4EB4FULL ^
346 static_cast<std::uint64_t
>(tile.z) * 0x165667B19E3779F9ULL);
347 return static_cast<std::size_t
>(hash) & mask();
350 void rehash(std::size_t new_capacity) {
351 auto old = std::vector<Slot>(new_capacity);
354 for (
const auto& slot : old) {
355 if (!slot.entity.is_null()) {
356 auto index = probe_start(slot.tile);
357 while (!slots_[index].entity.is_null()) {
358 index = (index + 1) & mask();
360 slots_[index] = slot;
366 std::vector<Slot> slots_;
367 std::size_t size_ = 0;
370template <
typename World,
typename ClassOrTag,
typename OccupancyTag,
371 typename ReservationTag>
378inline auto advance_path_agents_with_index(
381 DeltaCollector* render_deltas =
nullptr) -> PathAgentFrameStats {
382 const auto handles = batch.handles();
383 return advance_path_agents_with_movement<World, ClassOrTag, OccupancyTag,
385 world, batch.agents(), runtime, options,
386 [&index, handles, render_deltas](std::size_t agent_index, Coord3 from,
388 index.move(from, to, handles[agent_index]);
389 if (render_deltas !=
nullptr) {
390 render_deltas->record_move(handles[agent_index], from, to);
395template <
typename World,
typename ClassOrTag,
typename OccupancyTag,
404[[nodiscard]]
auto tick_ecs_unit_path_agents(
410 const auto info = source.collect(batch);
411 if (info.pathing_dirty) {
412 mark_pathing_dirty(state);
416 stats.tick = advance_sim_tick(state.clock);
417 if (render_deltas !=
nullptr) {
419 render_deltas->begin_tick(stats.tick);
422 const bool repath_needed =
423 prepare_path_agent_processing(batch.agents(), options, stats);
424 if (state.pathing_dirty || repath_needed) {
425 stats.pathing = process_unit_path_agents<World, ClassOrTag>(
426 world, batch.agents(), runtime, options.cache_policy, graph);
427 stats.processed_paths =
true;
428 state.pathing_dirty =
false;
431 stats.movement = advance_path_agents_with_index<
World, ClassOrTag,
432 OccupancyTag, ReservationTag>(
433 world, batch, runtime, index,
440template <
typename World,
typename Class, std::uint32_t MaxCost,
441 typename OccupancyTag,
typename ReservationTag,
449[[nodiscard]]
auto tick_ecs_path_agents(
455 const auto info = source.collect(batch);
456 if (info.pathing_dirty) {
457 mark_pathing_dirty(state);
461 stats.tick = advance_sim_tick(state.clock);
462 if (render_deltas !=
nullptr) {
464 render_deltas->begin_tick(stats.tick);
467 const bool repath_needed =
468 prepare_path_agent_processing(batch.agents(), options, stats);
469 if (state.pathing_dirty || repath_needed) {
470 stats.pathing = process_weighted_path_agents<World, Class, MaxCost>(
471 world, batch.agents(), runtime, options.cache_policy, graph);
472 stats.processed_paths =
true;
473 state.pathing_dirty =
false;
476 stats.movement = advance_path_agents_with_index<
World, Class, OccupancyTag,
478 world, batch, runtime, index,
Definition delta_frame.h:318
Definition path_runtime.h:197
Region graph storage specialized by dense or sparse residency policy.
Definition topology.h:382
Definition entity_handle.h:16
Configures bounded direct movement and the dirty bits it emits.
Definition path_agent.h:78
Stores one agent's goal, route cursor, and retry lifecycle state.
Definition path_agent.h:35
Configures per-tick movement, caching, and blocked-agent retry limits.
Definition path_agent_tick.h:84
Definition path_agent_tick.h:17
Summarizes path planning and movement performed during one tick.
Definition path_agent_tick.h:531