diff --git a/libs/ecs/include/psemek/ecs/accessor.hpp b/libs/ecs/include/psemek/ecs/accessor.hpp index bcda087d..522d6636 100644 --- a/libs/ecs/include/psemek/ecs/accessor.hpp +++ b/libs/ecs/include/psemek/ecs/accessor.hpp @@ -1,39 +1,13 @@ #pragma once +#include #include -#include -#include -#include #include 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. diff --git a/libs/ecs/include/psemek/ecs/container.hpp b/libs/ecs/include/psemek/ecs/container.hpp index 5556862a..89aa1e3e 100644 --- a/libs/ecs/include/psemek/ecs/container.hpp +++ b/libs/ecs/include/psemek/ecs/container.hpp @@ -8,9 +8,11 @@ #include #include #include +#include #include #include +#include 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 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. diff --git a/libs/ecs/include/psemek/ecs/detail/column.hpp b/libs/ecs/include/psemek/ecs/detail/column.hpp index 44c3efbc..dfe5f514 100644 --- a/libs/ecs/include/psemek/ecs/detail/column.hpp +++ b/libs/ecs/include/psemek/ecs/detail/column.hpp @@ -2,8 +2,10 @@ #include #include +#include #include +#include 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 column_impl::column_impl() - : column(detail::stride(), Component::uuid()) + : column(detail::stride(), Component::uuid(), typeid(Component), std::is_copy_constructible_v) {} template @@ -118,6 +137,31 @@ namespace psemek::ecs::detail row_count_ += count; } + template + void column_impl::insert_rows(std::uint8_t const * data, std::size_t count) + { + if constexpr (std::is_copy_constructible_v) + { + allocate(row_count_ + count); + + auto src = reinterpret_cast(data); + auto dst = reinterpret_cast(data_); + auto src_end = src + count; + while (src != src_end) + { + new (dst) Component{*src}; + ++src; + ++dst; + } + + row_count_ += count; + } + else + { + assert(false); + } + } + template void column_impl::swap_rows(std::size_t row1, std::size_t row2) { @@ -184,7 +228,7 @@ namespace psemek::ecs::detail template column_impl::column_impl() - : column(detail::stride(), Component::uuid()) + : column(detail::stride(), Component::uuid(), typeid(Component), std::is_copy_constructible_v) { data_ = reinterpret_cast(new Component[1]); } @@ -197,6 +241,10 @@ namespace psemek::ecs::detail void column_impl::emplace_rows(std::uint8_t *, std::size_t) {} + template + void column_impl::insert_rows(std::uint8_t const *, std::size_t) + {} + template void column_impl::swap_rows(std::size_t, std::size_t) {} diff --git a/libs/ecs/include/psemek/ecs/detail/table.hpp b/libs/ecs/include/psemek/ecs/detail/table.hpp index f8f69617..2f376eb8 100644 --- a/libs/ecs/include/psemek/ecs/detail/table.hpp +++ b/libs/ecs/include/psemek/ecs/detail/table.hpp @@ -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 flush_delayed(); + std::vector const & non_copyable_components() const; + protected: std::size_t hash_; std::vector> columns_; @@ -82,6 +85,8 @@ namespace psemek::ecs::detail std::optional iteration_data_; std::vector remove_queue_; std::unique_ptr delayed_table_; + + std::vector non_copyable_components_; }; } diff --git a/libs/ecs/include/psemek/ecs/exceptions.hpp b/libs/ecs/include/psemek/ecs/exceptions.hpp new file mode 100644 index 00000000..c0bdcf22 --- /dev/null +++ b/libs/ecs/include/psemek/ecs/exceptions.hpp @@ -0,0 +1,76 @@ +#pragma once + +#include +#include +#include +#include + +#include + +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 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 const & non_copyable_components() const + { + return non_copyable_components_; + } + + private: + handle entity_; + std::vector non_copyable_components_; + + static std::string make_message(handle const & entity, std::vector 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(); + } + }; + +} diff --git a/libs/ecs/source/detail/table.cpp b/libs/ecs/source/detail/table.cpp index 9c2fc7f8..de1f291f 100644 --- a/libs/ecs/source/detail/table.cpp +++ b/libs/ecs/source/detail/table.cpp @@ -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 const & table::non_copyable_components() const + { + return non_copyable_components_; + } + } diff --git a/libs/ecs/source/entity_container.cpp b/libs/ecs/source/entity_container.cpp index 31ad5ab2..097329e3 100644 --- a/libs/ecs/source/entity_container.cpp +++ b/libs/ecs/source/entity_container.cpp @@ -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 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> columns) { auto table = table_container_.insert(std::make_unique(std::move(columns)));