Properly call constructors & destructors when attaching/detaching ECS components

This commit is contained in:
Nikita Lisitsa 2024-05-23 13:28:48 +03:00
parent cedc29f39a
commit b9b18b800d
7 changed files with 116 additions and 41 deletions

View file

@ -12,6 +12,7 @@
#include <psemek/ecs/accessor.hpp>
#include <psemek/ecs/exceptions.hpp>
#include <psemek/util/range.hpp>
#include <psemek/util/object_pool.hpp>
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 <typename ... Components>
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 <typename ... Components>
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 <typename ... Components>
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<Components...>()` 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<Components...>()` call.
@ -386,8 +385,8 @@ namespace psemek::ecs
detail::component_registry component_registry_;
detail::index_container index_container_;
std::vector<util::uuid> uuid_helper_;
util::hash_set<util::uuid> uuid_set_helper_;
util::object_pool<std::vector<util::uuid>> uuid_list_pool_;
util::object_pool<util::hash_set<util::uuid>> uuid_set_pool_;
std::vector<query_cache> callback_caches_;
@ -395,7 +394,7 @@ namespace psemek::ecs
query_cache cache_impl(Constructor && constructor, Destructor && destructor);
detail::table * insert_table(std::vector<std::unique_ptr<detail::column>> 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<detail::entity_data> entities);
void finilize_iteration(detail::table & table);
};
@ -448,17 +447,20 @@ namespace psemek::ecs
(register_component<Components>(), ...);
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<Components>::uuid()) ? 0 : (archetype_changed = true, uuid_helper_.push_back(std::remove_cvref_t<Components>::uuid()), 0)), ...);
((data.table->column(std::remove_cvref_t<Components>::uuid()) ? 0 : (archetype_changed = true, attached_uuid_set.insert(std::remove_cvref_t<Components>::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::remove_cvref_t<Components>>() = std::forward<Components>(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 <typename ... Components>
@ -497,25 +503,32 @@ namespace psemek::ecs
{
static_assert(detail::all_different_types_v<std::remove_const_t<Components>...>, "all component types must be different");
(uuid_set_helper_.insert(std::remove_const_t<Components>::uuid()), ...);
auto detached_uuid_set = uuid_set_pool_.get();
(detached_uuid_set.insert(std::remove_const_t<Components>::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<std::unique_ptr<detail::column>> 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 <typename ... Components>
@ -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<std::uint32_t> 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<util::uuid> const & attached_components, bool force){
bool const invoke = force || (detail::contains_helper<Components>::contains(attached_components) || ...);
if (!invoke)
return;
typename detail::filter_with<detail::static_apply_helper, std::tuple<Components ...>>::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<std::uint32_t> 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<util::uuid> const & detached_components, bool force){
bool const invoke = force || (detail::contains_helper<Components>::contains(detached_components) || ...);
if (!invoke)
return;
typename detail::filter_with<detail::static_apply_helper, std::tuple<Components ...>>::type apply_helper(container, table.entity_handles());
for (std::size_t i = 0; i < apply_helper.column_count; ++i)

View file

@ -2,8 +2,9 @@
#include <psemek/ecs/handle.hpp>
#include <psemek/util/function.hpp>
#include <memory>
#include <psemek/util/uuid.hpp>
#include <psemek/util/span.hpp>
#include <psemek/util/hash_table.hpp>
namespace psemek::ecs
{
@ -15,7 +16,7 @@ namespace psemek::ecs
struct table;
using table_callback = util::function<void(container &, table &, std::uint32_t)>;
using table_callback = util::function<void(container &, table &, std::uint32_t, util::hash_set<util::uuid> const &, bool)>;
}

View file

@ -10,9 +10,7 @@
#include <psemek/util/assert.hpp>
#include <memory>
#include <type_traits>
#include <vector>
#include <array>
#include <optional>
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<util::uuid> const & attached_components);
void trigger_destructors(container & container, std::uint32_t row);
void trigger_destructors(container & container, std::uint32_t row, util::hash_set<util::uuid> const & detached_components);
protected:
std::size_t hash_;
std::vector<std::unique_ptr<detail::column>> columns_;
util::hash_set<util::uuid> column_uuid_set_;
util::hash_map<util::uuid, std::uint32_t> component_uuid_to_column_index_;
std::vector<handle> entity_handles_;

View file

@ -79,4 +79,24 @@ namespace psemek::ecs::detail
using type = typename filter_without_impl<MetaFunction, std::tuple<ExtraArgs...>, std::tuple<>, Components...>::type;
};
template <typename Component>
struct contains_helper
{
template <typename Container>
static bool contains(Container const & container)
{
return container.contains(Component::uuid());
}
};
template <typename Component>
struct contains_helper<without<Component>>
{
template <typename Container>
static bool contains(Container const &)
{
return false;
}
};
}

View file

@ -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<size_t>(h.id) << 32) | h.epoch;
}
};
}

View file

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

View file

@ -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<util::uuid> 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<util::uuid> const & detached_components)
{
for (auto const & callback : *destructors_)
callback(container, *this, row, detached_components, false);
}
}