Order of callbacks was affected by the order of caches, which are pretty much arbitrary. For a fix, explicitly order the callbacks globally per-world.
740 lines
31 KiB
C++
740 lines
31 KiB
C++
#pragma once
|
|
|
|
#include <psemek/ecs/detail/entity_list.hpp>
|
|
#include <psemek/ecs/detail/table_container.hpp>
|
|
#include <psemek/ecs/detail/query_cache_container.hpp>
|
|
#include <psemek/ecs/detail/apply_helper.hpp>
|
|
#include <psemek/ecs/detail/all_different_types.hpp>
|
|
#include <psemek/ecs/detail/component_uuid_helper.hpp>
|
|
#include <psemek/ecs/detail/without.hpp>
|
|
#include <psemek/ecs/detail/component_registry.hpp>
|
|
#include <psemek/ecs/detail/index_container.hpp>
|
|
#include <psemek/ecs/accessor.hpp>
|
|
#include <psemek/ecs/exceptions.hpp>
|
|
#include <psemek/util/range.hpp>
|
|
#include <psemek/util/object_pool.hpp>
|
|
|
|
namespace psemek::ecs
|
|
{
|
|
|
|
using query_cache = std::shared_ptr<detail::query_cache>;
|
|
|
|
// TODO:
|
|
// - Fully document which functions can be called from which callbacks
|
|
// - Modification callbacks implementation (const-only)
|
|
// - Tables serialization
|
|
// - Refactor query caches
|
|
// - Index API
|
|
// - Index implementation
|
|
|
|
struct container
|
|
{
|
|
/** Register a component type within this container. This is used to properly deserialize
|
|
* a serialized container, otherwise it wouldn't know how to create entity tables from
|
|
* serialized data.
|
|
*
|
|
* Note that creating entities or attaching components to them automatically registers all
|
|
* referenced components.
|
|
*
|
|
* @tparam Component The component type to register
|
|
*/
|
|
template <typename Component>
|
|
void register_component();
|
|
|
|
/** Create an entity with the specified components. It is faster to create an entity
|
|
* with all the components at once than to create it with no components and `attach()`
|
|
* them one-by-one.
|
|
*
|
|
* After the function returns, the new entity is considered alive (`alive()` returns true).
|
|
*
|
|
* If the entity is created during iteration (inside an `apply()` call), it is unspecified
|
|
* whether the created entity will be visited by iteration or not.
|
|
*
|
|
* This function automatically registers all the entity's component types by calling `register_component()`.
|
|
*
|
|
* @param components The components to initialize the entity with
|
|
* @return A unique handle to the created entity
|
|
* @warning If any two of the passed component types are equal, the call fails with
|
|
* a compilation error
|
|
* @warning Creating a new entity invalidates all previously created accessors
|
|
*/
|
|
template <typename ... Components>
|
|
handle create(Components && ... components);
|
|
|
|
/** Check if an entity handle refers to an alive entity, i.e. one that wasn't
|
|
* destroyed yetby a `destroy()` call.
|
|
*
|
|
* @param entity A handle to the entity
|
|
* @return True if the entity is still alive, false otherwise
|
|
* @pre The entity was previously obtained by a `create()` call
|
|
*/
|
|
bool alive(handle entity) const;
|
|
|
|
/** Destroy an entity specified by a handle. After the call, `alive()` returns false for this handle.
|
|
*
|
|
* If the entity is destroyed during iteration (inside an `apply()` call), the iteration
|
|
* is guaranteed not to visit the destroyed entity (unless it did so before the entity
|
|
* was destroyed).
|
|
*
|
|
* @param entity A handle to the entity to be destroyed
|
|
* @pre The entity was previously obtained by a `create()` call and is `alive()`
|
|
*/
|
|
void destroy(handle entity);
|
|
|
|
/** Get an accessor for an entity, which provides access to the entity's components.
|
|
* It is designed to be a single-use object; it cannot be stored as a reference to
|
|
* the entity. Use handle for that instead.
|
|
*
|
|
* @param entity A handle to the entity to be destroyed
|
|
* @return An accessor to the specified entity
|
|
* @pre The entity was previously obtained by a `create()` call and is `alive()`
|
|
* @warning Creating or destroying entities, as well as attaching or detaching components,
|
|
* invalidates all previously created accessors
|
|
*/
|
|
accessor get(handle entity);
|
|
|
|
/** Compute a string representation of an entity, based on the contained components.
|
|
* A component has to implement a to_string() method in order to provide extra info.
|
|
*
|
|
* @param entity A handle to the entity to describe
|
|
* @return A string representation of an entity
|
|
* @pre The entity was previously obtained by a `create()` call and is `alive()`
|
|
*/
|
|
std::string describe(handle entity) const;
|
|
|
|
/** 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 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 the cloned entity
|
|
* @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 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<handle> try_clone(handle entity);
|
|
|
|
/** Attach new components to an existing entity, or update existing
|
|
* components with new values. Other components of this entity
|
|
* are left untouched.
|
|
*
|
|
* For the purposes of `apply()` semantics, attaching behaves as if
|
|
* the entity was destroyed and then recreated with the same handle.
|
|
*
|
|
* This function automatically registers all the new component types by calling `register_component()`.
|
|
*
|
|
* @param entity A handle to the entity to attach components to
|
|
* @param components The components to attach to the entity
|
|
* @pre The entity was previously obtained by a `create()` call and is `alive()`
|
|
* @warning If any two of the passed component types are equal, the call fails with
|
|
* a compilation error
|
|
* @warning Attaching components invalidates all previously created accessors
|
|
*/
|
|
template <typename ... Components>
|
|
void attach(handle entity, Components && ... components);
|
|
|
|
/** Detach (remove) components from an existing entity. Other components of this
|
|
* entity are left untouched. Detaching a component that doesn't exist is not
|
|
* an error, and does nothing.
|
|
*
|
|
* For the purposes of `apply()` semantics, detaching behaves as if
|
|
* the entity was destroyed and then recreated with the same handle.
|
|
*
|
|
* @param entity A handle to the entity to detach components from
|
|
* @tparam Components The component types to detach from the entity
|
|
* @pre The entity was previously obtained by a `create()` call and is `alive()`
|
|
* @warning If any two of the passed component types are equal, the call fails with
|
|
* a compilation error
|
|
* @warning Detaching components invalidates all previously created accessors
|
|
*/
|
|
template <typename ... Components>
|
|
void detach(handle entity);
|
|
|
|
/** Create a query cache that can be used to speed up `apply()` calls.
|
|
*
|
|
* The constness of the component types is ignored.
|
|
*
|
|
* The component types can be equal to ecs::without<Component>.
|
|
*
|
|
* @tparam Components The component types matching the corresponding `apply()` call
|
|
* @return A query cache
|
|
*/
|
|
template <typename ... Components>
|
|
query_cache cache();
|
|
|
|
/** Apply a function to all entities having the specified components, in unspecified order.
|
|
*
|
|
* The function must have one of the following signatures:
|
|
* void(components...)
|
|
* void(handle, components...)
|
|
* 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 component types can be equal to ecs::without<Component>, 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.
|
|
* The function is guaranteed not to visit destroyed entities (unless it did
|
|
* so before the entity was destroyed).
|
|
*
|
|
* An optional query cache can be supplied to speed up iteration. If the caller doesn't
|
|
* supply a query cache, a new query cache is created as if by calling `cache<Components...>()`.
|
|
* In any case, the (user-provided or newly-created) query cache is returned.
|
|
*
|
|
* @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, 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
|
|
* currently visited entity, the behavior is undefined
|
|
* @warning If the function recursively calls `apply()` or `batch_apply()`, the
|
|
* behavior is undefined
|
|
*/
|
|
template <typename ... Components, typename Function>
|
|
query_cache apply(Function && function, query_cache cache = {});
|
|
|
|
/** Apply a function to all entities having the specified components,
|
|
* in unspecified order. Instead of applying the function to each
|
|
* entity separately, it is applied in "batch" mode, i.e. to array
|
|
* views of respective components.
|
|
*
|
|
* The function must have one of the following signatures:
|
|
* void(span<components>...)
|
|
* void(span<handle const>, span<components>...)
|
|
* 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 component types can be equal to ecs::without<Component>, 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<Component> is true),
|
|
* each having an unspecified non-zero size. If all components are empty types,
|
|
* the function should use the entity handles array view to compute the number
|
|
* of visited entities.
|
|
*
|
|
* 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.
|
|
* The function is guaranteed not to visit destroyed entities (unless it did
|
|
* so before the entity was destroyed).
|
|
*
|
|
* An optional query cache can be supplied to speed up iteration. If the caller doesn't
|
|
* supply a query cache, a new query cache is created as if by calling `cache<Components...>()`.
|
|
* In any case, the (user-provided or newly-created) query cache is returned.
|
|
*
|
|
* @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, ignoring
|
|
* constness
|
|
* @warning If any two of the passed component types are equal, the call fails with
|
|
* a compilation error
|
|
* @warning If the function recursively calls `apply()` or `batch_apply()`, the behavior is undefined
|
|
*/
|
|
template <typename ... Components, typename Function>
|
|
query_cache batch_apply(Function && function, query_cache cache = {});
|
|
|
|
/** Register a constructor. Each time an entity is created that has
|
|
* the specified set of components, the constructor is called for
|
|
* this entity immediately after it is created.
|
|
*
|
|
* 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.
|
|
*
|
|
* The component types can be equal to ecs::without<Component>, 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.
|
|
* However, this matching check ignores the constructor's 'without' parameters.
|
|
* TODO: Figure out the behavior concerning 'without' parameters
|
|
*
|
|
* The constructor function must have the same signature as a function
|
|
* passed to the `apply<Components...>()` call.
|
|
*
|
|
* 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
|
|
* @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), the behavior is undefined
|
|
* @warning If the constructor destroys the entity, the behavior is undefined
|
|
*/
|
|
template <typename ... Components, typename 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
|
|
* this entity immediately before it will be destroyed.
|
|
*
|
|
* 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.
|
|
*
|
|
* The component types can be equal to ecs::without<Component>, 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.
|
|
* However, this matching check ignores the destructors's 'without' parameters.
|
|
* TODO: Figure out the behavior concerning 'without' parameters
|
|
*
|
|
* The destructor function must have the same signature as a function
|
|
* passed to the `apply<Components...>()` call.
|
|
*
|
|
* 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
|
|
* 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
|
|
*/
|
|
template <typename ... Components, typename 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
|
|
* 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.
|
|
*
|
|
* The component types can be equal to ecs::without<Component>, 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.
|
|
*
|
|
* If the modification occurred via an `apply()` or `batch_apply()` call,
|
|
* the callback is called immediately after this call.
|
|
*
|
|
* Note that modifications from within a modification callback are not considered
|
|
* as modifications, i.e. they don't recursively invoke modification callbacks.
|
|
*
|
|
* The callback function must have the same signature as a function
|
|
* passed to the `apply<Components...>()` call.
|
|
*
|
|
* @param function A function to be applied to modified entity's components
|
|
* @return An ownerwhip token; destroying it removes the modification callback from the container
|
|
* @warning If any two of the passed component types are equal, the call fails with
|
|
* a compilation error
|
|
* @warning If the modification callback modifies the entity's archetype (i.e. attaches or
|
|
* detaches components), the behavior is undefined
|
|
*
|
|
* TODO: can we allow the callback to modify the archetype?
|
|
*/
|
|
// TODO: implement
|
|
template <typename ... Components, typename Function>
|
|
void watch(Function && function);
|
|
|
|
template <typename Index, typename ... Args>
|
|
Index & index(Args && ... args);
|
|
|
|
template <typename ... Components>
|
|
std::size_t memory_usage();
|
|
|
|
std::size_t entity_count();
|
|
|
|
std::size_t cache_count();
|
|
|
|
std::size_t table_count();
|
|
|
|
private:
|
|
detail::entity_list entity_list_;
|
|
detail::table_container table_container_;
|
|
detail::query_cache_container query_cache_container_;
|
|
detail::component_registry component_registry_;
|
|
detail::index_container index_container_;
|
|
|
|
int next_constructor_id_ = 0;
|
|
int next_destructor_id_ = 0;
|
|
|
|
util::object_pool<std::vector<util::uuid>> uuid_list_pool_;
|
|
util::object_pool<util::hash_set<util::uuid>> uuid_set_pool_;
|
|
|
|
detail::table * insert_table(std::vector<std::unique_ptr<detail::column>> columns);
|
|
void do_destroy(handle entity);
|
|
void remove_row(detail::table & table, std::uint32_t row, util::span<detail::entity_data> entities);
|
|
void finalize_iteration(detail::table & table);
|
|
};
|
|
|
|
template <typename Component>
|
|
void container::register_component()
|
|
{
|
|
component_registry_.register_component<std::remove_cvref_t<Component>>();
|
|
}
|
|
|
|
template <typename ... Components>
|
|
handle container::create(Components && ... components)
|
|
{
|
|
static_assert(detail::all_different_types_v<std::remove_cvref_t<Components>...>, "all component types must be different");
|
|
|
|
(register_component<std::remove_cvref_t<Components>>(), ...);
|
|
|
|
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<std::remove_cvref_t<Components>>>()), ...);
|
|
|
|
table = insert_table(std::move(columns));
|
|
}
|
|
|
|
if (table->get_iteration_data())
|
|
table = table->get_delayed_table();
|
|
|
|
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::remove_cvref_t<Components>>() = std::forward<Components>(components)), ...);
|
|
|
|
table->trigger_constructors(*this, row);
|
|
|
|
return handle;
|
|
}
|
|
|
|
template <typename ... Components>
|
|
void container::attach(handle entity, Components && ... components)
|
|
{
|
|
static_assert(detail::all_different_types_v<std::remove_cvref_t<Components>...>, "all component types must be different");
|
|
|
|
(register_component<Components>(), ...);
|
|
|
|
auto uuids = uuid_list_pool_.get();
|
|
auto attached_uuid_set = uuid_set_pool_.get();
|
|
|
|
auto * data = entity_list_.get_entities().begin() + entity.id;
|
|
for (auto const & column : data->table->columns())
|
|
uuids.push_back(column->uuid());
|
|
|
|
bool archetype_changed = false;
|
|
((data->table->column(std::remove_cvref_t<Components>::uuid()) ? 0 : (archetype_changed = true, attached_uuid_set.insert(std::remove_cvref_t<Components>::uuid()), 0)), ...);
|
|
|
|
for (auto const & uuid : attached_uuid_set)
|
|
uuids.push_back(uuid);
|
|
|
|
auto table = table_container_.get(uuids);
|
|
|
|
if (!table)
|
|
{
|
|
std::vector<std::unique_ptr<detail::column>> columns;
|
|
for (auto const & column : data->table->columns())
|
|
columns.push_back(column->clone());
|
|
|
|
((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));
|
|
}
|
|
|
|
if (table != data->table)
|
|
{
|
|
if (table->get_iteration_data())
|
|
table = table->get_delayed_table();
|
|
|
|
auto new_row = table->move_row(entity, data->table, data->row);
|
|
do_destroy(entity);
|
|
|
|
data->table = table;
|
|
data->row = new_row;
|
|
}
|
|
|
|
auto accessor = get(entity);
|
|
((accessor.get<std::remove_cvref_t<Components>>() = std::forward<Components>(components)), ...);
|
|
|
|
if (archetype_changed)
|
|
table->trigger_constructors(*this, data->row, attached_uuid_set);
|
|
|
|
attached_uuid_set.clear();
|
|
uuids.clear();
|
|
|
|
uuid_set_pool_.put(std::move(attached_uuid_set));
|
|
uuid_list_pool_.put(std::move(uuids));
|
|
}
|
|
|
|
template <typename ... Components>
|
|
void container::detach(handle entity)
|
|
{
|
|
static_assert(detail::all_different_types_v<std::remove_const_t<Components>...>, "all component types must be different");
|
|
|
|
auto detached_uuid_set = uuid_set_pool_.get();
|
|
(detached_uuid_set.insert(std::remove_const_t<Components>::uuid()), ...);
|
|
|
|
auto uuids = uuid_list_pool_.get();
|
|
|
|
auto * data = entity_list_.get_entities().begin() + entity.id;
|
|
for (auto const & column : data->table->columns())
|
|
{
|
|
if (detached_uuid_set.contains(column->uuid()))
|
|
detached_uuid_set.insert(column->uuid());
|
|
else
|
|
uuids.push_back(column->uuid());
|
|
}
|
|
|
|
bool const archetype_changed = sizeof...(Components) > 0;
|
|
|
|
auto table = table_container_.get(uuids);
|
|
|
|
if (archetype_changed)
|
|
data->table->trigger_destructors(*this, data->row, detached_uuid_set);
|
|
|
|
// Destructors could lead to reallocation of entity list
|
|
data = entity_list_.get_entities().begin() + entity.id;
|
|
|
|
if (!table)
|
|
{
|
|
std::vector<std::unique_ptr<detail::column>> columns;
|
|
for (auto const & column : data->table->columns())
|
|
if (!detached_uuid_set.contains(column->uuid()))
|
|
columns.push_back(column->clone());
|
|
|
|
table = insert_table(std::move(columns));
|
|
}
|
|
|
|
if (table != data->table)
|
|
{
|
|
if (table->get_iteration_data())
|
|
table = table->get_delayed_table();
|
|
|
|
auto new_row = table->move_row(entity, data->table, data->row);
|
|
do_destroy(entity);
|
|
|
|
data->table = table;
|
|
data->row = new_row;
|
|
}
|
|
|
|
detached_uuid_set.clear();
|
|
uuids.clear();
|
|
detached_uuid_set.clear();
|
|
|
|
uuid_list_pool_.put(std::move(uuids));
|
|
uuid_set_pool_.put(std::move(detached_uuid_set));
|
|
}
|
|
|
|
template <typename ... Components>
|
|
query_cache container::cache()
|
|
{
|
|
typename detail::filter_with <detail::component_uuid_helper, std::tuple<std::remove_cvref_t<Components>...>>::type with_uuids;
|
|
typename detail::filter_without<detail::component_uuid_helper, std::tuple<std::remove_cvref_t<Components>...>>::type without_uuids;
|
|
|
|
return query_cache_container_.create(with_uuids.get(), without_uuids.get(), table_container_);
|
|
}
|
|
|
|
template <typename ... Components, typename Function>
|
|
query_cache container::apply(Function && function, query_cache cache)
|
|
{
|
|
static_assert(detail::all_different_types_v<std::remove_const_t<Components>...>, "all component types must be different");
|
|
|
|
using invocable_type = typename detail::filter_with<detail::invocable, std::tuple<Components...>, Function>::type;
|
|
|
|
static_assert(invocable_type::value, "function is not invocable with these components");
|
|
|
|
if (!cache)
|
|
cache = this->cache<Components...>();
|
|
|
|
for (auto const & entry : cache->entries)
|
|
{
|
|
auto & iteration_data = entry.table->get_iteration_data();
|
|
iteration_data.emplace();
|
|
typename detail::filter_with<detail::static_apply_helper, std::tuple<Components...>>::type apply_helper(*this, entry.table->entity_handles());
|
|
|
|
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)
|
|
{
|
|
iteration_data->current_row = i;
|
|
apply_helper.apply(function);
|
|
apply_helper.advance();
|
|
}
|
|
|
|
iteration_data.reset();
|
|
finalize_iteration(*entry.table);
|
|
}
|
|
|
|
return cache;
|
|
}
|
|
|
|
template <typename ... Components, typename Function>
|
|
query_cache container::batch_apply(Function && function, query_cache cache)
|
|
{
|
|
static_assert(detail::all_different_types_v<std::remove_const_t<Components>...>, "all component types must be different");
|
|
|
|
using invocable_type = typename detail::filter_with<detail::batch_invocable, std::tuple<Components...>, Function>::type;
|
|
|
|
static_assert(invocable_type::value, "function is not batch-invocable with these components");
|
|
|
|
if (!cache)
|
|
cache = this->cache<Components...>();
|
|
|
|
for (auto const & entry : cache->entries)
|
|
{
|
|
auto & iteration_data = entry.table->get_iteration_data();
|
|
iteration_data.emplace();
|
|
iteration_data->current_row = entry.table->row_count();
|
|
|
|
typename detail::filter_with<detail::static_apply_helper, std::tuple<Components...>>::type apply_helper(*this, entry.table->entity_handles());
|
|
|
|
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);
|
|
|
|
iteration_data.reset();
|
|
finalize_iteration(*entry.table);
|
|
}
|
|
|
|
return cache;
|
|
}
|
|
|
|
template <typename ... Components, typename Function>
|
|
void container::constructor(Function && function)
|
|
{
|
|
static_assert(detail::all_different_types_v<std::remove_const_t<Components>...>, "all component types must be different");
|
|
|
|
using invocable_type = typename detail::filter_with<detail::invocable, std::tuple<Components ...>, Function>::type;
|
|
|
|
static_assert(invocable_type::value, "function is not invocable with these components");
|
|
|
|
auto id = next_constructor_id_++;
|
|
|
|
auto constructor_factory = [function = std::move(function)](std::vector<std::uint32_t> const & column_indices) -> detail::table_callback {
|
|
return [function, column_indices](container & container, detail::table & table, std::uint32_t row, util::hash_set<util::uuid> const & attached_components, bool force){
|
|
|
|
bool const invoke = force || (detail::contains_helper<Components>::contains(attached_components) || ...);
|
|
if (!invoke)
|
|
return;
|
|
|
|
typename detail::filter_with<detail::static_apply_helper, std::tuple<Components ...>>::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);
|
|
};
|
|
};
|
|
|
|
query_cache cache = this->cache<Components...>();
|
|
|
|
cache->constructor_factories.push_back([id, constructor_factory](auto const & ... args){ return detail::ordered_table_callback{id, constructor_factory(args...)}; });
|
|
for (auto const & entry : cache->entries)
|
|
entry.table->add_constructor({id, constructor_factory(entry.columns_indices)});
|
|
}
|
|
|
|
template <typename ... Components, typename Function>
|
|
void container::destructor(Function && function)
|
|
{
|
|
static_assert(detail::all_different_types_v<std::remove_const_t<Components>...>, "all component types must be different");
|
|
|
|
using invocable_type = typename detail::filter_with<detail::invocable, std::tuple<Components ...>, Function>::type;
|
|
|
|
static_assert(invocable_type::value, "function is not invocable with these components");
|
|
|
|
auto id = next_destructor_id_++;
|
|
|
|
auto destructor_factory = [function = std::move(function)](std::vector<std::uint32_t> const & column_indices) -> detail::table_callback {
|
|
return [function, column_indices](container & container, detail::table & table, std::uint32_t row, util::hash_set<util::uuid> const & detached_components, bool force){
|
|
|
|
bool const invoke = force || (detail::contains_helper<Components>::contains(detached_components) || ...);
|
|
if (!invoke)
|
|
return;
|
|
|
|
typename detail::filter_with<detail::static_apply_helper, std::tuple<Components ...>>::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);
|
|
};
|
|
};
|
|
|
|
query_cache cache = this->cache<Components...>();
|
|
|
|
cache->destructor_factories.push_back([id, destructor_factory](auto const & ... args){ return detail::ordered_table_callback{id, destructor_factory(args...)}; });
|
|
for (auto const & entry : cache->entries)
|
|
entry.table->add_destructor({id, destructor_factory(entry.columns_indices)});
|
|
}
|
|
|
|
template <typename Index, typename ... Args>
|
|
Index & container::index(Args && ... args)
|
|
{
|
|
return index_container_.get<Index>(*this, std::forward<Args>(args)...);
|
|
}
|
|
|
|
template <typename ... Components>
|
|
std::size_t container::memory_usage()
|
|
{
|
|
auto cache = this->cache<Components...>();
|
|
std::size_t result = 0;
|
|
|
|
for (auto const & entry : cache->entries)
|
|
result += entry.table->memory_usage();
|
|
|
|
return result;
|
|
}
|
|
|
|
}
|