3#include <tess/ecs/adapter.h>
5#if defined(TESS_ENABLE_ENTT)
9 "Include <entt/entity/registry.hpp> before <tess/ecs/entt/entt_adapter.h>"
35 using entity_type = entt::entity;
37 [[nodiscard]]
static auto to_handle(entt::entity entity)
noexcept
39 if (entity == entt::null) {
40 return kNullEntityHandle;
42 return EntityHandle{
static_cast<std::uint64_t
>(entt::to_integral(entity))};
45 [[nodiscard]]
static auto to_entity(
EntityHandle handle)
noexcept
47 if (handle.is_null()) {
50 return static_cast<entt::entity
>(
static_cast<entt::id_type
>(handle.value));
56class EnttTilePositionAdapter {
58 explicit EnttTilePositionAdapter(entt::registry& registry) noexcept
59 : registry_(®istry) {}
61 [[nodiscard]]
auto position(entt::entity entity)
const ->
Coord3 {
65 void set_position(entt::entity entity,
Coord3 coord) {
70 entt::registry* registry_;
76 std::uint64_t agent_id = 0;
77 entt::entity entity = entt::null;
94 std::vector<EnttAgentEntry> entries{};
95 std::uint64_t next_agent_id = 0;
97 void reserve(std::size_t agent_capacity) {
98 batch.reserve(agent_capacity);
99 entries.reserve(agent_capacity);
105template <
typename World>
106[[nodiscard]]
auto ecs_tile_resolves(
const World& world,
Coord3 tile)
noexcept
108 const auto resolved = world.try_resolve(tile);
109 if (!resolved.has_value()) {
112 if constexpr (std::is_same_v<
typename World::residency_type,
114 if (!world.is_resident(resolved->chunk_key)) {
121template <
typename World>
122void ecs_mark_tile_dirty(World& world, Coord3 tile, std::uint32_t mask) {
124 world.mark_dirty(world.resolve(tile).chunk_key, mask,
125 Box3{tile, Extent3{1, 1, 1}});
138class EnttPathAgentSource {
140 EnttPathAgentSource(entt::registry& registry,
142 : registry_(®istry), context_(&context) {}
145 auto& entries = context_->entries;
150 entt::exclude<OffBoard>);
151 for (
const auto entity : view) {
152 entries.push_back(
EnttAgentEntry{view.template get<AgentId>(entity).value,
154 &view.template get<PathState>(entity)});
156 std::sort(entries.begin(), entries.end(),
158 return lhs.agent_id < rhs.agent_id;
162 info.count = entries.size();
165 for (
const auto& entry : entries) {
166 auto& state = *entry.state;
167 if (
const auto* goal = registry_->try_get<
PathGoal>(entry.entity)) {
168 if (!state.agent.has_goal || state.agent.goal != goal->coord) {
169 set_path_agent_goal(state.agent, goal->coord);
170 info.pathing_dirty =
true;
172 }
else if (state.agent.has_goal) {
173 clear_path_agent_goal(state.agent);
175 batch.push(EnttHandleAdapter::to_handle(entry.entity), state.agent);
181 entt::registry* registry_;
186template <
typename Position = EnttTilePositionAdapter>
194class EnttPathAgentSink {
197 Position position) noexcept
198 : registry_(®istry),
200 position_(
static_cast<Position&&
>(position)) {}
202 EnttPathAgentSink(entt::registry& registry,
204 requires std::constructible_from<Position, entt::registry&>
205 : EnttPathAgentSink(registry, context, Position(registry)) {}
208 const auto agents = batch.agents();
209 const auto& entries = context_->entries;
210 TESS_ASSERT(agents.size() == entries.size());
211 for (std::size_t i = 0; i < entries.size(); ++i) {
212 const auto entity = entries[i].entity;
213 const auto& agent = agents[i];
214 registry_->get<
PathState>(entity).agent = agent;
215 position_.set_position(entity, agent.position);
216 if (
const auto* goal = registry_->try_get<
PathGoal>(entity);
217 goal !=
nullptr && !agent.has_goal && agent.position == goal->coord) {
218 registry_->remove<
PathGoal>(entity);
224 entt::registry* registry_;
236template <
typename World,
typename OccupancyTag>
237[[nodiscard]]
auto spawn_entt_path_agent(
241 if (!detail::ecs_tile_resolves(world, position) ||
242 static_cast<bool>(world.template field<OccupancyTag>(position)) ||
243 !index.entity_at(position).is_null()) {
246 const auto entity = registry.create();
247 registry.emplace<AgentId>(entity, AgentId{context.next_agent_id++});
248 registry.emplace<TilePosition>(entity, TilePosition{position});
249 auto state = PathAgentState{};
250 state.position = position;
251 registry.emplace<PathState>(entity, PathState{state});
252 world.template field<OccupancyTag>(position) =
true;
253 const auto inserted =
254 index.insert(position, EnttHandleAdapter::to_handle(entity));
255 TESS_ASSERT(inserted);
256 static_cast<void>(inserted);
257 detail::ecs_mark_tile_dirty(world, position, dirty_mask);
258 if (render_deltas !=
nullptr) {
259 render_deltas->record_spawn(EnttHandleAdapter::to_handle(entity), position);
269[[nodiscard]]
inline auto spawn_entt_path_agent_off_board(
270 entt::registry& registry, EnttPathAgentContext& context) -> entt::entity {
271 const auto entity = registry.create();
272 registry.emplace<AgentId>(entity, AgentId{context.next_agent_id++});
273 registry.emplace<TilePosition>(entity, TilePosition{});
274 registry.emplace<PathState>(entity, PathState{});
275 registry.emplace<OffBoard>(entity);
285template <
typename World,
typename OccupancyTag>
286auto despawn_entt_path_agent(entt::registry& registry, World& world,
287 TileOccupancyIndex& index, entt::entity entity,
288 std::uint32_t dirty_mask = 0,
289 DeltaCollector* render_deltas =
nullptr) ->
bool {
290 const auto* state = registry.try_get<PathState>(entity);
291 if (state ==
nullptr) {
294 if (!registry.all_of<OffBoard>(entity)) {
295 const auto position = state->agent.position;
296 world.template field<OccupancyTag>(position) =
false;
297 const auto erased = index.erase(position);
298 TESS_ASSERT(erased == EnttHandleAdapter::to_handle(entity));
299 static_cast<void>(erased);
300 detail::ecs_mark_tile_dirty(world, position, dirty_mask);
301 if (render_deltas !=
nullptr) {
304 render_deltas->record_despawn(EnttHandleAdapter::to_handle(entity),
308 registry.destroy(entity);
318template <
typename World,
typename OccupancyTag>
319auto teleport_entt_path_agent(entt::registry& registry, World& world,
320 TileOccupancyIndex& index, entt::entity entity,
321 Coord3 to, std::uint32_t dirty_mask = 0,
322 DeltaCollector* render_deltas =
nullptr) ->
bool {
323 auto* state = registry.try_get<PathState>(entity);
324 if (state ==
nullptr || registry.all_of<OffBoard>(entity)) {
327 if (!detail::ecs_tile_resolves(world, to) ||
328 static_cast<bool>(world.template field<OccupancyTag>(to)) ||
329 !index.entity_at(to).is_null()) {
332 const auto from = state->agent.position;
333 world.template field<OccupancyTag>(from) =
false;
334 world.template field<OccupancyTag>(to) =
true;
335 index.move(from, to, EnttHandleAdapter::to_handle(entity));
336 auto fresh = PathAgentState{};
338 state->agent = fresh;
339 registry.get<TilePosition>(entity).coord = to;
340 detail::ecs_mark_tile_dirty(world, from, dirty_mask);
341 detail::ecs_mark_tile_dirty(world, to, dirty_mask);
342 if (render_deltas !=
nullptr) {
343 render_deltas->record_teleport(EnttHandleAdapter::to_handle(entity), from,
355template <
typename World,
typename OccupancyTag>
356auto park_entt_path_agent(entt::registry& registry, World& world,
357 TileOccupancyIndex& index, entt::entity entity,
358 std::uint32_t dirty_mask = 0,
359 DeltaCollector* render_deltas =
nullptr) ->
bool {
360 auto* state = registry.try_get<PathState>(entity);
361 if (state ==
nullptr || registry.all_of<OffBoard>(entity)) {
364 const auto position = state->agent.position;
365 world.template field<OccupancyTag>(position) =
false;
366 const auto erased = index.erase(position);
367 TESS_ASSERT(erased == EnttHandleAdapter::to_handle(entity));
368 static_cast<void>(erased);
369 clear_path_agent_goal(state->agent);
370 registry.emplace<OffBoard>(entity);
371 detail::ecs_mark_tile_dirty(world, position, dirty_mask);
372 if (render_deltas !=
nullptr) {
373 render_deltas->record_park(EnttHandleAdapter::to_handle(entity), position);
384template <
typename World,
typename OccupancyTag>
385auto place_entt_path_agent(entt::registry& registry, World& world,
386 TileOccupancyIndex& index, entt::entity entity,
387 Coord3 position, std::uint32_t dirty_mask = 0,
388 DeltaCollector* render_deltas =
nullptr) ->
bool {
389 auto* state = registry.try_get<PathState>(entity);
390 if (state ==
nullptr || !registry.all_of<OffBoard>(entity)) {
393 if (!detail::ecs_tile_resolves(world, position) ||
394 static_cast<bool>(world.template field<OccupancyTag>(position)) ||
395 !index.entity_at(position).is_null()) {
398 world.template field<OccupancyTag>(position) =
true;
399 const auto inserted =
400 index.insert(position, EnttHandleAdapter::to_handle(entity));
401 TESS_ASSERT(inserted);
402 static_cast<void>(inserted);
403 auto fresh = PathAgentState{};
404 fresh.position = position;
405 state->agent = fresh;
406 registry.get<TilePosition>(entity).coord = position;
407 registry.remove<OffBoard>(entity);
408 detail::ecs_mark_tile_dirty(world, position, dirty_mask);
409 if (render_deltas !=
nullptr) {
410 render_deltas->record_place(EnttHandleAdapter::to_handle(entity), position);
416inline void set_entt_path_agent_goal(entt::registry& registry,
417 entt::entity entity, Coord3 goal) {
418 registry.emplace_or_replace<PathGoal>(entity, PathGoal{goal});
422inline void clear_entt_path_agent_goal(entt::registry& registry,
423 entt::entity entity) {
424 registry.remove<PathGoal>(entity);
427template <
typename World,
typename ClassOrTag,
typename OccupancyTag,
428 typename ReservationTag,
typename Position = EnttTilePositionAdapter>
435[[nodiscard]]
auto tick_entt_unit_path_agents(
436 entt::registry& registry, EnttPathAgentContext& context, World& world,
437 PathRequestRuntime& runtime, TileOccupancyIndex& index,
438 PathAgentTickOptions options = {}, std::uint32_t movement_dirty_mask = 0,
439 const RegionGraphT<typename World::residency_type>* graph =
nullptr,
440 DeltaCollector* render_deltas =
nullptr) -> PathAgentTickStats {
441 EnttPathAgentSource source(registry, context);
442 EnttPathAgentSink<Position> sink(registry, context);
443 return tick_ecs_unit_path_agents<World, ClassOrTag, OccupancyTag,
445 context.tick_state, world, source, sink, context.batch, runtime, index,
446 options, movement_dirty_mask, graph, render_deltas);
449template <
typename World,
typename Class, std::uint32_t MaxCost,
450 typename OccupancyTag,
typename ReservationTag,
451 typename Position = EnttTilePositionAdapter>
453[[nodiscard]]
auto tick_entt_path_agents(
454 entt::registry& registry, EnttPathAgentContext& context, World& world,
455 PathRequestRuntime& runtime, TileOccupancyIndex& index,
456 PathAgentTickOptions options = {}, std::uint32_t movement_dirty_mask = 0,
457 const RegionGraphT<typename World::residency_type>* graph =
nullptr,
458 DeltaCollector* render_deltas =
nullptr) -> PathAgentTickStats {
459 EnttPathAgentSource source(registry, context);
460 EnttPathAgentSink<Position> sink(registry, context);
461 return tick_ecs_path_agents<World, Class, MaxCost, OccupancyTag,
463 context.tick_state, world, source, sink, context.batch, runtime, index,
464 options, movement_dirty_mask, graph, render_deltas);
467template <
typename World,
typename PassableTag,
typename CostTag,
468 std::uint32_t MaxCost,
typename OccupancyTag,
typename ReservationTag,
469 typename Position = EnttTilePositionAdapter>
471[[nodiscard]]
auto tick_entt_path_agents(
472 entt::registry& registry, EnttPathAgentContext& context, World& world,
473 PathRequestRuntime& runtime, TileOccupancyIndex& index,
474 PathAgentTickOptions options = {}, std::uint32_t movement_dirty_mask = 0,
475 const RegionGraphT<typename World::residency_type>* graph =
nullptr,
476 DeltaCollector* render_deltas =
nullptr) -> PathAgentTickStats {
477 EnttPathAgentSource source(registry, context);
478 EnttPathAgentSink<Position> sink(registry, context);
479 return tick_ecs_path_agents<World, PassableTag, CostTag, MaxCost,
480 OccupancyTag, ReservationTag>(
481 context.tick_state, world, source, sink, context.batch, runtime, index,
482 options, movement_dirty_mask, graph, render_deltas);
Definition delta_frame.h:226
Definition entity_handle.h:16
Definition entt_adapter.h:75
Definition entt_adapter.h:34
Definition entt_adapter.h:91
Owns the shared clock, world-dirty flag, and retained routes across ticks.
Definition path_agent_tick.h:13