Support cloning entities in ecs::container
This commit is contained in:
parent
9f86a8d71a
commit
3b5e649a31
7 changed files with 227 additions and 31 deletions
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{}
|
||||
|
|
|
|||
|
|
@ -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_;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
76
libs/ecs/include/psemek/ecs/exceptions.hpp
Normal file
76
libs/ecs/include/psemek/ecs/exceptions.hpp
Normal 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();
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -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_;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)));
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue