Add ecs::container::finally mechanism

This commit is contained in:
Nikita Lisitsa 2025-04-10 11:57:50 +03:00
parent 0034e6a9f1
commit 083e5841aa
2 changed files with 104 additions and 0 deletions

View file

@ -385,6 +385,30 @@ namespace psemek::ecs
template <typename ... Components, typename Function>
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 <typename Function>
void finally(Function && function);
template <typename Index, typename ... Args>
Index & index(Args && ... args);
@ -412,10 +436,14 @@ namespace psemek::ecs
util::object_pool<std::vector<util::uuid>> uuid_list_pool_;
util::object_pool<util::hash_set<util::uuid>> uuid_set_pool_;
std::size_t method_recursion_depth_ = 0;
std::vector<util::function<void(container &)>> finally_callbacks_;
detail::table * insert_table(std::vector<std::unique_ptr<detail::column>> columns);
void do_destroy(handle entity);
void remove_row(detail::table & table, std::uint32_t row, util::span<detail::entity_data> entities);
void finalize_iteration(detail::table & table);
void finalize_method();
};
template <typename Component>
@ -429,6 +457,8 @@ namespace psemek::ecs
{
static_assert(detail::all_different_types_v<std::remove_cvref_t<Components>...>, "all component types must be different");
++method_recursion_depth_;
(register_component<std::remove_cvref_t<Components>>(), ...);
detail::component_uuid_helper<std::remove_cvref_t<Components>...> 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<std::remove_cvref_t<Components>...>, "all component types must be different");
++method_recursion_depth_;
(register_component<Components>(), ...);
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 <typename ... Components>
@ -525,6 +565,8 @@ namespace psemek::ecs
{
static_assert(detail::all_different_types_v<std::remove_const_t<Components>...>, "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<Components>::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 <typename ... Components>
@ -595,6 +641,8 @@ namespace psemek::ecs
{
static_assert(detail::all_different_types_v<std::remove_const_t<Components>...>, "all component types must be different");
++method_recursion_depth_;
using invocable_type = typename detail::filter_with<detail::invocable, std::tuple<Components...>, 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<std::remove_const_t<Components>...>, "all component types must be different");
++method_recursion_depth_;
using invocable_type = typename detail::filter_with<detail::batch_invocable, std::tuple<Components...>, 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 <typename Function>
void container::finally(Function && function)
{
util::function<void(container &)> wrapper;
if constexpr (std::is_invocable_v<Function, container &>)
{
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 <typename Index, typename ... Args>
Index & container::index(Args && ... args)
{

View file

@ -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<handle> 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();