Properly call constructors & destructors when attaching/detaching ECS components
This commit is contained in:
parent
cedc29f39a
commit
b9b18b800d
7 changed files with 116 additions and 41 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)>;
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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_;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue