Support cloning entities in ecs::container

This commit is contained in:
Nikita Lisitsa 2023-12-17 15:30:12 +03:00
parent 9f86a8d71a
commit 3b5e649a31
7 changed files with 227 additions and 31 deletions

View file

@ -1,39 +1,13 @@
#pragma once
#include <psemek/ecs/exceptions.hpp>
#include <psemek/ecs/detail/table.hpp>
#include <psemek/util/exception.hpp>
#include <psemek/util/type_name.hpp>
#include <psemek/util/to_string.hpp>
#include <typeindex>
namespace psemek::ecs
{
struct component_not_found_exception
: util::exception
{
component_not_found_exception(std::type_info const & type, handle const & handle, boost::stacktrace::stacktrace stacktrace = {})
: util::exception(util::to_string("Component ", util::type_name(type), " not found for entity ", handle), std::move(stacktrace))
, type_(type)
, handle_(handle)
{}
std::type_info const & type() const
{
return type_;
}
ecs::handle const & handle() const
{
return handle_;
}
private:
std::type_info const & type_;
ecs::handle handle_;
};
struct accessor
{
/** Create an invalid accessor, i.e. one that doesn't refer to an entity.

View file

@ -8,9 +8,11 @@
#include <psemek/ecs/detail/component_uuid_helper.hpp>
#include <psemek/ecs/detail/without.hpp>
#include <psemek/ecs/accessor.hpp>
#include <psemek/ecs/exceptions.hpp>
#include <psemek/util/range.hpp>
#include <typeindex>
#include <sstream>
namespace psemek::ecs
{
@ -22,7 +24,6 @@ namespace psemek::ecs
// - Fully document which functions can be called from which callbacks
// - Constructors & destructors implementation (const-only)
// - Modification callbacks implementation (const-only)
// - Entity cloning
// - Registering components
// - Tables serialization
// - Refactor query caches
@ -83,6 +84,39 @@ namespace psemek::ecs
*/
accessor get(handle const & entity);
/** Check if the entity can be cloned.
*
* An entity can be cloned if all of its components are copy-constructible.
*
* @param entity A handle to the entity to check for cloning
* @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;
/** Clone the entity into a new entity by copy-constructing all components,
* as if by calling `create()` with all the original entity's components.
*
* The new entity is considered alive immediately after this call.
*
* @param entity A handle to the entity to clone
* @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()`
* @throw entity_not_cloneable if the entity is not cloneable
*/
handle clone(handle const & 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.
*
* The new entity is considered alive immediately after this call.
*
* @param entity A handle to the entity to clone
* @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);
/** Attach new components to an existing entity, or update existing
* components with new values. Other components of this entity
* are left untouched.

View file

@ -2,8 +2,10 @@
#include <psemek/ecs/detail/stride.hpp>
#include <psemek/util/uuid.hpp>
#include <psemek/util/assert.hpp>
#include <memory>
#include <typeindex>
namespace psemek::ecs::detail
{
@ -25,8 +27,19 @@ namespace psemek::ecs::detail
return uuid_;
}
std::type_index const & type() const
{
return type_;
}
bool copy_constructible() const
{
return copy_constructible_;
}
virtual void push_row() = 0;
virtual void emplace_rows(std::uint8_t * data, std::size_t count) = 0;
virtual void insert_rows(std::uint8_t const * data, std::size_t count) = 0;
virtual void swap_rows(std::size_t row1, std::size_t row2) = 0;
virtual void pop_row() = 0;
virtual void clear() = 0;
@ -39,10 +52,14 @@ namespace psemek::ecs::detail
std::uint8_t * data_ = nullptr;
std::size_t stride_;
util::uuid uuid_;
std::type_index type_;
bool copy_constructible_;
column(std::size_t stride, util::uuid const & uuid)
column(std::size_t stride, util::uuid const & uuid, std::type_index const & type, bool copy_constructible)
: stride_(stride)
, uuid_(uuid)
, type_(type)
, copy_constructible_(copy_constructible)
{}
};
@ -54,6 +71,7 @@ namespace psemek::ecs::detail
void push_row() override;
void emplace_rows(std::uint8_t * data, std::size_t count) override;
void insert_rows(std::uint8_t const * data, std::size_t count) override;
void swap_rows(std::size_t row1, std::size_t row2) override;
void pop_row() override;
void clear() override;
@ -77,6 +95,7 @@ namespace psemek::ecs::detail
void push_row() override;
void emplace_rows(std::uint8_t * data, std::size_t count) override;
void insert_rows(std::uint8_t const * data, std::size_t count) override;
void swap_rows(std::size_t row1, std::size_t row2) override;
void pop_row() override;
void clear() override;
@ -88,7 +107,7 @@ namespace psemek::ecs::detail
template <typename Component, bool Empty>
column_impl<Component, Empty>::column_impl()
: column(detail::stride<Component>(), Component::uuid())
: column(detail::stride<Component>(), Component::uuid(), typeid(Component), std::is_copy_constructible_v<Component>)
{}
template <typename Component, bool Empty>
@ -118,6 +137,31 @@ namespace psemek::ecs::detail
row_count_ += count;
}
template <typename Component, bool Empty>
void column_impl<Component, Empty>::insert_rows(std::uint8_t const * data, std::size_t count)
{
if constexpr (std::is_copy_constructible_v<Component>)
{
allocate(row_count_ + count);
auto src = reinterpret_cast<Component const *>(data);
auto dst = reinterpret_cast<Component *>(data_);
auto src_end = src + count;
while (src != src_end)
{
new (dst) Component{*src};
++src;
++dst;
}
row_count_ += count;
}
else
{
assert(false);
}
}
template <typename Component, bool Empty>
void column_impl<Component, Empty>::swap_rows(std::size_t row1, std::size_t row2)
{
@ -184,7 +228,7 @@ namespace psemek::ecs::detail
template <typename Component>
column_impl<Component, true>::column_impl()
: column(detail::stride<Component>(), Component::uuid())
: column(detail::stride<Component>(), Component::uuid(), typeid(Component), std::is_copy_constructible_v<Component>)
{
data_ = reinterpret_cast<std::uint8_t *>(new Component[1]);
}
@ -197,6 +241,10 @@ namespace psemek::ecs::detail
void column_impl<Component, true>::emplace_rows(std::uint8_t *, std::size_t)
{}
template <typename Component>
void column_impl<Component, true>::insert_rows(std::uint8_t const *, std::size_t)
{}
template <typename Component>
void column_impl<Component, true>::swap_rows(std::size_t, std::size_t)
{}

View file

@ -50,6 +50,7 @@ namespace psemek::ecs::detail
std::size_t push_row(handle handle);
std::size_t move_row(handle handle, table * from, std::size_t from_row);
std::size_t copy_row(handle handle, table * from, std::size_t from_row);
void swap_rows(std::size_t row1, std::size_t row2);
void pop_row();
void clear();
@ -72,6 +73,8 @@ namespace psemek::ecs::detail
table * get_delayed_table();
util::span<handle const> flush_delayed();
std::vector<std::type_index> const & non_copyable_components() const;
protected:
std::size_t hash_;
std::vector<std::unique_ptr<detail::column>> columns_;
@ -82,6 +85,8 @@ namespace psemek::ecs::detail
std::optional<iteration_data> iteration_data_;
std::vector<std::uint32_t> remove_queue_;
std::unique_ptr<table> delayed_table_;
std::vector<std::type_index> non_copyable_components_;
};
}

View file

@ -0,0 +1,76 @@
#pragma once
#include <psemek/ecs/handle.hpp>
#include <psemek/util/exception.hpp>
#include <psemek/util/to_string.hpp>
#include <psemek/util/type_name.hpp>
#include <typeindex>
namespace psemek::ecs
{
struct component_not_found_exception
: util::exception
{
component_not_found_exception(std::type_info const & type, handle const & handle, boost::stacktrace::stacktrace stacktrace = {})
: util::exception(util::to_string("Component ", util::type_name(type), " not found for entity ", handle), std::move(stacktrace))
, type_(type)
, handle_(handle)
{}
std::type_info const & type() const
{
return type_;
}
ecs::handle const & handle() const
{
return handle_;
}
private:
std::type_info const & type_;
ecs::handle handle_;
};
struct entity_not_cloneable
: util::exception
{
entity_not_cloneable(handle const & entity, std::vector<std::type_index> non_copyable_components, boost::stacktrace::stacktrace stacktrace = {})
: util::exception(make_message(entity, non_copyable_components), std::move(stacktrace))
, entity_(entity)
, non_copyable_components_(std::move(non_copyable_components))
{}
handle const & entity() const
{
return entity_;
}
std::vector<std::type_index> const & non_copyable_components() const
{
return non_copyable_components_;
}
private:
handle entity_;
std::vector<std::type_index> non_copyable_components_;
static std::string make_message(handle const & entity, std::vector<std::type_index> const & non_copyable_components)
{
std::ostringstream os;
os << "entity " << entity << " is not cloneable because components ";
bool first = true;
for (auto const & type : non_copyable_components)
{
if (!first) os << ", ";
first = false;
os << util::type_name(type);
}
os << " are not copy constructible";
return os.str();
}
};
}

View file

@ -12,6 +12,8 @@ namespace psemek::ecs::detail
auto uuid = column->uuid();
hasher(uuid);
component_uuid_to_column_.insert({uuid, column.get()});
if (!column->copy_constructible())
non_copyable_components_.push_back(column->type());
}
hash_ = hasher.result;
@ -46,6 +48,17 @@ namespace psemek::ecs::detail
return entity_handles_.size() - 1;
}
std::size_t table::copy_row(handle handle, table * from, std::size_t from_row)
{
for (std::size_t i = 0; i < columns_.size(); ++i)
{
auto from_column = from->columns_[i].get();
columns_[i]->insert_rows(from_column->data() + from_column->stride() * from_row, 1);
}
entity_handles_.push_back(handle);
return entity_handles_.size() - 1;
}
void table::swap_rows(std::size_t row1, std::size_t row2)
{
for (auto & column : columns_)
@ -110,4 +123,9 @@ namespace psemek::ecs::detail
return {entity_handles_.data() + entity_handles_.size() - count, count};
}
std::vector<std::type_index> const & table::non_copyable_components() const
{
return non_copyable_components_;
}
}

View file

@ -20,6 +20,47 @@ namespace psemek::ecs
return {data.table, data.row};
}
bool container::can_clone(handle const & entity) const
{
return entity_list_.get_entities()[entity.id].table->non_copyable_components().empty();
}
handle container::clone(handle const & entity)
{
auto const data = entity_list_.get_entities()[entity.id];
if (!data.table->non_copyable_components().empty())
throw entity_not_cloneable(entity, data.table->non_copyable_components());
auto table = data.table;
if (table->get_iteration_data())
table = table->get_delayed_table();
auto id = entity_list_.create(table, table->row_count());
handle handle{id, entity_list_.get_entities()[id].epoch};
table->copy_row(handle, data.table, data.row);
return handle;
}
std::optional<handle> container::try_clone(handle const & entity)
{
auto const data = entity_list_.get_entities()[entity.id];
if (!data.table->non_copyable_components().empty())
return std::nullopt;
auto table = data.table;
if (table->get_iteration_data())
table = table->get_delayed_table();
auto id = entity_list_.create(table, table->row_count());
handle handle{id, entity_list_.get_entities()[id].epoch};
table->copy_row(handle, data.table, data.row);
return handle;
}
detail::table * container::insert_table(std::vector<std::unique_ptr<detail::column>> columns)
{
auto table = table_container_.insert(std::make_unique<detail::table>(std::move(columns)));