3#include <tess/ecs/adapter.h>
5#if defined(TESS_ENABLE_FLECS)
8#error "Include <flecs.h> before <tess/ecs/flecs/flecs_adapter.h>"
11#if FLECS_VERSION_MAJOR < 4 || \
12 (FLECS_VERSION_MAJOR == 4 && FLECS_VERSION_MINOR < 1) || \
13 (FLECS_VERSION_MAJOR == 4 && FLECS_VERSION_MINOR == 1 && \
14 FLECS_VERSION_PATCH < 5)
15#error "The tess Flecs adapter requires Flecs 4.1.5 or newer"
42 using entity_type = flecs::entity_t;
44 [[nodiscard]]
static auto to_handle(flecs::entity_t entity)
noexcept
47 return kNullEntityHandle;
49 TESS_ASSERT_MSG(entity != kNullEntityHandle.value,
50 "the all-ones Flecs ID is reserved for tess null");
54 [[nodiscard]]
static auto to_entity(
EntityHandle handle)
noexcept
56 return handle.is_null() ? 0u : handle.value;
62class FlecsTilePositionAdapter {
64 explicit FlecsTilePositionAdapter(flecs::world& world) noexcept
67 [[nodiscard]]
auto position(flecs::entity_t entity)
const ->
Coord3 {
71 void set_position(flecs::entity_t entity,
Coord3 coord) {
82 std::uint64_t agent_id = 0;
83 flecs::entity_t entity = 0u;
95struct FlecsPathAgentContext {
100 explicit FlecsPathAgentContext(flecs::world& world)
101 : world_identity(world.c_ptr()),
109 std::vector<FlecsAgentEntry> entries{};
110 std::uint64_t next_agent_id = 0;
113 flecs::world_t* world_identity =
nullptr;
114 flecs::query<PathState, const TilePosition, const AgentId> agent_query;
116 void reserve(std::size_t agent_capacity) {
117 batch.reserve(agent_capacity);
118 entries.reserve(agent_capacity);
124template <
typename World>
125[[nodiscard]]
auto flecs_tile_resolves(
const World& world,
Coord3 tile)
noexcept
127 const auto resolved = world.try_resolve(tile);
128 if (!resolved.has_value()) {
131 if constexpr (std::is_same_v<
typename World::residency_type,
133 if (!world.is_resident(resolved->chunk_key)) {
140template <
typename World>
141void flecs_mark_tile_dirty(World& world, Coord3 tile, DirtyMask mask) {
143 world.mark_dirty(world.resolve(tile).chunk_key, mask,
144 Box3{tile, Extent3{1, 1, 1}});
148inline void flecs_assert_immediate_lifecycle(
149 [[maybe_unused]]
const flecs::world& world)
noexcept {
152 TESS_ASSERT_MSG(!world.is_deferred() && !world.is_readonly(),
153 "Flecs lifecycle intents require immediate mode");
156inline void flecs_assert_context_world(
157 [[maybe_unused]]
const flecs::world& world,
159 TESS_ASSERT_MSG(context.world_identity == world.c_ptr(),
160 "FlecsPathAgentContext must use the same Flecs world");
172class FlecsPathAgentSource {
174 FlecsPathAgentSource(flecs::world& world,
176 : world_(&world), context_(&context) {}
179 detail::flecs_assert_immediate_lifecycle(*world_);
180 detail::flecs_assert_context_world(*world_, *context_);
181 auto& entries = context_->entries;
185 context_->agent_query.each([&](flecs::entity entity,
PathState& state,
194 entries.push_back(
FlecsAgentEntry{agent_id.value, entity.id(), &state});
197 std::sort(entries.begin(), entries.end(),
199 return lhs.agent_id < rhs.agent_id;
203 info.count = entries.size();
206 for (
const auto& entry : entries) {
207 auto& state = *entry.state;
208 if (
const auto* goal = world_->entity(entry.entity).try_get<
PathGoal>()) {
209 if (!state.agent.has_goal || state.agent.goal != goal->coord) {
210 set_path_agent_goal(state.agent, goal->coord);
211 info.pathing_dirty =
true;
213 }
else if (state.agent.has_goal) {
214 clear_path_agent_goal(state.agent);
216 batch.push(FlecsHandleAdapter::to_handle(entry.entity), state.agent);
222 flecs::world* world_;
227template <
typename Position = FlecsTilePositionAdapter>
235class FlecsPathAgentSink {
238 Position position) noexcept
241 position_(
static_cast<Position&&
>(position)) {}
243 FlecsPathAgentSink(flecs::world& world,
245 requires std::constructible_from<Position, flecs::world&>
246 : FlecsPathAgentSink(world, context, Position(world)) {}
249 detail::flecs_assert_immediate_lifecycle(*world_);
250 detail::flecs_assert_context_world(*world_, *context_);
251 const auto agents = batch.agents();
252 const auto& entries = context_->entries;
253 TESS_ASSERT(agents.size() == entries.size());
254 for (std::size_t i = 0; i < entries.size(); ++i) {
255 const auto entity = entries[i].entity;
256 const auto& agent = agents[i];
257 auto flecs_entity = world_->entity(entity);
259 position_.set_position(entity, agent.position);
260 if (
const auto* goal = flecs_entity.try_get<
PathGoal>();
261 goal !=
nullptr && !agent.has_goal && agent.position == goal->coord) {
268 flecs::world* world_;
280template <
typename World,
typename OccupancyTag>
281[[nodiscard]]
auto spawn_flecs_path_agent(
284 DeltaCollector* render_deltas =
nullptr) -> flecs::entity_t {
285 detail::flecs_assert_immediate_lifecycle(ecs);
286 detail::flecs_assert_context_world(ecs, context);
287 if (!detail::flecs_tile_resolves(world, position) ||
288 static_cast<bool>(world.template field<OccupancyTag>(position)) ||
289 !index.entity_at(position).is_null()) {
295 index.reserve(index.size() + 1);
296 auto state = PathAgentState{};
297 state.position = position;
298 const auto flecs_entity = ecs.entity()
299 .set<AgentId>(AgentId{context.next_agent_id++})
300 .set<TilePosition>(TilePosition{position})
301 .set<PathState>(PathState{state});
302 const auto entity = flecs_entity.id();
303 world.template field<OccupancyTag>(position) =
true;
304 const auto inserted =
305 index.insert(position, FlecsHandleAdapter::to_handle(entity));
306 TESS_ASSERT(inserted);
307 static_cast<void>(inserted);
308 detail::flecs_mark_tile_dirty(world, position, dirty_mask);
309 if (render_deltas !=
nullptr) {
310 render_deltas->record_spawn(FlecsHandleAdapter::to_handle(entity),
321[[nodiscard]]
inline auto spawn_flecs_path_agent_off_board(
322 flecs::world& world, FlecsPathAgentContext& context) -> flecs::entity_t {
323 detail::flecs_assert_immediate_lifecycle(world);
324 detail::flecs_assert_context_world(world, context);
325 return world.entity()
326 .set<AgentId>(AgentId{context.next_agent_id++})
327 .set<TilePosition>(TilePosition{})
328 .set<PathState>(PathState{})
339template <
typename World,
typename OccupancyTag>
340auto despawn_flecs_path_agent(flecs::world& ecs, World& world,
341 TileOccupancyIndex& index, flecs::entity_t entity,
342 DirtyMask dirty_mask = {},
343 DeltaCollector* render_deltas =
nullptr) ->
bool {
344 detail::flecs_assert_immediate_lifecycle(ecs);
345 if (!ecs.is_alive(entity)) {
348 auto flecs_entity = ecs.entity(entity);
349 const auto* state = flecs_entity.try_get<PathState>();
350 if (state ==
nullptr) {
353 if (!flecs_entity.has<OffBoard>()) {
354 const auto position = state->agent.position;
355 world.template field<OccupancyTag>(position) =
false;
356 const auto erased = index.erase(position);
357 TESS_ASSERT(erased == FlecsHandleAdapter::to_handle(entity));
358 static_cast<void>(erased);
359 detail::flecs_mark_tile_dirty(world, position, dirty_mask);
360 if (render_deltas !=
nullptr) {
364 render_deltas->record_despawn(FlecsHandleAdapter::to_handle(entity),
368 flecs_entity.destruct();
378template <
typename World,
typename OccupancyTag>
379auto teleport_flecs_path_agent(flecs::world& ecs, World& world,
380 TileOccupancyIndex& index,
381 flecs::entity_t entity, Coord3 to,
382 DirtyMask dirty_mask = {},
383 DeltaCollector* render_deltas =
nullptr)
385 detail::flecs_assert_immediate_lifecycle(ecs);
386 if (!ecs.is_alive(entity)) {
389 auto flecs_entity = ecs.entity(entity);
390 auto* state = flecs_entity.try_get_mut<PathState>();
391 if (state ==
nullptr || flecs_entity.has<OffBoard>()) {
394 if (!detail::flecs_tile_resolves(world, to) ||
395 static_cast<bool>(world.template field<OccupancyTag>(to)) ||
396 !index.entity_at(to).is_null()) {
399 const auto from = state->agent.position;
400 world.template field<OccupancyTag>(from) =
false;
401 world.template field<OccupancyTag>(to) =
true;
402 index.move(from, to, FlecsHandleAdapter::to_handle(entity));
403 auto fresh = PathAgentState{};
405 flecs_entity.set<PathState>(PathState{fresh});
406 flecs_entity.set<TilePosition>(TilePosition{to});
407 detail::flecs_mark_tile_dirty(world, from, dirty_mask);
408 detail::flecs_mark_tile_dirty(world, to, dirty_mask);
409 if (render_deltas !=
nullptr) {
410 render_deltas->record_teleport(FlecsHandleAdapter::to_handle(entity), from,
422template <
typename World,
typename OccupancyTag>
423auto park_flecs_path_agent(flecs::world& ecs, World& world,
424 TileOccupancyIndex& index, flecs::entity_t entity,
425 DirtyMask dirty_mask = {},
426 DeltaCollector* render_deltas =
nullptr) ->
bool {
427 detail::flecs_assert_immediate_lifecycle(ecs);
428 if (!ecs.is_alive(entity)) {
431 auto flecs_entity = ecs.entity(entity);
432 auto* state = flecs_entity.try_get_mut<PathState>();
433 if (state ==
nullptr || flecs_entity.has<OffBoard>()) {
436 const auto position = state->agent.position;
437 world.template field<OccupancyTag>(position) =
false;
438 const auto erased = index.erase(position);
439 TESS_ASSERT(erased == FlecsHandleAdapter::to_handle(entity));
440 static_cast<void>(erased);
441 clear_path_agent_goal(state->agent);
442 flecs_entity.modified<PathState>();
443 flecs_entity.add<OffBoard>();
444 detail::flecs_mark_tile_dirty(world, position, dirty_mask);
445 if (render_deltas !=
nullptr) {
446 render_deltas->record_park(FlecsHandleAdapter::to_handle(entity), position);
457template <
typename World,
typename OccupancyTag>
458auto place_flecs_path_agent(flecs::world& ecs, World& world,
459 TileOccupancyIndex& index, flecs::entity_t entity,
460 Coord3 position, DirtyMask dirty_mask = {},
461 DeltaCollector* render_deltas =
nullptr) ->
bool {
462 detail::flecs_assert_immediate_lifecycle(ecs);
463 if (!ecs.is_alive(entity)) {
466 auto flecs_entity = ecs.entity(entity);
467 auto* state = flecs_entity.try_get_mut<PathState>();
468 if (state ==
nullptr || !flecs_entity.has<OffBoard>()) {
471 if (!detail::flecs_tile_resolves(world, position) ||
472 static_cast<bool>(world.template field<OccupancyTag>(position)) ||
473 !index.entity_at(position).is_null()) {
478 index.reserve(index.size() + 1);
479 world.template field<OccupancyTag>(position) =
true;
480 const auto inserted =
481 index.insert(position, FlecsHandleAdapter::to_handle(entity));
482 TESS_ASSERT(inserted);
483 static_cast<void>(inserted);
484 auto fresh = PathAgentState{};
485 fresh.position = position;
486 flecs_entity.set<PathState>(PathState{fresh});
487 flecs_entity.set<TilePosition>(TilePosition{position});
488 flecs_entity.remove<OffBoard>();
489 detail::flecs_mark_tile_dirty(world, position, dirty_mask);
490 if (render_deltas !=
nullptr) {
491 render_deltas->record_place(FlecsHandleAdapter::to_handle(entity),
498inline void set_flecs_path_agent_goal(flecs::world& world,
499 flecs::entity_t entity, Coord3 goal) {
500 world.entity(entity).set<PathGoal>(PathGoal{goal});
504inline void clear_flecs_path_agent_goal(flecs::world& world,
505 flecs::entity_t entity) {
506 world.entity(entity).remove<PathGoal>();
509template <
typename World,
typename ClassOrTag,
typename OccupancyTag,
510 typename ReservationTag,
typename Position = FlecsTilePositionAdapter>
517[[nodiscard]]
auto tick_flecs_unit_path_agents(
518 flecs::world& ecs, FlecsPathAgentContext& context, World& world,
519 PathRequestRuntime& runtime, TileOccupancyIndex& index,
520 PathAgentTickOptions options = {},
521 const RegionGraphT<typename World::residency_type>* graph =
nullptr,
522 DeltaCollector* render_deltas =
nullptr) -> PathAgentTickStats {
523 detail::flecs_assert_immediate_lifecycle(ecs);
524 detail::flecs_assert_context_world(ecs, context);
525 FlecsPathAgentSource source(ecs, context);
526 FlecsPathAgentSink<Position> sink(ecs, context);
527 return tick_ecs_unit_path_agents<World, ClassOrTag, OccupancyTag,
529 context.tick_state, world, source, sink, context.batch, runtime, index,
530 options, graph, render_deltas);
533template <
typename World,
typename Class, std::uint32_t MaxCost,
534 typename OccupancyTag,
typename ReservationTag,
535 typename Position = FlecsTilePositionAdapter>
537[[nodiscard]]
auto tick_flecs_path_agents(
538 flecs::world& ecs, FlecsPathAgentContext& context, World& world,
539 PathRequestRuntime& runtime, TileOccupancyIndex& index,
540 PathAgentTickOptions options = {},
541 const RegionGraphT<typename World::residency_type>* graph =
nullptr,
542 DeltaCollector* render_deltas =
nullptr) -> PathAgentTickStats {
543 detail::flecs_assert_immediate_lifecycle(ecs);
544 detail::flecs_assert_context_world(ecs, context);
545 FlecsPathAgentSource source(ecs, context);
546 FlecsPathAgentSink<Position> sink(ecs, context);
547 return tick_ecs_path_agents<World, Class, MaxCost, OccupancyTag,
549 context.tick_state, world, source, sink, context.batch, runtime, index,
550 options, graph, render_deltas);
Definition metadata_types.h:12
Definition entity_handle.h:16
Definition flecs_adapter.h:81
Definition flecs_adapter.h:41
Definition flecs_adapter.h:95
Definition path_agent_tick.h:17