diff --git a/libs/ecs/include/psemek/ecs/container.hpp b/libs/ecs/include/psemek/ecs/container.hpp index 1cff650b..9da9497e 100644 --- a/libs/ecs/include/psemek/ecs/container.hpp +++ b/libs/ecs/include/psemek/ecs/container.hpp @@ -122,6 +122,8 @@ namespace psemek::ecs void detach(handle const & entity); /** Create a query cache that can be used to speed up `apply()` calls. + * + * The constness of the component types is ignored. * * @tparam Components The component types matching the corresponding `apply()` call * @return A query cache @@ -137,6 +139,11 @@ namespace psemek::ecs * void(container, components...) * void(container, handle, components...) * + * The component types can be const-qualified, in which case the corresponding function must + * also accept the corresponding components by a const reference. This can be used to indicate + * that this function doesn't modify specific components, to prevent modification callbacks + * from being triggered. + * * 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. @@ -150,7 +157,8 @@ namespace psemek::ecs * @param function The function to apply to all entities with the specified components * @param cache A query cache * @return The query cache, either user-provided, or newly-created - * @pre The query query cache was created with the exact same sequence of component types + * @pre The query query cache was created with the exact same sequence of component types, ignoring + * constness * @warning If any two of the passed component types are equal, the call fails with * a compilation error * @warning If the function accesses passed components after destroying the @@ -172,6 +180,11 @@ namespace psemek::ecs * void(container, span...) * void(container, span, span...) * + * The component types can be const-qualified, in which case the corresponding function must + * also accept the corresponding components by views to const. This can be used to indicate + * that this function doesn't modify specific components, to prevent modification callbacks + * from being triggered. + * * 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, @@ -185,7 +198,8 @@ namespace psemek::ecs * @param function The function to batch-apply to all entities with the specified components * @param cache A query cache * @return The query cache, either user-provided, or newly-created - * @pre The query query cache was created with the exact same sequence of component types + * @pre The query query cache was created with the exact same sequence of component types, ignoring + * constness * @warning If any two of the passed component types are equal, the call fails with * a compilation error * @warning If the function creates or destroyes entities, or attaches or detaches components, @@ -201,6 +215,9 @@ namespace psemek::ecs * * The entity is considered `alive()` during the constructor invocation. * + * The component types can be const-qualified, in which case the corresponding function must + * also accept the corresponding components by a const reference. + * * 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. @@ -210,6 +227,9 @@ namespace psemek::ecs * * 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. + * * @param function A function to be applied to created entity's components * @return An ownerwhip token; destroying this token removes * the constructor from this container @@ -230,6 +250,9 @@ namespace psemek::ecs * * The entity is considered `alive()` during the destructor invocation. * + * The component types can be const-qualified, in which case the corresponding function must + * also accept the corresponding components by a const reference. + * * 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. @@ -239,6 +262,9 @@ namespace psemek::ecs * * Note that there is no way to cancel the entity's destruction. * + * The destructor is not considered to be a modification of the entity, i.e. it + * doesn't trigger modification callbacks. + * * @param function A function to be applied to a to-be destroyed entity's components * @return An ownerwhip token; destroying it removes the destructor from the container * @warning If any two of the passed component types are equal, the call fails with @@ -254,6 +280,9 @@ namespace psemek::ecs * the specified set of components is modified, and the modification affects * one of this callback's component types, the callback is called. * + * The component types can be const-qualified, in which case the corresponding function must + * also accept the corresponding components by a const reference. + * * If the modification occurred via an accessor, the callback is called * after the accessor is destroyed, allowing for transaction-like modification. * @@ -295,16 +324,16 @@ namespace psemek::ecs template handle container::create(Components && ... components) { - 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"); - detail::component_uuid_helper uuids; + detail::component_uuid_helper...> uuids; auto table = table_container_.get(uuids.get()); if (!table) { std::vector> columns; - (columns.push_back(std::make_unique>()), ...); + (columns.push_back(std::make_unique>>()), ...); table = insert_table(std::move(columns)); } @@ -317,7 +346,7 @@ namespace psemek::ecs [[maybe_unused]] accessor accessor = get(handle); table->push_row(handle); - ((accessor.get() = std::move(components)), ...); + ((accessor.get>() = std::forward(components)), ...); return handle; } @@ -325,13 +354,13 @@ namespace psemek::ecs template void container::attach(handle const & entity, Components && ... components) { - 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"); auto & data = entity_list_.get_entities()[entity.id]; for (auto const & column : data.table->columns()) uuid_helper_.push_back(column->uuid()); - ((data.table->column(Components::uuid()) ? 0 : (uuid_helper_.push_back(Components::uuid()), 0)), ...); + ((data.table->column(std::remove_cvref_t::uuid()) ? 0 : (uuid_helper_.push_back(std::remove_cvref_t::uuid()), 0)), ...); auto table = table_container_.get(uuid_helper_); @@ -341,7 +370,7 @@ namespace psemek::ecs for (auto const & column : data.table->columns()) columns.push_back(column->clone()); - ((data.table->column(Components::uuid()) ? 0 : (columns.push_back(std::make_unique>()), 0)), ...); + ((data.table->column(std::remove_cvref_t::uuid()) ? 0 : (columns.push_back(std::make_unique>>()), 0)), ...); table = insert_table(std::move(columns)); } @@ -359,7 +388,7 @@ namespace psemek::ecs } auto accessor = get(entity); - ((accessor.get() = std::move(components)), ...); + ((accessor.get>() = std::forward(components)), ...); uuid_helper_.clear(); } @@ -367,9 +396,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(Components::uuid()), ...); + (uuid_set_helper_.insert(std::remove_cvref_t::uuid()), ...); auto & data = entity_list_.get_entities()[entity.id]; for (auto const & column : data.table->columns()) @@ -407,7 +436,7 @@ namespace psemek::ecs template query_cache container::cache() { - detail::component_uuid_helper uuids; + detail::component_uuid_helper...> uuids; auto result = query_cache_container_.create(uuids.get()); @@ -421,7 +450,7 @@ namespace psemek::ecs template 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::all_different_types_v...>, "all component types must be different"); if (!cache) cache = this->cache(); @@ -465,7 +494,7 @@ namespace psemek::ecs template 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::all_different_types_v...>, "all component types must be different"); if (!cache) cache = this->cache(); diff --git a/libs/ecs/tests/apply.cpp b/libs/ecs/tests/apply.cpp index a3ba249b..7f08759e 100644 --- a/libs/ecs/tests/apply.cpp +++ b/libs/ecs/tests/apply.cpp @@ -261,3 +261,15 @@ test_case(ecs_apply_create) }); expect_equal(call_count, 2 * count); } + +test_case(ecs_apply_const) +{ + container container; + + container.create(component_1{10}, component_2{20}); + container.apply([](component_1 & value1, component_2 const & value2){ + expect_equal(value1.value, 10); + expect_equal(value2.value, 20); + }); +} +