diff --git a/libs/ecs/include/psemek/ecs/container.hpp b/libs/ecs/include/psemek/ecs/container.hpp index c3caa449..47165184 100644 --- a/libs/ecs/include/psemek/ecs/container.hpp +++ b/libs/ecs/include/psemek/ecs/container.hpp @@ -6,7 +6,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -23,8 +25,6 @@ namespace psemek::ecs // TODO: // - Fully document which functions can be called from which callbacks - // - Const-qualified component access - // - Negated component access // - Constructors & destructors implementation // - Modification callbacks implementation // - Refactor query caches @@ -125,6 +125,8 @@ namespace psemek::ecs * * The constness of the component types is ignored. * + * The component types can be equal to ecs::without. + * * @tparam Components The component types matching the corresponding `apply()` call * @return A query cache */ @@ -144,6 +146,10 @@ namespace psemek::ecs * that this function doesn't modify specific components, to prevent modification callbacks * from being triggered. * + * The component types can be equal to ecs::without, indicating that entities having + * this component type will not be visited by this function. These component types are not + * included in the called function signature. + * * The function can freely create or destroy entities, and or attach/detach * components to existing entities. It is unspecified whether the function * will or will not visit newly created entities during this `apply()` call. @@ -185,6 +191,10 @@ namespace psemek::ecs * that this function doesn't modify specific components, to prevent modification callbacks * from being triggered. * + * The component types can be equal to ecs::without, indicating that entities having + * this component type will not be visited by this function. These component types are not + * included in the called function signature. + * * The sizes of all spans within a single function call are the same, * except for empty component types (i.e. std::is_empty_v is true), * each having an unspecified non-zero size. If all components are empty types, @@ -218,6 +228,10 @@ namespace psemek::ecs * The component types can be const-qualified, in which case the corresponding function must * also accept the corresponding components by a const reference. * + * The component types can be equal to ecs::without, indicating that entities having + * this component type will not be visited by this constructor. These component types are not + * included in the called function signature. + * * 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. @@ -253,6 +267,10 @@ namespace psemek::ecs * The component types can be const-qualified, in which case the corresponding function must * also accept the corresponding components by a const reference. * + * The component types can be equal to ecs::without, indicating that entities having + * this component type will not be visited by this destructor. These component types are not + * included in the called function signature. + * * 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. @@ -283,6 +301,10 @@ namespace psemek::ecs * The component types can be const-qualified, in which case the corresponding function must * also accept the corresponding components by a const reference. * + * The component types can be equal to ecs::without, indicating that entities having + * this component type will not be watched by this callback. These component types are not + * included in the called function signature. + * * If the modification occurred via an accessor, the callback is called * after the accessor is destroyed, allowing for transaction-like modification. * @@ -436,13 +458,14 @@ namespace psemek::ecs template query_cache container::cache() { - detail::component_uuid_helper...> uuids; + typename detail::filter_with ...>>::type with_uuids; + typename detail::filter_without...>>::type without_uuids; - auto result = query_cache_container_.create(uuids.get()); + auto result = query_cache_container_.create(with_uuids.get(), without_uuids.get()); table_container_.apply([&](detail::table & table){ result->add(&table); - }, uuids.get()); + }, with_uuids.get(), without_uuids.get()); return result; } @@ -451,7 +474,10 @@ namespace psemek::ecs query_cache container::apply(Function && function, query_cache cache) { static_assert(detail::all_different_types_v...>, "all component types must be different"); - static_assert(detail::invocable, "function is not invocable with these components"); + + using invocable_type = typename detail::filter_with...>, Function>::type; + + static_assert(invocable_type::value, "function is not invocable with these components"); if (!cache) cache = this->cache(); @@ -460,10 +486,10 @@ namespace psemek::ecs { auto & iteration_data = entry.table->get_iteration_data(); iteration_data.emplace(); - detail::static_apply_helper apply_helper(*this, entry.table->entity_handles()); + typename detail::filter_with>::type apply_helper(*this, entry.table->entity_handles()); - for (std::size_t i = 0; i < sizeof...(Components); ++i) - apply_helper.pointers[i] = entry.table->column(cache->component_uuids[i])->data(); + for (std::size_t i = 0; i < cache->with_uuids.size(); ++i) + apply_helper.pointers[i] = entry.table->column(cache->with_uuids[i])->data(); for (std::size_t i = 0; i < entry.table->row_count(); ++i) { @@ -496,17 +522,20 @@ namespace psemek::ecs query_cache container::batch_apply(Function && function, query_cache cache) { static_assert(detail::all_different_types_v...>, "all component types must be different"); - static_assert(detail::batch_invocable, "function is not batch-invocable with these components"); + + using invocable_type = typename detail::filter_with...>, Function>::type; + + static_assert(invocable_type::value, "function is not batch-invocable with these components"); if (!cache) cache = this->cache(); for (auto const & entry : cache->entries) { - detail::static_apply_helper apply_helper(*this, entry.table->entity_handles()); + typename detail::filter_with>::type apply_helper(*this, entry.table->entity_handles()); - for (std::size_t i = 0; i < sizeof...(Components); ++i) - apply_helper.pointers[i] = entry.table->column(cache->component_uuids[i])->data(); + for (std::size_t i = 0; i < cache->with_uuids.size(); ++i) + apply_helper.pointers[i] = entry.table->column(cache->with_uuids[i])->data(); apply_helper.batch_apply(function); } diff --git a/libs/ecs/include/psemek/ecs/detail/apply_helper.hpp b/libs/ecs/include/psemek/ecs/detail/apply_helper.hpp index 27d31fa0..cdf537ff 100644 --- a/libs/ecs/include/psemek/ecs/detail/apply_helper.hpp +++ b/libs/ecs/include/psemek/ecs/detail/apply_helper.hpp @@ -16,12 +16,15 @@ namespace psemek::ecs::detail { template - constexpr bool invocable = false - || std::invocable - || std::invocable - || std::invocable - || std::invocable - ; + struct invocable + { + static constexpr bool value = false + || std::invocable + || std::invocable + || std::invocable + || std::invocable + ; + }; template void invoke(Function && function, container & parent, handle const & handle, Components & ... components) @@ -45,12 +48,15 @@ namespace psemek::ecs::detail } template - constexpr bool batch_invocable = false - || std::invocable, util::span ...> - || std::invocable ...> - || std::invocable, util::span ...> - || std::invocable ...> - ; + struct batch_invocable + { + static constexpr bool value = false + || std::invocable, util::span ...> + || std::invocable ...> + || std::invocable, util::span ...> + || std::invocable ...> + ; + }; template void batch_invoke(Function && function, container & parent, std::size_t count, handle const * handles, Components * ... components) diff --git a/libs/ecs/include/psemek/ecs/detail/component_hash.hpp b/libs/ecs/include/psemek/ecs/detail/component_hash.hpp index 534e9307..f74bf8c0 100644 --- a/libs/ecs/include/psemek/ecs/detail/component_hash.hpp +++ b/libs/ecs/include/psemek/ecs/detail/component_hash.hpp @@ -6,23 +6,33 @@ namespace psemek::ecs::detail { + template struct component_hasher { std::size_t result = 0xcd5694d2b3f3443eull; void operator()(util::uuid const & uuid) { - result ^= std::hash{}(uuid); + if constexpr (With) + result ^= std::hash{}(uuid); + else + result ^= ~std::hash{}(uuid); } }; - template + template std::size_t component_hash(UUIDs const & uuids) { - component_hasher hasher; + component_hasher hasher; for (auto const & uuid : uuids) hasher(uuid); return hasher.result; } + template + std::size_t component_hash(WithUUIDs const & with_uuids, WithoutUUIDs const & without_uuids) + { + return component_hash(with_uuids) ^ component_hash(without_uuids); + } + } diff --git a/libs/ecs/include/psemek/ecs/detail/query_cache.hpp b/libs/ecs/include/psemek/ecs/detail/query_cache.hpp index d4b30dde..e71b1a77 100644 --- a/libs/ecs/include/psemek/ecs/detail/query_cache.hpp +++ b/libs/ecs/include/psemek/ecs/detail/query_cache.hpp @@ -18,7 +18,8 @@ namespace psemek::ecs::detail struct query_cache { - std::vector component_uuids; + std::vector with_uuids; + std::vector without_uuids; std::vector entries; void add(table * table); diff --git a/libs/ecs/include/psemek/ecs/detail/query_cache_container.hpp b/libs/ecs/include/psemek/ecs/detail/query_cache_container.hpp index 63609898..6e1c1201 100644 --- a/libs/ecs/include/psemek/ecs/detail/query_cache_container.hpp +++ b/libs/ecs/include/psemek/ecs/detail/query_cache_container.hpp @@ -12,15 +12,16 @@ namespace psemek::ecs::detail struct query_cache_set { std::size_t hash; - util::hash_set uuids; + util::hash_set with_uuids; + util::hash_set without_uuids; std::vector> caches; }; struct query_cache_hash { - std::size_t operator()(util::span const & uuids) const + std::size_t operator()(std::pair, util::span> const & uuids) const { - return component_hash(uuids); + return component_hash(uuids.first, uuids.second); } std::size_t operator()(std::unique_ptr const & set) const @@ -31,13 +32,20 @@ namespace psemek::ecs::detail struct query_cache_equal { - bool operator()(util::span const & uuids, std::unique_ptr const & set) const + bool operator()(std::pair, util::span> const & uuids, std::unique_ptr const & set) const { - if (uuids.size() != set->uuids.size()) + if (uuids.first.size() != set->with_uuids.size()) return false; - for (auto const & uuid : uuids) - if (!set->uuids.contains(uuid)) + if (uuids.second.size() != set->without_uuids.size()) + return false; + + for (auto const & uuid : uuids.first) + if (!set->with_uuids.contains(uuid)) + return false; + + for (auto const & uuid : uuids.second) + if (!set->without_uuids.contains(uuid)) return false; return true; @@ -47,23 +55,25 @@ namespace psemek::ecs::detail { return set1.get() == set2.get(); } - }; // TODO: store query caches in a bitmask trie balanced by subtree size struct query_cache_container { - std::shared_ptr create(util::span component_uuids) + std::shared_ptr create(util::span with_uuids, util::span without_uuids) { auto result = std::make_shared(); - result->component_uuids.assign(component_uuids.begin(), component_uuids.end()); - auto it = caches_.find(component_uuids); + result->with_uuids.assign(with_uuids.begin(), with_uuids.end()); + result->without_uuids.assign(without_uuids.begin(), without_uuids.end()); + auto it = caches_.find(std::pair{with_uuids, without_uuids}); if (it == caches_.end()) { auto value = std::make_unique(); - for (auto const & uuid : component_uuids) - value->uuids.insert(uuid); - value->hash = component_hash(component_uuids); + for (auto const & uuid : with_uuids) + value->with_uuids.insert(uuid); + for (auto const & uuid : without_uuids) + value->without_uuids.insert(uuid); + value->hash = component_hash(with_uuids, without_uuids); it = caches_.insert(std::move(value)).first; } it->get()->caches.push_back(result); @@ -76,7 +86,7 @@ namespace psemek::ecs::detail for (auto & cache_set : caches_) { bool good = true; - for (auto const & uuid : cache_set->uuids) + for (auto const & uuid : cache_set->with_uuids) if (!contains_uuid(uuid)) { good = false; diff --git a/libs/ecs/include/psemek/ecs/detail/table_container.hpp b/libs/ecs/include/psemek/ecs/detail/table_container.hpp index ad26bdad..a6b4cec5 100644 --- a/libs/ecs/include/psemek/ecs/detail/table_container.hpp +++ b/libs/ecs/include/psemek/ecs/detail/table_container.hpp @@ -13,7 +13,7 @@ namespace psemek::ecs::detail { std::size_t operator()(util::span const & uuids) const { - return component_hash(uuids); + return component_hash(uuids); } std::size_t operator()(std::unique_ptr const & table) const @@ -47,7 +47,7 @@ namespace psemek::ecs::detail table * insert(std::unique_ptr
table); template - void apply(Function && function, util::span component_uuids); + void apply(Function && function, util::span with_uuids, util::span without_uuids); private: util::hash_set, table_hashset_hash, table_hashset_equal> tables_; @@ -69,18 +69,26 @@ namespace psemek::ecs::detail } template - void table_container::apply(Function && function, util::span component_uuids) + void table_container::apply(Function && function, util::span with_uuids, util::span without_uuids) { for (auto & table : tables_) { bool good = true; - for (auto const & uuid : component_uuids) + + for (auto const & uuid : with_uuids) if (!table->column(uuid)) { good = false; break; } + for (auto const & uuid : without_uuids) + if (table->column(uuid)) + { + good = false; + break; + } + if (good) function(*table); } } diff --git a/libs/ecs/include/psemek/ecs/detail/without.hpp b/libs/ecs/include/psemek/ecs/detail/without.hpp new file mode 100644 index 00000000..c5c1645b --- /dev/null +++ b/libs/ecs/include/psemek/ecs/detail/without.hpp @@ -0,0 +1,70 @@ +#pragma once + +#include + +#include + +namespace psemek::ecs::detail +{ + + template