From 028b4e129663277d66ece7ddb8eade6b4aa963f1 Mon Sep 17 00:00:00 2001 From: lisyarus Date: Mon, 18 Dec 2023 12:38:58 +0300 Subject: [PATCH] ECS constructors wip --- libs/ecs/include/psemek/ecs/container.hpp | 59 ++++++++++++++----- .../psemek/ecs/detail/apply_helper.hpp | 10 ++++ .../include/psemek/ecs/detail/callback.hpp | 22 +++++++ .../include/psemek/ecs/detail/query_cache.hpp | 5 +- libs/ecs/include/psemek/ecs/detail/table.hpp | 17 +++--- .../psemek/ecs/detail/table_container.hpp | 2 +- .../ecs/include/psemek/ecs/detail/without.hpp | 12 ++++ libs/ecs/source/detail/query_cache.cpp | 5 +- libs/ecs/source/detail/table.cpp | 36 +++++++++-- libs/ecs/tests/callback.cpp | 54 +++++++++++++++++ 10 files changed, 192 insertions(+), 30 deletions(-) create mode 100644 libs/ecs/include/psemek/ecs/detail/callback.hpp create mode 100644 libs/ecs/tests/callback.cpp diff --git a/libs/ecs/include/psemek/ecs/container.hpp b/libs/ecs/include/psemek/ecs/container.hpp index bc5e44c6..5bce2e31 100644 --- a/libs/ecs/include/psemek/ecs/container.hpp +++ b/libs/ecs/include/psemek/ecs/container.hpp @@ -19,7 +19,6 @@ namespace psemek::ecs { using query_cache = std::shared_ptr; - using token = std::shared_ptr; // TODO: // - Fully document which functions can be called from which callbacks @@ -283,12 +282,11 @@ 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 * * The constructor function must have the same signature as a function * passed to the `apply()` call. * - * The constructor can immediately destroy the created entity. - * * The constuctor is not considered to be a modification of the entity, i.e. it * doesn't trigger modification callbacks. * @@ -298,13 +296,11 @@ namespace psemek::ecs * @warning If any two of the passed component types are equal, the call fails with * a compilation error * @warning If the constructor modifies the entity's archetype (i.e. attaches or - * detaches components), it might be called recursively, leading to - * infinite recursion. It is best not to change the archetype from the - * constructor. + * detaches components), the behavior is undefined + * @warning If the constructor destroys the entity, the behavior is undefined */ - // TODO: implement template - token constructor(Function && function); + void constructor(Function && function); /** Register a destructor. Each time an entity is destroyed that has * the specified set of components, the destructor is called for @@ -322,6 +318,7 @@ 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 * * The destructor function must have the same signature as a function * passed to the `apply()` call. @@ -337,10 +334,11 @@ namespace psemek::ecs * a compilation error * @warning If the destructor modifies the entity's archetype (i.e. attaches or * detaches components), the behavior is undefined + * @warning If the destructor destroys the entity recursively, the behavior is undefined */ // TODO: implement template - token destructor(Function && function); + void destructor(Function && function); /** Register a component modification callback. Each time an entity that has * the specified set of components is modified, and the modification affects @@ -376,7 +374,7 @@ namespace psemek::ecs */ // TODO: implement template - token watch(Function && function); + void watch(Function && function); private: detail::entity_list entity_list_; @@ -387,6 +385,8 @@ namespace psemek::ecs std::vector uuid_helper_; util::hash_set uuid_set_helper_; + std::vector callback_caches_; + detail::table * insert_table(std::vector> columns); void do_destroy(handle const & entity); void remove_row(detail::table & table, std::uint32_t row, util::span entities); @@ -420,13 +420,16 @@ namespace psemek::ecs if (table->get_iteration_data()) table = table->get_delayed_table(); - auto id = entity_list_.create(table, table->row_count()); + auto row = table->row_count(); + auto id = entity_list_.create(table, row); handle handle{id, entity_list_.get_entities()[id].epoch}; [[maybe_unused]] accessor accessor = get(handle); table->push_row(handle); ((accessor.get>() = std::forward(components)), ...); + table->trigger_constructors(*this, row); + return handle; } @@ -477,9 +480,9 @@ namespace psemek::ecs template void container::detach(handle const & entity) { - static_assert(detail::all_different_types_v...>, "all component types must be different"); + static_assert(detail::all_different_types_v...>, "all component types must be different"); - (uuid_set_helper_.insert(std::remove_cvref_t::uuid()), ...); + (uuid_set_helper_.insert(std::remove_const_t::uuid()), ...); auto & data = entity_list_.get_entities()[entity.id]; for (auto const & column : data.table->columns()) @@ -534,7 +537,7 @@ namespace psemek::ecs { static_assert(detail::all_different_types_v...>, "all component types must be different"); - using invocable_type = typename detail::filter_with...>, Function>::type; + using invocable_type = typename detail::filter_with, Function>::type; static_assert(invocable_type::value, "function is not invocable with these components"); @@ -582,7 +585,7 @@ namespace psemek::ecs { static_assert(detail::all_different_types_v...>, "all component types must be different"); - using invocable_type = typename detail::filter_with...>, Function>::type; + using invocable_type = typename detail::filter_with, Function>::type; static_assert(invocable_type::value, "function is not batch-invocable with these components"); @@ -602,4 +605,30 @@ namespace psemek::ecs return cache; } + template + void container::constructor(Function && function) + { + static_assert(detail::all_different_types_v...>, "all component types must be different"); + + using invocable_type = typename detail::filter_with, Function>::type; + + static_assert(invocable_type::value, "function is not invocable with these components"); + + query_cache cache = this->cache(); + + cache->constructor_factory = [function = std::move(function)](std::vector const & column_indices) -> detail::table_callback { + return [function, column_indices](container & container, detail::table & table, std::uint32_t row){ + typename detail::filter_with>::type apply_helper(container, table.entity_handles()); + + for (std::size_t i = 0; i < apply_helper.column_count; ++i) + apply_helper.pointers[i] = table.columns()[column_indices[i]]->data(); + + apply_helper.advance(row); + apply_helper.apply(function); + }; + }; + + callback_caches_.push_back(cache); + } + } diff --git a/libs/ecs/include/psemek/ecs/detail/apply_helper.hpp b/libs/ecs/include/psemek/ecs/detail/apply_helper.hpp index cdf537ff..00301fdd 100644 --- a/libs/ecs/include/psemek/ecs/detail/apply_helper.hpp +++ b/libs/ecs/include/psemek/ecs/detail/apply_helper.hpp @@ -84,6 +84,8 @@ namespace psemek::ecs::detail template struct static_apply_helper { + static constexpr std::size_t column_count = sizeof...(Components); + container & parent; std::size_t row_count; handle const * entity_handles_pointer; @@ -132,6 +134,14 @@ namespace psemek::ecs::detail std::size_t i = 0; ((pointers[i++] += stride()), ...); } + + void advance(std::size_t rows) + { + entity_handles_pointer += rows; + + std::size_t i = 0; + ((pointers[i++] += stride() * rows), ...); + } }; } diff --git a/libs/ecs/include/psemek/ecs/detail/callback.hpp b/libs/ecs/include/psemek/ecs/detail/callback.hpp new file mode 100644 index 00000000..040626bb --- /dev/null +++ b/libs/ecs/include/psemek/ecs/detail/callback.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include +#include + +#include + +namespace psemek::ecs +{ + + struct container; + + namespace detail + { + + struct table; + + using table_callback = util::function; + + } + +} diff --git a/libs/ecs/include/psemek/ecs/detail/query_cache.hpp b/libs/ecs/include/psemek/ecs/detail/query_cache.hpp index e71b1a77..8ab00fd9 100644 --- a/libs/ecs/include/psemek/ecs/detail/query_cache.hpp +++ b/libs/ecs/include/psemek/ecs/detail/query_cache.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include #include @@ -13,7 +14,7 @@ namespace psemek::ecs::detail struct query_cache_entry { struct table * table = nullptr; - std::vector columns; + std::vector columns_indices; }; struct query_cache @@ -22,6 +23,8 @@ namespace psemek::ecs::detail std::vector without_uuids; std::vector entries; + util::function const &)> constructor_factory; + void add(table * table); }; diff --git a/libs/ecs/include/psemek/ecs/detail/table.hpp b/libs/ecs/include/psemek/ecs/detail/table.hpp index 2f376eb8..b205909b 100644 --- a/libs/ecs/include/psemek/ecs/detail/table.hpp +++ b/libs/ecs/include/psemek/ecs/detail/table.hpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -26,7 +27,8 @@ namespace psemek::ecs::detail return hash_; } - detail::column * column(util::uuid const & uuid) const; + std::optional column_index(util::uuid const & uuid) const; + struct column * column(util::uuid const & uuid) const; util::span entity_handles() const { @@ -38,11 +40,6 @@ namespace psemek::ecs::detail return columns_; } - std::size_t column_count() const - { - return component_uuid_to_column_.size(); - } - std::size_t row_count() const { return entity_handles_.size(); @@ -75,10 +72,14 @@ namespace psemek::ecs::detail std::vector const & non_copyable_components() const; + void add_constructor(table_callback callback); + + void trigger_constructors(container & container, std::uint32_t row); + protected: std::size_t hash_; std::vector> columns_; - util::hash_map component_uuid_to_column_; + util::hash_map component_uuid_to_column_index_; std::vector entity_handles_; @@ -87,6 +88,8 @@ namespace psemek::ecs::detail std::unique_ptr delayed_table_; std::vector non_copyable_components_; + + std::shared_ptr> constructors_; }; } diff --git a/libs/ecs/include/psemek/ecs/detail/table_container.hpp b/libs/ecs/include/psemek/ecs/detail/table_container.hpp index a6b4cec5..1d9474b4 100644 --- a/libs/ecs/include/psemek/ecs/detail/table_container.hpp +++ b/libs/ecs/include/psemek/ecs/detail/table_container.hpp @@ -26,7 +26,7 @@ namespace psemek::ecs::detail { bool operator()(util::span const & uuids, std::unique_ptr
const & table) const { - if (uuids.size() != table->column_count()) + if (uuids.size() != table->columns().size()) return false; for (auto const & uuid : uuids) if (!table->column(uuid)) diff --git a/libs/ecs/include/psemek/ecs/detail/without.hpp b/libs/ecs/include/psemek/ecs/detail/without.hpp index c5c1645b..b26edaa4 100644 --- a/libs/ecs/include/psemek/ecs/detail/without.hpp +++ b/libs/ecs/include/psemek/ecs/detail/without.hpp @@ -28,6 +28,12 @@ namespace psemek::ecs::detail using type = typename filter_with_impl, std::tuple, RemainingComponents...>::type; }; + template