From 083e5841aace550478bfa1ea4a2aea4c5a896722 Mon Sep 17 00:00:00 2001 From: lisyarus Date: Thu, 10 Apr 2025 11:57:50 +0300 Subject: [PATCH] Add ecs::container::finally mechanism --- libs/ecs/include/psemek/ecs/container.hpp | 77 +++++++++++++++++++++++ libs/ecs/source/container.cpp | 27 ++++++++ 2 files changed, 104 insertions(+) diff --git a/libs/ecs/include/psemek/ecs/container.hpp b/libs/ecs/include/psemek/ecs/container.hpp index a09f73a8..26228e54 100644 --- a/libs/ecs/include/psemek/ecs/container.hpp +++ b/libs/ecs/include/psemek/ecs/container.hpp @@ -385,6 +385,30 @@ namespace psemek::ecs template void watch(Function && function); + /** Call a callback after exiting all currently executing ECS container methods. + * + * If no ECS container method is currently executed, call the callback immediately instead. + * + * The function must have one of the following signatures: + * void() + * void(container) + * + * This method is meant to be called from inside other callbacks (typically + * constructors and destructors) and can be used to add extra modifications + * to entities that don't clash with other callbacks and archetype changes. + * + * Generally, if a constructor, destructor, apply or batch apply function + * changes the archetype of some entities, it should do so using `finally` + * instead of doing that directly. + * + * @param function A callback to be called before the topmost currently executing + * ECS container method returns. + * @warning If a callback adds new callbacks via `finally`, those will also get executed, + * which potentially leads to an infinite loop. + */ + template + void finally(Function && function); + template Index & index(Args && ... args); @@ -412,10 +436,14 @@ namespace psemek::ecs util::object_pool> uuid_list_pool_; util::object_pool> uuid_set_pool_; + std::size_t method_recursion_depth_ = 0; + std::vector> finally_callbacks_; + detail::table * insert_table(std::vector> columns); void do_destroy(handle entity); void remove_row(detail::table & table, std::uint32_t row, util::span entities); void finalize_iteration(detail::table & table); + void finalize_method(); }; template @@ -429,6 +457,8 @@ namespace psemek::ecs { static_assert(detail::all_different_types_v...>, "all component types must be different"); + ++method_recursion_depth_; + (register_component>(), ...); detail::component_uuid_helper...> uuids; @@ -456,6 +486,10 @@ namespace psemek::ecs table->trigger_constructors(*this, row); + finalize_method(); + + --method_recursion_depth_; + return handle; } @@ -464,6 +498,8 @@ namespace psemek::ecs { static_assert(detail::all_different_types_v...>, "all component types must be different"); + ++method_recursion_depth_; + (register_component(), ...); auto uuids = uuid_list_pool_.get(); @@ -518,6 +554,10 @@ namespace psemek::ecs uuid_set_pool_.put(std::move(attached_uuid_set)); uuid_list_pool_.put(std::move(uuids)); + + finalize_method(); + + --method_recursion_depth_; } template @@ -525,6 +565,8 @@ namespace psemek::ecs { static_assert(detail::all_different_types_v...>, "all component types must be different"); + ++method_recursion_depth_; + auto detached_uuid_set = uuid_set_pool_.get(); (detached_uuid_set.insert(std::remove_const_t::uuid()), ...); @@ -579,6 +621,10 @@ namespace psemek::ecs uuid_list_pool_.put(std::move(uuids)); uuid_set_pool_.put(std::move(detached_uuid_set)); + + finalize_method(); + + --method_recursion_depth_; } template @@ -595,6 +641,8 @@ namespace psemek::ecs { static_assert(detail::all_different_types_v...>, "all component types must be different"); + ++method_recursion_depth_; + using invocable_type = typename detail::filter_with, Function>::type; static_assert(invocable_type::value, "function is not invocable with these components"); @@ -622,6 +670,10 @@ namespace psemek::ecs finalize_iteration(*entry.table); } + finalize_method(); + + --method_recursion_depth_; + return cache; } @@ -630,6 +682,8 @@ namespace psemek::ecs { static_assert(detail::all_different_types_v...>, "all component types must be different"); + ++method_recursion_depth_; + using invocable_type = typename detail::filter_with, Function>::type; static_assert(invocable_type::value, "function is not batch-invocable with these components"); @@ -654,6 +708,10 @@ namespace psemek::ecs finalize_iteration(*entry.table); } + finalize_method(); + + --method_recursion_depth_; + return cache; } @@ -732,6 +790,25 @@ namespace psemek::ecs entry.table->add_destructor({id, destructor_factory(entry.columns_indices)}); } + template + void container::finally(Function && function) + { + util::function wrapper; + if constexpr (std::is_invocable_v) + { + wrapper = std::move(function); + } + else + { + wrapper = [function = std::move(function)](ecs::container &){ function(); }; + } + + if (method_recursion_depth_ == 0) + wrapper(*this); + else + finally_callbacks_.push_back(std::move(wrapper)); + } + template Index & container::index(Args && ... args) { diff --git a/libs/ecs/source/container.cpp b/libs/ecs/source/container.cpp index ac0ba484..788ab5f3 100644 --- a/libs/ecs/source/container.cpp +++ b/libs/ecs/source/container.cpp @@ -15,11 +15,17 @@ namespace psemek::ecs { assert(alive(entity)); + ++method_recursion_depth_; + auto const data = entity_list_.get_entities()[entity.id]; data.table->trigger_destructors(*this, data.row); do_destroy(entity); entity_list_.destroy(entity.id); + + finalize_method(); + + --method_recursion_depth_; } accessor container::get(handle entity) @@ -52,6 +58,8 @@ namespace psemek::ecs std::optional container::try_clone(handle entity) { + ++method_recursion_depth_; + auto const data = entity_list_.get_entities()[entity.id]; if (!data.table->non_copyable_components().empty()) return std::nullopt; @@ -68,6 +76,10 @@ namespace psemek::ecs table->trigger_constructors(*this, row); + finalize_method(); + + --method_recursion_depth_; + return handle; } @@ -125,6 +137,21 @@ namespace psemek::ecs } } + void container::finalize_method() + { + if (method_recursion_depth_ > 1) + return; + + while (!finally_callbacks_.empty()) + { + auto callbacks = std::move(finally_callbacks_); + finally_callbacks_.clear(); + + for (auto & callback : callbacks) + callback(*this); + } + } + std::size_t container::entity_count() { return entity_list_.size();