diff --git a/libs/ecs/include/psemek/ecs/entity_accessor.hpp b/libs/ecs/include/psemek/ecs/entity_accessor.hpp deleted file mode 100644 index 075ee83e..00000000 --- a/libs/ecs/include/psemek/ecs/entity_accessor.hpp +++ /dev/null @@ -1,50 +0,0 @@ -#pragma once - -#include -#include - -namespace psemek::ecs -{ - - struct entity_accessor - { - entity_accessor(detail::table * table, std::uint32_t row) - : table_(table) - , row_(row) - {} - - template - Component * get_if() - { - util::uuid const uuid = Component::uuid(); - - auto const component_uuids = table_->get_component_uuids(); - - for (std::size_t i = 0; i < component_uuids.size(); ++i) - if (uuid == component_uuids[i]) - return reinterpret_cast(table_->get_component_pointers()[i].data + detail::stride() * row_); - - return nullptr; - } - - template - Component & get() - { - if (auto ptr = get_if()) - return *ptr; - assert(false); - __builtin_unreachable(); - } - - template - bool contains() const - { - return get_if() != nullptr; - } - - private: - detail::table * table_; - std::uint32_t row_; - }; - -} diff --git a/libs/ecs/include/psemek/ecs/entity_container.hpp b/libs/ecs/include/psemek/ecs/entity_container.hpp index c3ddf5fe..04df8620 100644 --- a/libs/ecs/include/psemek/ecs/entity_container.hpp +++ b/libs/ecs/include/psemek/ecs/entity_container.hpp @@ -6,79 +6,183 @@ #include #include #include -#include #include #include +#include +#include +#include + +#include namespace psemek::ecs { using query_cache = std::shared_ptr; + struct component_exception + : util::exception + { + component_exception(std::type_info const & type, entity_handle const & handle, boost::stacktrace::stacktrace stacktrace = {}) + : util::exception(util::to_string("Component ", util::type_name(type), " not found for entity ", handle), std::move(stacktrace)) + , type_(type) + , handle_(handle) + {} + + std::type_info const & type() const + { + return type_; + } + + entity_handle const & handle() const + { + return handle_; + } + + private: + std::type_info const & type_; + entity_handle handle_; + }; + + struct entity_accessor + { + entity_accessor() = default; + entity_accessor(entity_accessor const &) = default; + + explicit operator bool() const; + + /** Obtain a pointer to the specified component type + * of the accessed entity, or a null pointer if + * the entity doesn't contain this component type. + */ + template + Component * get_if(); + + /** Obtain a reference to the specified component type + * of the accessed entity. + * If the entity doesn't contain this component type, + * an exception of type `component_exception` is thrown. + */ + template + Component & get(); + + /** Check if the entity contains this component type. + */ + template + bool contains() const; + + private: + detail::table * table_ = nullptr; + std::uint32_t row_ = 0; + + friend struct entity_container; + + entity_accessor(detail::table * table, std::uint32_t row); + }; + struct entity_container { - // Create an entity with the specified components - // NB: equivalent to create() followed by a - // series of attach() calls, but faster - // All components must be unique + /** 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. + * If any two of the passed component types are equal, the call fails with + * a compilation error. + * After the function returns, the new entity is considered alive (`alive(handle)` returns true). + * Creating a new entity invalidates all previously created entity accessors. + * 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. + */ template entity_handle create(Components && ... components); - // Check if an entity handle refers to an active entity - // UB if the handle wasn't obtained by a create() call + /** Check if an entity handle refers to an alive entity (i.e. one that wasn't + * destroyed yet. + * Destroying an entity invalidates all previously created entity accessors. + * If the handle wasn't previously obtained by a `create()` call, the + * behavior is undefined. + */ bool alive(entity_handle const & entity) const; - // Destroy an entity - // UB if the handle wasn't obtained by a create() call - // or if the entity was already destroyed + /** 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). + * If the handle wasn't previously obtained by a `create()` call, or + * the refered entity was already destroyed, the behavior is undefined. + */ void destroy(entity_handle const & entity); - // Get an entity accessor for an entity - // UB if the handle wasn't obtained by a create() call - // or if the entity isn't active + /** Get an entity accessor for an entity, which provides access to + * the entitie's components. + * If the handle wasn't previously obtained by a `create()` call, or + * the refered entity was already destroyed, the behavior is undefined. + * Creating or destroying entities invalidates all previously created + * entity accessors. + */ entity_accessor get(entity_handle const & entity); - // Attach new components to an existing entity - // If the entity already has a component, its value - // is replaced with a new one + /** Attach new components to an existing entity, or update existing + * components with new values. + * If any two of the passed component types are equal, the call fails with + * a compilation error. + * Attaching components invalidates all previously created entity accessors. + * If the handle wasn't previously obtained by a `create()` call, or + * the refered entity was already destroyed, the behavior is undefined. + */ // TODO: implement template void attach(entity_handle const & entity, Components && ... components); - // Remove components from an existing entity + /** Detach (remove) components from an existing entity. + * Detaching components invalidates all previously created entity accessors. + * If the handle wasn't previously obtained by a `create()` call, or + * the refered entity was already destroyed, the behavior is undefined. + */ // TODO: implement template void detach(entity_handle const & entity); - // Create a query cache that can be used for the - // apply() call to speed it up + /** Create a query cache that can be used for speeding up + * the `apply()` calls. + */ template query_cache cache(); - // Apply a function to all entities having the specified - // components. The function signature is one of - // void(components...) - // void(entity_handle, components...) - // void(entity_container, components...) - // void(entity_container, entity_handle, components...) - // The function can create or destroy any entities. - // An optional query cache can be supplied to speed up the call - // UB if the cache wasn't created with the exact same component sequence - // UB if accessing the entitie's component after destroying it + /** 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(entity_handle, components...) + * void(entity_container, components...) + * void(entity_container, entity_handle, components...) + * The function can freely create or destroy 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 query cache wasn't created with the exact sequence of component + * types, the behavior is undefined. + * If the function accesses passed components after destroying the + * currently visited entity, the behavior is undefined. + * If the function recursively calls `apply()`, the behavior is undefined. + */ template void apply(Function && function, query_cache cache = {}); - // Apply a function to all entities having the specified - // components. Instead of applying the function to each entity, - // the function is applied in "batch" mode, i.e. to array views - // of components. The function signature is one of - // void(span...) - // void(span, span...) - // void(entity_container, span...) - // void(entity_container, span, span...) - // An optional query cache can be supplied to speed up the call - // UB if the cache wasn't created with the exact same component sequence - // UB if the function tries to create or destroy entities + /** 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...) + * void(span, span...) + * void(entity_container, span...) + * void(entity_container, span, span...) + * An optional query cache can be supplied to speed up iteration. + * If the query cache wasn't created with the exact sequence of component + * types, the behavior is undefined. + * If the function creates or destroyes entities, the behavior is undefined. + */ template void batch_apply(Function && function, query_cache cache = {}); @@ -195,4 +299,43 @@ namespace psemek::ecs } } + inline entity_accessor::operator bool() const + { + return table_ != nullptr; + } + + template + Component * entity_accessor::get_if() + { + util::uuid const uuid = Component::uuid(); + + auto const component_uuids = table_->get_component_uuids(); + + for (std::size_t i = 0; i < component_uuids.size(); ++i) + if (uuid == component_uuids[i]) + return reinterpret_cast(table_->get_component_pointers()[i].data + detail::stride() * row_); + + return nullptr; + } + + template + Component & entity_accessor::get() + { + if (auto ptr = get_if()) + return *ptr; + assert(false); + __builtin_unreachable(); + } + + template + bool entity_accessor::contains() const + { + return get_if() != nullptr; + } + + inline entity_accessor::entity_accessor(detail::table * table, std::uint32_t row) + : table_(table) + , row_(row) + {} + }