From b9b18b800d3324674a80407fe613a58240e7aea2 Mon Sep 17 00:00:00 2001 From: lisyarus Date: Thu, 23 May 2024 13:28:48 +0300 Subject: [PATCH] Properly call constructors & destructors when attaching/detaching ECS components --- libs/ecs/include/psemek/ecs/container.hpp | 86 ++++++++++++------- .../include/psemek/ecs/detail/callback.hpp | 7 +- libs/ecs/include/psemek/ecs/detail/table.hpp | 6 +- .../ecs/include/psemek/ecs/detail/without.hpp | 20 +++++ libs/ecs/include/psemek/ecs/handle.hpp | 14 +++ libs/ecs/source/container.cpp | 7 +- libs/ecs/source/detail/table.cpp | 17 +++- 7 files changed, 116 insertions(+), 41 deletions(-) diff --git a/libs/ecs/include/psemek/ecs/container.hpp b/libs/ecs/include/psemek/ecs/container.hpp index 6c0b7c05..08309d72 100644 --- a/libs/ecs/include/psemek/ecs/container.hpp +++ b/libs/ecs/include/psemek/ecs/container.hpp @@ -12,6 +12,7 @@ #include #include #include +#include namespace psemek::ecs { @@ -56,7 +57,6 @@ namespace psemek::ecs * @warning If any two of the passed component types are equal, the call fails with * a compilation error * @warning Creating a new entity invalidates all previously created accessors - * @warning It is an error to create or destroy entities during `batch_apply()` */ template handle create(Components && ... components); @@ -78,7 +78,6 @@ namespace psemek::ecs * * @param entity A handle to the entity to be destroyed * @pre The entity was previously obtained by a `create()` call and is `alive()` - * @warning It is an error to create or destroy entities during `batch_apply()` */ void destroy(handle const & entity); @@ -142,7 +141,6 @@ namespace psemek::ecs * @warning If any two of the passed component types are equal, the call fails with * a compilation error * @warning Attaching components invalidates all previously created accessors - * @warning It is an error to attach or detach components during `batch_apply()` */ template void attach(handle const & entity, Components && ... components); @@ -160,7 +158,6 @@ namespace psemek::ecs * @warning If any two of the passed component types are equal, the call fails with * a compilation error * @warning Detaching components invalidates all previously created accessors - * @warning It is an error to attach or detach components during `batch_apply()` */ template void detach(handle const & entity); @@ -283,7 +280,8 @@ namespace psemek::ecs * When attaching components to an entity, the constructor is called * exactly when the entity didn't match the constructor's component types * before attaching new components, and does match them after attaching. - * TODO: implement this behavior + * However, this matching check ignores the constructor's 'without' parameters. + * TODO: Figure out the behavior concerning 'without' parameters * * The constructor function must have the same signature as a function * passed to the `apply()` call. @@ -319,7 +317,8 @@ namespace psemek::ecs * When detaching components from an entity, the destructor is called * exactly when the entity did match the destructor's component types * before detaching components, and doesn't match them after detaching. - * TODO: implement this behavior + * However, this matching check ignores the destructors's 'without' parameters. + * TODO: Figure out the behavior concerning 'without' parameters * * The destructor function must have the same signature as a function * passed to the `apply()` call. @@ -386,8 +385,8 @@ namespace psemek::ecs detail::component_registry component_registry_; detail::index_container index_container_; - std::vector uuid_helper_; - util::hash_set uuid_set_helper_; + util::object_pool> uuid_list_pool_; + util::object_pool> uuid_set_pool_; std::vector callback_caches_; @@ -395,7 +394,7 @@ namespace psemek::ecs query_cache cache_impl(Constructor && constructor, Destructor && destructor); detail::table * insert_table(std::vector> columns); - void do_destroy(handle const & entity); + void do_destroy(handle const & entity, bool trigger_destructors); void remove_row(detail::table & table, std::uint32_t row, util::span entities); void finilize_iteration(detail::table & table); }; @@ -448,17 +447,20 @@ namespace psemek::ecs (register_component(), ...); + auto uuids = uuid_list_pool_.get(); + auto attached_uuid_set = uuid_set_pool_.get(); + auto & data = entity_list_.get_entities()[entity.id]; for (auto const & column : data.table->columns()) - uuid_helper_.push_back(column->uuid()); + uuids.push_back(column->uuid()); bool archetype_changed = false; - ((data.table->column(std::remove_cvref_t::uuid()) ? 0 : (archetype_changed = true, uuid_helper_.push_back(std::remove_cvref_t::uuid()), 0)), ...); + ((data.table->column(std::remove_cvref_t::uuid()) ? 0 : (archetype_changed = true, attached_uuid_set.insert(std::remove_cvref_t::uuid()), 0)), ...); - if (archetype_changed) - data.table->trigger_destructors(*this, data.row); + for (auto const & uuid : attached_uuid_set) + uuids.push_back(uuid); - auto table = table_container_.get(uuid_helper_); + auto table = table_container_.get(uuids); if (!table) { @@ -477,7 +479,7 @@ namespace psemek::ecs table = table->get_delayed_table(); auto new_row = table->move_row(entity, data.table, data.row); - do_destroy(entity); + do_destroy(entity, false); data.table = table; data.row = new_row; @@ -486,10 +488,14 @@ namespace psemek::ecs auto accessor = get(entity); ((accessor.get>() = std::forward(components)), ...); - uuid_helper_.clear(); - if (archetype_changed) - table->trigger_constructors(*this, data.row); + table->trigger_constructors(*this, data.row, attached_uuid_set); + + attached_uuid_set.clear(); + uuids.clear(); + + uuid_set_pool_.put(std::move(attached_uuid_set)); + uuid_list_pool_.put(std::move(uuids)); } template @@ -497,25 +503,32 @@ namespace psemek::ecs { static_assert(detail::all_different_types_v...>, "all component types must be different"); - (uuid_set_helper_.insert(std::remove_const_t::uuid()), ...); + auto detached_uuid_set = uuid_set_pool_.get(); + (detached_uuid_set.insert(std::remove_const_t::uuid()), ...); + + auto uuids = uuid_list_pool_.get(); auto & data = entity_list_.get_entities()[entity.id]; for (auto const & column : data.table->columns()) - if (!uuid_set_helper_.contains(column->uuid())) - uuid_helper_.push_back(column->uuid()); + { + if (detached_uuid_set.contains(column->uuid())) + detached_uuid_set.insert(column->uuid()); + else + uuids.push_back(column->uuid()); + } bool const archetype_changed = sizeof...(Components) > 0; - auto table = table_container_.get(uuid_helper_); + auto table = table_container_.get(uuids); if (archetype_changed) - data.table->trigger_destructors(*this, data.row); + data.table->trigger_destructors(*this, data.row, detached_uuid_set); if (!table) { std::vector> columns; for (auto const & column : data.table->columns()) - if (!uuid_set_helper_.contains(column->uuid())) + if (!detached_uuid_set.contains(column->uuid())) columns.push_back(column->clone()); table = insert_table(std::move(columns)); @@ -527,17 +540,18 @@ namespace psemek::ecs table = table->get_delayed_table(); auto new_row = table->move_row(entity, data.table, data.row); - do_destroy(entity); + do_destroy(entity, false); data.table = table; data.row = new_row; } - uuid_set_helper_.clear(); - uuid_helper_.clear(); + detached_uuid_set.clear(); + uuids.clear(); + detached_uuid_set.clear(); - if (archetype_changed) - table->trigger_constructors(*this, data.row); + uuid_list_pool_.put(std::move(uuids)); + uuid_set_pool_.put(std::move(detached_uuid_set)); } template @@ -623,7 +637,12 @@ namespace psemek::ecs static_assert(invocable_type::value, "function is not invocable with these components"); auto constructor = [function = std::move(function)](std::vector const & column_indices) -> detail::table_callback { - return [function, column_indices](container & container, detail::table & table, std::uint32_t row){ + return [function, column_indices](container & container, detail::table & table, std::uint32_t row, util::hash_set const & attached_components, bool force){ + + bool const invoke = force || (detail::contains_helper::contains(attached_components) || ...); + if (!invoke) + return; + typename detail::filter_with>::type apply_helper(container, table.entity_handles()); for (std::size_t i = 0; i < apply_helper.column_count; ++i) @@ -649,7 +668,12 @@ namespace psemek::ecs static_assert(invocable_type::value, "function is not invocable with these components"); auto destructor = [function = std::move(function)](std::vector const & column_indices) -> detail::table_callback { - return [function, column_indices](container & container, detail::table & table, std::uint32_t row){ + return [function, column_indices](container & container, detail::table & table, std::uint32_t row, util::hash_set const & detached_components, bool force){ + + bool const invoke = force || (detail::contains_helper::contains(detached_components) || ...); + if (!invoke) + return; + typename detail::filter_with>::type apply_helper(container, table.entity_handles()); for (std::size_t i = 0; i < apply_helper.column_count; ++i) diff --git a/libs/ecs/include/psemek/ecs/detail/callback.hpp b/libs/ecs/include/psemek/ecs/detail/callback.hpp index 040626bb..be210916 100644 --- a/libs/ecs/include/psemek/ecs/detail/callback.hpp +++ b/libs/ecs/include/psemek/ecs/detail/callback.hpp @@ -2,8 +2,9 @@ #include #include - -#include +#include +#include +#include namespace psemek::ecs { @@ -15,7 +16,7 @@ namespace psemek::ecs struct table; - using table_callback = util::function; + using table_callback = util::function const &, bool)>; } diff --git a/libs/ecs/include/psemek/ecs/detail/table.hpp b/libs/ecs/include/psemek/ecs/detail/table.hpp index c8fe59d7..4f853713 100644 --- a/libs/ecs/include/psemek/ecs/detail/table.hpp +++ b/libs/ecs/include/psemek/ecs/detail/table.hpp @@ -10,9 +10,7 @@ #include #include -#include #include -#include #include namespace psemek::ecs::detail @@ -76,11 +74,15 @@ namespace psemek::ecs::detail void add_destructor(table_callback callback); void trigger_constructors(container & container, std::uint32_t row); + void trigger_constructors(container & container, std::uint32_t row, util::hash_set const & attached_components); + void trigger_destructors(container & container, std::uint32_t row); + void trigger_destructors(container & container, std::uint32_t row, util::hash_set const & detached_components); protected: std::size_t hash_; std::vector> columns_; + util::hash_set column_uuid_set_; util::hash_map component_uuid_to_column_index_; std::vector entity_handles_; diff --git a/libs/ecs/include/psemek/ecs/detail/without.hpp b/libs/ecs/include/psemek/ecs/detail/without.hpp index b26edaa4..97b7efcb 100644 --- a/libs/ecs/include/psemek/ecs/detail/without.hpp +++ b/libs/ecs/include/psemek/ecs/detail/without.hpp @@ -79,4 +79,24 @@ namespace psemek::ecs::detail using type = typename filter_without_impl, std::tuple<>, Components...>::type; }; + template + struct contains_helper + { + template + static bool contains(Container const & container) + { + return container.contains(Component::uuid()); + } + }; + + template + struct contains_helper> + { + template + static bool contains(Container const &) + { + return false; + } + }; + } diff --git a/libs/ecs/include/psemek/ecs/handle.hpp b/libs/ecs/include/psemek/ecs/handle.hpp index 009db51a..2a8b64c1 100644 --- a/libs/ecs/include/psemek/ecs/handle.hpp +++ b/libs/ecs/include/psemek/ecs/handle.hpp @@ -23,3 +23,17 @@ namespace psemek::ecs } } + +namespace std +{ + + template <> + struct hash<::psemek::ecs::handle> + { + size_t operator()(::psemek::ecs::handle const & h) const noexcept + { + return (static_cast(h.id) << 32) | h.epoch; + } + }; + +} diff --git a/libs/ecs/source/container.cpp b/libs/ecs/source/container.cpp index 1e62ff73..41d37d75 100644 --- a/libs/ecs/source/container.cpp +++ b/libs/ecs/source/container.cpp @@ -12,7 +12,7 @@ namespace psemek::ecs void container::destroy(handle const & entity) { assert(alive(entity)); - do_destroy(entity); + do_destroy(entity, true); entity_list_.destroy(entity.id); } @@ -69,13 +69,14 @@ namespace psemek::ecs return table; } - void container::do_destroy(handle const & entity) + void container::do_destroy(handle const & entity, bool trigger_destructors) { auto entities = entity_list_.get_entities(); auto & data = entities[entity.id]; auto & iteration_data = data.table->get_iteration_data(); - data.table->trigger_destructors(*this, data.row); + if (trigger_destructors) + data.table->trigger_destructors(*this, data.row); if (!iteration_data || iteration_data->current_row < data.row) remove_row(*data.table, data.row, entities); diff --git a/libs/ecs/source/detail/table.cpp b/libs/ecs/source/detail/table.cpp index 4e383da1..e57e7c6e 100644 --- a/libs/ecs/source/detail/table.cpp +++ b/libs/ecs/source/detail/table.cpp @@ -12,6 +12,7 @@ namespace psemek::ecs::detail auto const & column = columns[i]; auto uuid = column->uuid(); hasher(uuid); + column_uuid_set_.insert(uuid); component_uuid_to_column_index_.insert({uuid, i}); if (!column->copy_constructible()) non_copyable_components_.push_back(column->type()); @@ -156,13 +157,25 @@ namespace psemek::ecs::detail void table::trigger_constructors(container & container, std::uint32_t row) { for (auto const & callback : *constructors_) - callback(container, *this, row); + callback(container, *this, row, column_uuid_set_, true); + } + + void table::trigger_constructors(container & container, std::uint32_t row, util::hash_set const & attached_components) + { + for (auto const & callback : *constructors_) + callback(container, *this, row, attached_components, false); } void table::trigger_destructors(container & container, std::uint32_t row) { for (auto const & callback : *destructors_) - callback(container, *this, row); + callback(container, *this, row, column_uuid_set_, true); + } + + void table::trigger_destructors(container & container, std::uint32_t row, util::hash_set const & detached_components) + { + for (auto const & callback : *destructors_) + callback(container, *this, row, detached_components, false); } }