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, DirtyMask 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(
240 DeltaCollector* render_deltas =
nullptr) -> entt::entity {
241 if (!detail::ecs_tile_resolves(world, position) ||
242 static_cast<bool>(world.template field<OccupancyTag>(position)) ||
243 !index.entity_at(position).is_null()) {
249 index.reserve(index.size() + 1);
250 const auto entity = registry.create();
251 registry.emplace<AgentId>(entity, AgentId{context.next_agent_id++});
252 registry.emplace<TilePosition>(entity, TilePosition{position});
253 auto state = PathAgentState{};
254 state.position = position;
255 registry.emplace<PathState>(entity, PathState{state});
256 world.template field<OccupancyTag>(position) =
true;
257 const auto inserted =
258 index.insert(position, EnttHandleAdapter::to_handle(entity));
259 TESS_ASSERT(inserted);
260 static_cast<void>(inserted);
261 detail::ecs_mark_tile_dirty(world, position, dirty_mask);
262 if (render_deltas !=
nullptr) {
263 render_deltas->record_spawn(EnttHandleAdapter::to_handle(entity), position);
273[[nodiscard]]
inline auto spawn_entt_path_agent_off_board(
274 entt::registry& registry, EnttPathAgentContext& context) -> entt::entity {
275 const auto entity = registry.create();
276 registry.emplace<AgentId>(entity, AgentId{context.next_agent_id++});
277 registry.emplace<TilePosition>(entity, TilePosition{});
278 registry.emplace<PathState>(entity, PathState{});
279 registry.emplace<OffBoard>(entity);
289template <
typename World,
typename OccupancyTag>
290auto despawn_entt_path_agent(entt::registry& registry, World& world,
291 TileOccupancyIndex& index, entt::entity entity,
292 DirtyMask dirty_mask = {},
293 DeltaCollector* render_deltas =
nullptr) ->
bool {
294 const auto* state = registry.try_get<PathState>(entity);
295 if (state ==
nullptr) {
298 if (!registry.all_of<OffBoard>(entity)) {
299 const auto position = state->agent.position;
300 world.template field<OccupancyTag>(position) =
false;
301 const auto erased = index.erase(position);
302 TESS_ASSERT(erased == EnttHandleAdapter::to_handle(entity));
303 static_cast<void>(erased);
304 detail::ecs_mark_tile_dirty(world, position, dirty_mask);
305 if (render_deltas !=
nullptr) {
308 render_deltas->record_despawn(EnttHandleAdapter::to_handle(entity),
312 registry.destroy(entity);
322template <
typename World,
typename OccupancyTag>
323auto teleport_entt_path_agent(entt::registry& registry, World& world,
324 TileOccupancyIndex& index, entt::entity entity,
325 Coord3 to, DirtyMask dirty_mask = {},
326 DeltaCollector* render_deltas =
nullptr) ->
bool {
327 auto* state = registry.try_get<PathState>(entity);
328 if (state ==
nullptr || registry.all_of<OffBoard>(entity)) {
331 if (!detail::ecs_tile_resolves(world, to) ||
332 static_cast<bool>(world.template field<OccupancyTag>(to)) ||
333 !index.entity_at(to).is_null()) {
336 const auto from = state->agent.position;
337 world.template field<OccupancyTag>(from) =
false;
338 world.template field<OccupancyTag>(to) =
true;
339 index.move(from, to, EnttHandleAdapter::to_handle(entity));
340 auto fresh = PathAgentState{};
342 state->agent = fresh;
343 registry.get<TilePosition>(entity).coord = to;
344 detail::ecs_mark_tile_dirty(world, from, dirty_mask);
345 detail::ecs_mark_tile_dirty(world, to, dirty_mask);
346 if (render_deltas !=
nullptr) {
347 render_deltas->record_teleport(EnttHandleAdapter::to_handle(entity), from,
359template <
typename World,
typename OccupancyTag>
360auto park_entt_path_agent(entt::registry& registry, World& world,
361 TileOccupancyIndex& index, entt::entity entity,
362 DirtyMask dirty_mask = {},
363 DeltaCollector* render_deltas =
nullptr) ->
bool {
364 auto* state = registry.try_get<PathState>(entity);
365 if (state ==
nullptr || registry.all_of<OffBoard>(entity)) {
368 const auto position = state->agent.position;
369 world.template field<OccupancyTag>(position) =
false;
370 const auto erased = index.erase(position);
371 TESS_ASSERT(erased == EnttHandleAdapter::to_handle(entity));
372 static_cast<void>(erased);
373 clear_path_agent_goal(state->agent);
374 registry.emplace<OffBoard>(entity);
375 detail::ecs_mark_tile_dirty(world, position, dirty_mask);
376 if (render_deltas !=
nullptr) {
377 render_deltas->record_park(EnttHandleAdapter::to_handle(entity), position);
388template <
typename World,
typename OccupancyTag>
389auto place_entt_path_agent(entt::registry& registry, World& world,
390 TileOccupancyIndex& index, entt::entity entity,
391 Coord3 position, DirtyMask dirty_mask = {},
392 DeltaCollector* render_deltas =
nullptr) ->
bool {
393 auto* state = registry.try_get<PathState>(entity);
394 if (state ==
nullptr || !registry.all_of<OffBoard>(entity)) {
397 if (!detail::ecs_tile_resolves(world, position) ||
398 static_cast<bool>(world.template field<OccupancyTag>(position)) ||
399 !index.entity_at(position).is_null()) {
403 index.reserve(index.size() + 1);
404 world.template field<OccupancyTag>(position) =
true;
405 const auto inserted =
406 index.insert(position, EnttHandleAdapter::to_handle(entity));
407 TESS_ASSERT(inserted);
408 static_cast<void>(inserted);
409 auto fresh = PathAgentState{};
410 fresh.position = position;
411 state->agent = fresh;
412 registry.get<TilePosition>(entity).coord = position;
413 registry.remove<OffBoard>(entity);
414 detail::ecs_mark_tile_dirty(world, position, dirty_mask);
415 if (render_deltas !=
nullptr) {
416 render_deltas->record_place(EnttHandleAdapter::to_handle(entity), position);
422inline void set_entt_path_agent_goal(entt::registry& registry,
423 entt::entity entity, Coord3 goal) {
424 registry.emplace_or_replace<PathGoal>(entity, PathGoal{goal});
428inline void clear_entt_path_agent_goal(entt::registry& registry,
429 entt::entity entity) {
430 registry.remove<PathGoal>(entity);
433template <
typename World,
typename ClassOrTag,
typename OccupancyTag,
434 typename ReservationTag,
typename Position = EnttTilePositionAdapter>
441[[nodiscard]]
auto tick_entt_unit_path_agents(
442 entt::registry& registry, EnttPathAgentContext& context, World& world,
443 PathRequestRuntime& runtime, TileOccupancyIndex& index,
444 PathAgentTickOptions options = {},
445 const RegionGraphT<typename World::residency_type>* graph =
nullptr,
446 DeltaCollector* render_deltas =
nullptr) -> PathAgentTickStats {
447 EnttPathAgentSource source(registry, context);
448 EnttPathAgentSink<Position> sink(registry, context);
449 return tick_ecs_unit_path_agents<World, ClassOrTag, OccupancyTag,
451 context.tick_state, world, source, sink, context.batch, runtime, index,
452 options, graph, render_deltas);
455template <
typename World,
typename Class, std::uint32_t MaxCost,
456 typename OccupancyTag,
typename ReservationTag,
457 typename Position = EnttTilePositionAdapter>
459[[nodiscard]]
auto tick_entt_path_agents(
460 entt::registry& registry, EnttPathAgentContext& context, World& world,
461 PathRequestRuntime& runtime, TileOccupancyIndex& index,
462 PathAgentTickOptions options = {},
463 const RegionGraphT<typename World::residency_type>* graph =
nullptr,
464 DeltaCollector* render_deltas =
nullptr) -> PathAgentTickStats {
465 EnttPathAgentSource source(registry, context);
466 EnttPathAgentSink<Position> sink(registry, context);
467 return tick_ecs_path_agents<World, Class, MaxCost, OccupancyTag,
469 context.tick_state, world, source, sink, context.batch, runtime, index,
470 options, graph, render_deltas);
Definition metadata_types.h:12
Definition entity_handle.h:16
Definition entt_adapter.h:75
Definition entt_adapter.h:34
Definition entt_adapter.h:91
Definition path_agent_tick.h:17