ECS object lifetime issues fixes

This commit is contained in:
Nikita Lisitsa 2024-05-24 21:42:17 +03:00
parent 01b8bd3d59
commit 443fa3d8ca
5 changed files with 58 additions and 49 deletions

View file

@ -68,7 +68,7 @@ namespace psemek::ecs
* @return True if the entity is still alive, false otherwise
* @pre The entity was previously obtained by a `create()` call
*/
bool alive(handle const & entity) const;
bool alive(handle entity) const;
/** Destroy an entity specified by a handle. After the call, `alive()` returns false for this handle.
*
@ -79,7 +79,7 @@ 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()`
*/
void destroy(handle const & entity);
void destroy(handle entity);
/** Get an accessor for an entity, which provides access to the entity's components.
* It is designed to be a single-use object; it cannot be stored as a reference to
@ -91,7 +91,7 @@ namespace psemek::ecs
* @warning Creating or destroying entities, as well as attaching or detaching components,
* invalidates all previously created accessors
*/
accessor get(handle const & entity);
accessor get(handle entity);
/** Check if the entity can be cloned.
*
@ -101,7 +101,7 @@ namespace psemek::ecs
* @return True if the entity can be cloned, false otherwise
* @pre The entity was previously obtained by a `create()` call and is `alive()`
*/
bool can_clone(handle const & entity) const;
bool can_clone(handle entity) const;
/** Clone the entity into a new entity by copy-constructing all components,
* as if by calling `create()` with all the original entity's components.
@ -113,7 +113,7 @@ namespace psemek::ecs
* @pre The entity was previously obtained by a `create()` call and is `alive()`
* @throw entity_not_cloneable if the entity is not cloneable
*/
handle clone(handle const & entity);
handle clone(handle entity);
/** Try to clone the entity into a new entity by copy-constructing all components,
* as if by calling `create()` with all the original entity's components.
@ -124,7 +124,7 @@ namespace psemek::ecs
* @return A handle to cloned entity if the original entity was cloneable, and std::nullopt otherwise
* @pre The entity was previously obtained by a `create()` call and is `alive()`
*/
std::optional<handle> try_clone(handle const & entity);
std::optional<handle> try_clone(handle entity);
/** Attach new components to an existing entity, or update existing
* components with new values. Other components of this entity
@ -143,7 +143,7 @@ namespace psemek::ecs
* @warning Attaching components invalidates all previously created accessors
*/
template <typename ... Components>
void attach(handle const & entity, Components && ... components);
void attach(handle entity, Components && ... components);
/** Detach (remove) components from an existing entity. Other components of this
* entity are left untouched. Detaching a component that doesn't exist is not
@ -160,7 +160,7 @@ namespace psemek::ecs
* @warning Detaching components invalidates all previously created accessors
*/
template <typename ... Components>
void detach(handle const & entity);
void detach(handle entity);
/** Create a query cache that can be used to speed up `apply()` calls.
*
@ -394,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, bool trigger_destructors);
void do_destroy(handle entity);
void remove_row(detail::table & table, std::uint32_t row, util::span<detail::entity_data> entities);
void finilize_iteration(detail::table & table);
};
@ -441,7 +441,7 @@ namespace psemek::ecs
}
template <typename ... Components>
void container::attach(handle const & entity, Components && ... components)
void container::attach(handle entity, Components && ... components)
{
static_assert(detail::all_different_types_v<std::remove_cvref_t<Components>...>, "all component types must be different");
@ -450,12 +450,12 @@ namespace psemek::ecs
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())
auto * data = entity_list_.get_entities().begin() + entity.id;
for (auto const & column : data->table->columns())
uuids.push_back(column->uuid());
bool archetype_changed = false;
((data.table->column(std::remove_cvref_t<Components>::uuid()) ? 0 : (archetype_changed = true, attached_uuid_set.insert(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)), ...);
for (auto const & uuid : attached_uuid_set)
uuids.push_back(uuid);
@ -465,31 +465,31 @@ namespace psemek::ecs
if (!table)
{
std::vector<std::unique_ptr<detail::column>> columns;
for (auto const & column : data.table->columns())
for (auto const & column : data->table->columns())
columns.push_back(column->clone());
((data.table->column(std::remove_cvref_t<Components>::uuid()) ? 0 : (columns.push_back(std::make_unique<detail::column_impl<std::remove_cvref_t<Components>>>()), 0)), ...);
((data->table->column(std::remove_cvref_t<Components>::uuid()) ? 0 : (columns.push_back(std::make_unique<detail::column_impl<std::remove_cvref_t<Components>>>()), 0)), ...);
table = insert_table(std::move(columns));
}
if (table != data.table)
if (table != data->table)
{
if (table->get_iteration_data())
table = table->get_delayed_table();
auto new_row = table->move_row(entity, data.table, data.row);
do_destroy(entity, false);
auto new_row = table->move_row(entity, data->table, data->row);
do_destroy(entity);
data.table = table;
data.row = new_row;
data->table = table;
data->row = new_row;
}
auto accessor = get(entity);
((accessor.get<std::remove_cvref_t<Components>>() = std::forward<Components>(components)), ...);
if (archetype_changed)
table->trigger_constructors(*this, data.row, attached_uuid_set);
table->trigger_constructors(*this, data->row, attached_uuid_set);
attached_uuid_set.clear();
uuids.clear();
@ -499,7 +499,7 @@ namespace psemek::ecs
}
template <typename ... Components>
void container::detach(handle const & entity)
void container::detach(handle entity)
{
static_assert(detail::all_different_types_v<std::remove_const_t<Components>...>, "all component types must be different");
@ -508,8 +508,8 @@ namespace psemek::ecs
auto uuids = uuid_list_pool_.get();
auto & data = entity_list_.get_entities()[entity.id];
for (auto const & column : data.table->columns())
auto * data = entity_list_.get_entities().begin() + entity.id;
for (auto const & column : data->table->columns())
{
if (detached_uuid_set.contains(column->uuid()))
detached_uuid_set.insert(column->uuid());
@ -522,28 +522,31 @@ namespace psemek::ecs
auto table = table_container_.get(uuids);
if (archetype_changed)
data.table->trigger_destructors(*this, data.row, detached_uuid_set);
data->table->trigger_destructors(*this, data->row, detached_uuid_set);
// Destructors could lead to reallocation of entity list
data = entity_list_.get_entities().begin() + entity.id;
if (!table)
{
std::vector<std::unique_ptr<detail::column>> columns;
for (auto const & column : data.table->columns())
for (auto const & column : data->table->columns())
if (!detached_uuid_set.contains(column->uuid()))
columns.push_back(column->clone());
table = insert_table(std::move(columns));
}
if (table != data.table)
if (table != data->table)
{
if (table->get_iteration_data())
table = table->get_delayed_table();
auto new_row = table->move_row(entity, data.table, data.row);
do_destroy(entity, false);
auto new_row = table->move_row(entity, data->table, data->row);
do_destroy(entity);
data.table = table;
data.row = new_row;
data->table = table;
data->row = new_row;
}
detached_uuid_set.clear();

View file

@ -3,8 +3,6 @@
#include <psemek/ecs/detail/table.hpp>
#include <psemek/ecs/detail/entity_list.hpp>
#include <concepts>
namespace psemek::ecs
{

View file

@ -4,8 +4,6 @@
#include <psemek/util/hash_table.hpp>
#include <psemek/util/function.hpp>
#include <concepts>
namespace psemek::ecs
{

View file

@ -4,31 +4,35 @@
namespace psemek::ecs
{
bool container::alive(handle const & entity) const
bool container::alive(handle entity) const
{
return entity_list_.get_entities()[entity.id].epoch == entity.epoch;
}
void container::destroy(handle const & entity)
void container::destroy(handle entity)
{
assert(alive(entity));
do_destroy(entity, true);
auto const data = entity_list_.get_entities()[entity.id];
data.table->trigger_destructors(*this, data.row);
do_destroy(entity);
entity_list_.destroy(entity.id);
}
accessor container::get(handle const & entity)
accessor container::get(handle entity)
{
assert(alive(entity));
auto const data = entity_list_.get_entities()[entity.id];
return {data.table, data.row};
}
bool container::can_clone(handle const & entity) const
bool container::can_clone(handle entity) const
{
return entity_list_.get_entities()[entity.id].table->non_copyable_components().empty();
}
handle container::clone(handle const & entity)
handle container::clone(handle entity)
{
if (auto result = try_clone(entity))
return *result;
@ -37,7 +41,7 @@ namespace psemek::ecs
throw entity_not_cloneable(entity, data.table->non_copyable_components());
}
std::optional<handle> container::try_clone(handle const & entity)
std::optional<handle> container::try_clone(handle entity)
{
auto const data = entity_list_.get_entities()[entity.id];
if (!data.table->non_copyable_components().empty())
@ -69,15 +73,12 @@ namespace psemek::ecs
return table;
}
void container::do_destroy(handle const & entity, bool trigger_destructors)
void container::do_destroy(handle entity)
{
auto entities = entity_list_.get_entities();
auto & data = entities[entity.id];
auto & iteration_data = data.table->get_iteration_data();
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);
else

View file

@ -1,4 +1,5 @@
#include <psemek/ecs/detail/entity_list.hpp>
#include <psemek/util/assert.hpp>
namespace psemek::ecs::detail
{
@ -11,15 +12,23 @@ namespace psemek::ecs::detail
auto id = free_ids_.back();
free_ids_.pop_back();
entities_[id].table = table;
entities_[id].row = row;
auto & data = entities_[id];
assert(data.table == nullptr);
data.table = table;
data.row = row;
return id;
}
void entity_list::destroy(entity_id id)
{
entities_[id].epoch += 1;
auto & data = entities_[id];
assert(data.table != nullptr);
data.table = nullptr;
data.epoch += 1;
free_ids_.push_back(id);
}