Support const-qualified components in ecs::container::apply and add const-related docs

This commit is contained in:
Nikita Lisitsa 2023-12-16 21:51:45 +03:00
parent 2a97a467aa
commit eb87f1ea20
2 changed files with 56 additions and 15 deletions

View file

@ -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<components>...)
* void(container, span<handle const>, span<components>...)
*
* 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<Component> 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 <typename ... Components>
handle container::create(Components && ... components)
{
static_assert(detail::all_different_types_v<Components...>, "all component types must be different");
static_assert(detail::all_different_types_v<std::remove_cvref_t<Components>...>, "all component types must be different");
detail::component_uuid_helper<Components...> uuids;
detail::component_uuid_helper<std::remove_cvref_t<Components>...> uuids;
auto table = table_container_.get(uuids.get());
if (!table)
{
std::vector<std::unique_ptr<detail::column>> columns;
(columns.push_back(std::make_unique<detail::column_impl<Components>>()), ...);
(columns.push_back(std::make_unique<detail::column_impl<std::remove_cvref_t<Components>>>()), ...);
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<Components>() = std::move(components)), ...);
((accessor.get<std::remove_cvref_t<Components>>() = std::forward<Components>(components)), ...);
return handle;
}
@ -325,13 +354,13 @@ namespace psemek::ecs
template <typename ... Components>
void container::attach(handle const & entity, Components && ... components)
{
static_assert(detail::all_different_types_v<Components...>, "all component types must be different");
static_assert(detail::all_different_types_v<std::remove_cvref_t<Components>...>, "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<Components>::uuid()) ? 0 : (uuid_helper_.push_back(std::remove_cvref_t<Components>::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<detail::column_impl<Components>>()), 0)), ...);
((data.table->column(std::remove_cvref_t<Components>::uuid()) ? 0 : (columns.push_back(std::make_unique<detail::column_impl<std::remove_cvref_t<Components>>>()), 0)), ...);
table = insert_table(std::move(columns));
}
@ -359,7 +388,7 @@ namespace psemek::ecs
}
auto accessor = get(entity);
((accessor.get<Components>() = std::move(components)), ...);
((accessor.get<std::remove_cvref_t<Components>>() = std::forward<Components>(components)), ...);
uuid_helper_.clear();
}
@ -367,9 +396,9 @@ namespace psemek::ecs
template <typename ... Components>
void container::detach(handle const & entity)
{
static_assert(detail::all_different_types_v<Components...>, "all component types must be different");
static_assert(detail::all_different_types_v<std::remove_cvref_t<Components>...>, "all component types must be different");
(uuid_set_helper_.insert(Components::uuid()), ...);
(uuid_set_helper_.insert(std::remove_cvref_t<Components>::uuid()), ...);
auto & data = entity_list_.get_entities()[entity.id];
for (auto const & column : data.table->columns())
@ -407,7 +436,7 @@ namespace psemek::ecs
template <typename ... Components>
query_cache container::cache()
{
detail::component_uuid_helper<Components...> uuids;
detail::component_uuid_helper<std::remove_cvref_t<Components>...> uuids;
auto result = query_cache_container_.create(uuids.get());
@ -421,7 +450,7 @@ namespace psemek::ecs
template <typename ... Components, typename Function>
query_cache container::apply(Function && function, query_cache cache)
{
static_assert(detail::all_different_types_v<Components...>, "all component types must be different");
static_assert(detail::all_different_types_v<std::remove_const_t<Components>...>, "all component types must be different");
if (!cache)
cache = this->cache<Components...>();
@ -465,7 +494,7 @@ namespace psemek::ecs
template <typename ... Components, typename Function>
query_cache container::batch_apply(Function && function, query_cache cache)
{
static_assert(detail::all_different_types_v<Components...>, "all component types must be different");
static_assert(detail::all_different_types_v<std::remove_const_t<Components>...>, "all component types must be different");
if (!cache)
cache = this->cache<Components...>();

View file

@ -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, component_2 const>([](component_1 & value1, component_2 const & value2){
expect_equal(value1.value, 10);
expect_equal(value2.value, 20);
});
}