From b6c950ffc5dcbfa4294404fab544643d09045c60 Mon Sep 17 00:00:00 2001 From: Michele Caini Date: Wed, 15 Nov 2017 22:25:37 +0100 Subject: [PATCH] tests, tags and few other features --- README.md | 64 +++++++++- src/entt/entity/registry.hpp | 225 ++++++++++++++++++++++++++++++--- src/entt/entity/sparse_set.hpp | 21 +-- src/entt/entity/view.hpp | 2 +- src/entt/process/scheduler.hpp | 2 +- test/entt/entity/registry.cpp | 104 +++++++++++++++ 6 files changed, 385 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index 623eb7188..adcd63727 100644 --- a/README.md +++ b/README.md @@ -461,7 +461,7 @@ their components are destroyed: registry.reset(); ``` -Finally, references to components can be retrieved by just doing this: +Finally, references to components can be retrieved simply by doing this: ```cpp // either a non-const reference ... @@ -476,6 +476,68 @@ const auto &position = cregistry.get(entity); The `get` member function template gives direct access to the component of an entity stored in the underlying data structures of the registry. +### Single instance components + +In those cases where all what is needed is a single instance component, tags are +the right tool to achieve the purpose.
+Tags undergo the same requirements of components. They can be either plain old +data structures or more complex and moveable data structures with a proper +constructor.
+Actually, the same type can be used both as a tag and as a component and the +registry will not complain about it. It is up to the users to properly manage +their own types. + +Attaching tags to entities and removing them is trivial: + +```cpp +auto player = registry.create(); +auto camera = registry.create(); + +// attaches a default-initialized tag to an entity +registry.attach(player); + +// attaches a tag to an entity and initializes it +registry.attach(camera, player); + +// removes tags from their owners +registry.remove(); +registry.remove(); +``` + +If in doubt about whether or not a tag has already an owner, the `has` member +function template may be useful: + +```cpp +bool b = registry.has(); +``` + +References to tags can be retrieved simply by doing this: + +```cpp +// either a non-const reference ... +entt::DefaultRegistry registry; +auto &player = registry.get(); + +// ... or a const one +const auto &cregistry = registry; +const auto &camera = cregistry.get(); +``` + +The `get` member function template gives direct access to the tag as stored in +the underlying data structures of the registry. + +As shown above, in almost all the cases the entity identifier isn't required, +since a single instance component can have only one associated entity and +therefore it doesn't make much sense to mention it explicitly.
+To find out who the owner is, just do the following: + +```cpp +auto player = registry.attachee(); +``` + +Note that iterating tags isn't possible for obvious reasons. Tags give direct +access to single entities and nothing more. + ### Sorting: is it possible? It goes without saying that sorting entities and components is possible with diff --git a/src/entt/entity/registry.hpp b/src/entt/entity/registry.hpp index 7dd4d7487..5d549ef71 100644 --- a/src/entt/entity/registry.hpp +++ b/src/entt/entity/registry.hpp @@ -7,6 +7,7 @@ #include #include #include +#include #include "../core/family.hpp" #include "sparse_set.hpp" #include "traits.hpp" @@ -28,10 +29,26 @@ namespace entt { */ template class Registry { + using tag_family = Family; using component_family = Family; using view_family = Family; using traits_type = entt_traits; + struct Attachee { + Entity entity; + }; + + template + struct Attaching: Attachee { + // requirements for aggregates are relaxed only since C++17 + template + Attaching(Entity entity, Tag tag) + : Attachee{entity}, tag{std::move(tag)} + {} + + Tag tag; + }; + template struct Pool: SparseSet { using test_fn_type = bool(Registry::*)(Entity) const; @@ -42,7 +59,7 @@ class Registry { for(auto &&listener: listeners) { if((registry.*listener.second)(entity)) { - listener.first.construct(entity); + listener.first->construct(entity); } } @@ -53,20 +70,26 @@ class Registry { SparseSet::destroy(entity); for(auto &&listener: listeners) { - auto &handler = listener.first; + auto *handler = listener.first; - if(handler.has(entity)) { - handler.destroy(entity); + if(handler->has(entity)) { + handler->destroy(entity); } } } - inline void append(SparseSet &handler, test_fn_type fn) { + inline void append(SparseSet *handler, test_fn_type fn) { listeners.emplace_back(handler, fn); } + inline void remove(SparseSet *handler) { + listeners.erase(std::remove_if(listeners.begin(), listeners.end(), [handler](auto &listener) { + return listener.first == handler; + }), listeners.end()); + } + private: - std::vector &, test_fn_type>> listeners; + std::vector *, test_fn_type>> listeners; }; template @@ -121,7 +144,7 @@ class Registry { } accumulator_type accumulator = { - (ensure().append(*set, &Registry::has), 0)... + (ensure().append(set.get(), &Registry::has), 0)... }; handlers[vtype] = std::move(set); @@ -358,6 +381,122 @@ class Registry { } } + /** + * @brief Attaches a tag to an entity. + * + * Usually, pools of components allocate enough memory to store a bunch of + * elements even if only one of them is used. On the other hand, there are + * cases where all what is needed is a single instance component to attach + * to an entity.
+ * Tags are the right tool to achieve the purpose. + * + * @warning + * Attempting to use an invalid entity or to attach to an entity a tag that + * already has an owner results in undefined behavior.
+ * An assertion will abort the execution at runtime in debug mode in case of + * invalid entity or if the tag has been already attached to another entity. + * + * @tparam Tag Type of tag to create. + * @tparam Args Types of arguments to use to construct the tag. + * @param entity A valid entity identifier + * @param args Parameters to use to initialize the tag. + * @return A reference to the newly created tag. + */ + template + Tag & attach(entity_type entity, Args&&... args) { + assert(valid(entity)); + assert(!has()); + const auto ttype = tag_family::type(); + + if(!(ttype < tags.size())) { + tags.resize(ttype + 1); + } + + tags[ttype].reset(new Attaching{entity, { std::forward(args)... }}); + tags[ttype]->entity = entity; + + return static_cast *>(tags[ttype].get())->tag; + } + + /** + * @brief Removes a tag from its owner, if any. + * @tparam Tag Type of tag to remove. + */ + template + void remove() { + if(has()) { + tags[tag_family::type()].reset(); + } + } + + /** + * @brief Checks if a tag has an owner. + * @tparam Tag Type of tag for which to perform the check. + * @return True if the tag already has an owner, false otherwise. + */ + template + bool has() const noexcept { + const auto ttype = tag_family::type(); + return (ttype < tags.size() && + // it's a valid tag + tags[ttype] && + // the associated entity hasn't been destroyed in the meantime + tags[ttype]->entity == (entities[tags[ttype]->entity & traits_type::entity_mask])); + } + + /** + * @brief Returns a reference to a tag. + * + * @warning + * Attempting to get a tag that hasn't an owner results in undefined + * behavior.
+ * An assertion will abort the execution at runtime in debug mode if the + * tag hasn't been previously attached to an entity. + * + * @tparam Tag Type of tag to get. + * @return A reference to the tag. + */ + template + const Tag & get() const noexcept { + assert(has()); + return static_cast *>(tags[tag_family::type()].get())->tag; + } + + /** + * @brief Returns a reference to a tag. + * + * @warning + * Attempting to get a tag that hasn't an owner results in undefined + * behavior.
+ * An assertion will abort the execution at runtime in debug mode if the + * tag hasn't been previously attached to an entity. + * + * @tparam Tag Type of tag to get. + * @return A reference to the tag. + */ + template + Tag & get() noexcept { + return const_cast(const_cast(this)->get()); + } + + /** + * @brief Gets the owner of a tag, if any. + * + * @warning + * Attempting to get the owner of a tag that hasn't been previously attached + * to an entity results in undefined behavior.
+ * An assertion will abort the execution at runtime in debug mode if the + * tag hasn't an owner. + * + * @tparam Tag Type of tag of which to get the owner. + * @return A valid entity identifier. + */ + template + entity_type attachee() const noexcept { + assert(has()); + return tags[tag_family::type()]->entity; + } + /** * @brief Assigns the given component to an entity. * @@ -372,7 +511,7 @@ class Registry { * invalid entity or if the entity already owns an instance of the given * component. * - * @tparam Component Type of the component to create. + * @tparam Component Type of component to create. * @tparam Args Types of arguments to use to construct the component. * @param entity A valid entity identifier. * @param args Parameters to use to initialize the component. @@ -394,7 +533,7 @@ class Registry { * invalid entity or if the entity doesn't own an instance of the given * component. * - * @tparam Component Type of the component to remove. + * @tparam Component Type of component to remove. * @param entity A valid entity identifier. */ template @@ -436,7 +575,7 @@ class Registry { * invalid entity or if the entity doesn't own an instance of the given * component. * - * @tparam Component Type of the component to get. + * @tparam Component Type of component to get. * @param entity A valid entity identifier. * @return A reference to the instance of the component owned by the entity. */ @@ -456,7 +595,7 @@ class Registry { * invalid entity or if the entity doesn't own an instance of the given * component. * - * @tparam Component Type of the component to get. + * @tparam Component Type of component to get. * @param entity A valid entity identifier. * @return A reference to the instance of the component owned by the entity. */ @@ -479,7 +618,7 @@ class Registry { * invalid entity or if the entity doesn't own an instance of the given * component. * - * @tparam Component Type of the component to replace. + * @tparam Component Type of component to replace. * @tparam Args Types of arguments to use to construct the component. * @param entity A valid entity identifier. * @param args Parameters to use to initialize the component. @@ -512,7 +651,7 @@ class Registry { * An assertion will abort the execution at runtime in debug mode in case of * invalid entity. * - * @tparam Component Type of the component to assign or replace. + * @tparam Component Type of component to assign or replace. * @tparam Args Types of arguments to use to construct the component. * @param entity A valid entity identifier. * @param args Parameters to use to initialize the component. @@ -549,8 +688,8 @@ class Registry { * * Where `e1` and `e2` are valid entity identifiers. * - * @tparam Component Type of the components to sort. - * @tparam Compare Type of the comparison function object. + * @tparam Component Type of components to sort. + * @tparam Compare Type of comparison function object. * @param compare A valid comparison function object. */ template @@ -589,8 +728,8 @@ class Registry { * * Any subsequent change to `B` won't affect the order in `A`. * - * @tparam To Type of the components to sort. - * @tparam From Type of the components to use to sort. + * @tparam To Type of components to sort. + * @tparam From Type of components to use to sort. */ template void sort() { @@ -608,7 +747,7 @@ class Registry { * An assertion will abort the execution at runtime in debug mode in case of * invalid entity. * - * @tparam Component Type of the component to reset. + * @tparam Component Type of component to reset. * @param entity A valid entity identifier. */ template @@ -630,7 +769,7 @@ class Registry { * For each entity that has an instance of the given component, the * component itself is removed and thus destroyed. * - * @tparam Component type of the component whose pool must be reset. + * @tparam Component Type of component whose pool must be reset. */ template void reset() { @@ -673,6 +812,10 @@ class Registry { pool->reset(); } } + + for(auto &&tag: tags) { + tag.reset(); + } } /** @@ -733,6 +876,46 @@ class Registry { handler(); } + /** + * @brief Discards all the data structures used for a given persitent view. + * + * Persistent views occupy memory, no matter if they are in use or not.
+ * This function can be used to discard all the internal data structures + * dedicated to a specific persisten view, with the goal of reducing the + * memory pressure. + * + * @warning + * Attempting to use a persistent view created before calling this function + * results in undefined behavior. No assertion available in this case, + * neither in debug mode nor in release mode. + * + * @tparam Component Types of components of the persistent view. + */ + template + void discard() { + if(contains()) { + using accumulator_type = int[]; + const auto vtype = view_family::type(); + auto *set = handlers[vtype].get(); + // if a set exists, pools have already been created for it + accumulator_type accumulator = { (pool().remove(set), 0)... }; + handlers[vtype].reset(); + (void)accumulator; + } + } + + /** + * @brief Checks if a persistent view has already been prepared. + * @tparam Component Types of components of the persistent view. + * @return True if the view has already been prepared, false otherwise. + */ + template + bool contains() const noexcept { + static_assert(sizeof...(Component) > 1, "!"); + const auto vtype = view_family::type(); + return vtype < handlers.size() && handlers[vtype]; + } + /** * @brief Returns a persistent view for the given components. * @@ -772,12 +955,14 @@ class Registry { */ template PersistentView persistent() { - return PersistentView{handler(), ensure()...}; + // after the calls to handler, pools have already been created + return PersistentView{handler(), pool()...}; } private: std::vector>> handlers; std::vector>> pools; + std::vector> tags; std::vector available; std::vector entities; }; diff --git a/src/entt/entity/sparse_set.hpp b/src/entt/entity/sparse_set.hpp index 9100276fd..7cd1f19ce 100644 --- a/src/entt/entity/sparse_set.hpp +++ b/src/entt/entity/sparse_set.hpp @@ -264,7 +264,7 @@ class SparseSet { // has size 1), switching the two lines below doesn't work as expected reverse[back] = pos | in_use; reverse[entt] = pos; - // swap-and-pop the last element with the selected ont + // swapping isn't required here, we are getting rid of the last element direct[pos] = direct.back(); direct.pop_back(); } @@ -412,7 +412,7 @@ class SparseSet: public SparseSet { public: /*! @brief Type of the objects associated to the entities. */ - using type = Type; + using object_type = Type; /*! @brief Underlying entity identifier. */ using entity_type = typename underlying_type::entity_type; /*! @brief Entity dependent position type. */ @@ -450,7 +450,7 @@ class SparseSet: public SparseSet { * * @return A pointer to the array of objects. */ - const type * raw() const noexcept { + const object_type * raw() const noexcept { return instances.data(); } @@ -469,7 +469,7 @@ class SparseSet: public SparseSet { * * @return A pointer to the array of objects. */ - type * raw() noexcept { + object_type * raw() noexcept { return instances.data(); } @@ -485,7 +485,7 @@ class SparseSet: public SparseSet { * @param entity A valid entity identifier. * @return The object associated to the entity. */ - const type & get(entity_type entity) const noexcept { + const object_type & get(entity_type entity) const noexcept { return instances[underlying_type::get(entity)]; } @@ -501,8 +501,8 @@ class SparseSet: public SparseSet { * @param entity A valid entity identifier. * @return The object associated to the entity. */ - type & get(entity_type entity) noexcept { - return const_cast(const_cast(this)->get(entity)); + object_type & get(entity_type entity) noexcept { + return const_cast(const_cast(this)->get(entity)); } /** @@ -520,8 +520,9 @@ class SparseSet: public SparseSet { * @return The object associated to the entity. */ template - type & construct(entity_type entity, Args&&... args) { + object_type & construct(entity_type entity, Args&&... args) { underlying_type::construct(entity); + // emplace_back doesn't work well with PODs because of its placement new instances.push_back({ std::forward(args)... }); return instances.back(); } @@ -538,7 +539,7 @@ class SparseSet: public SparseSet { * @param entity A valid entity identifier. */ void destroy(entity_type entity) override { - // swaps isn't required here, we are getting rid of the last element + // swapping isn't required here, we are getting rid of the last element instances[underlying_type::get(entity)] = std::move(instances.back()); instances.pop_back(); underlying_type::destroy(entity); @@ -574,7 +575,7 @@ class SparseSet: public SparseSet { } private: - std::vector instances; + std::vector instances; }; diff --git a/src/entt/entity/view.hpp b/src/entt/entity/view.hpp index e474128c2..15ba82e64 100644 --- a/src/entt/entity/view.hpp +++ b/src/entt/entity/view.hpp @@ -571,7 +571,7 @@ class View final { /*! @brief Unsigned integer type. */ using size_type = typename pool_type::size_type; /*! Type of the component iterated by the view. */ - using raw_type = typename pool_type::type; + using raw_type = typename pool_type::object_type; /** * @brief Constructs a view out of a pool of components. diff --git a/src/entt/process/scheduler.hpp b/src/entt/process/scheduler.hpp index f76ee017b..9c78a5678 100644 --- a/src/entt/process/scheduler.hpp +++ b/src/entt/process/scheduler.hpp @@ -292,7 +292,7 @@ class Scheduler final { * Unless an immediate operation is requested, the abort is scheduled for * the next tick. Processes won't be executed anymore in any case.
* Once a process is fully aborted and thus finished, it's discarded along - * with its child if any. + * with its child, if any. * * @param immediately Requests an immediate operation. */ diff --git a/test/entt/entity/registry.cpp b/test/entt/entity/registry.cpp index a62598b7c..87f5bbd0a 100644 --- a/test/entt/entity/registry.cpp +++ b/test/entt/entity/registry.cpp @@ -134,6 +134,110 @@ TEST(DefaultRegistry, CreateDestroyEntities) { ASSERT_EQ(registry.current(pre), registry.current(post)); } +TEST(DefaultRegistry, AttachRemoveTags) { + entt::DefaultRegistry registry; + const auto &cregistry = registry; + + ASSERT_FALSE(registry.has()); + + auto entity = registry.create(); + registry.attach(entity, 42); + + ASSERT_TRUE(registry.has()); + ASSERT_EQ(registry.get(), 42); + ASSERT_EQ(cregistry.get(), 42); + ASSERT_EQ(registry.attachee(), entity); + + registry.remove(); + + ASSERT_FALSE(registry.has()); + + registry.attach(entity, 42); + registry.destroy(entity); + + ASSERT_FALSE(registry.has()); +} + +TEST(DefaultRegistry, StandardViews) { + entt::DefaultRegistry registry; + auto mview = registry.view(); + auto iview = registry.view(); + auto cview = registry.view(); + + registry.create(0, 'c'); + registry.create(0); + registry.create(0, 'c'); + + ASSERT_EQ(iview.size(), decltype(iview)::size_type{3}); + ASSERT_EQ(cview.size(), decltype(cview)::size_type{2}); + + decltype(mview)::size_type cnt{0}; + mview.each([&cnt](auto...) { ++cnt; }); + + ASSERT_EQ(cnt, decltype(mview)::size_type{2}); +} + +TEST(DefaultRegistry, PersistentViews) { + entt::DefaultRegistry registry; + auto view = registry.persistent(); + + ASSERT_TRUE((registry.contains())); + ASSERT_FALSE((registry.contains())); + + registry.prepare(); + + ASSERT_TRUE((registry.contains())); + + registry.discard(); + + ASSERT_FALSE((registry.contains())); + + registry.create(0, 'c'); + registry.create(0); + registry.create(0, 'c'); + + decltype(view)::size_type cnt{0}; + view.each([&cnt](auto...) { ++cnt; }); + + ASSERT_EQ(cnt, decltype(view)::size_type{2}); +} + +TEST(DefaultRegistry, CleanStandardViewsAfterReset) { + entt::DefaultRegistry registry; + auto view = registry.view(); + registry.create(0); + + ASSERT_EQ(view.size(), entt::DefaultRegistry::size_type{1}); + + registry.reset(); + + ASSERT_EQ(view.size(), entt::DefaultRegistry::size_type{0}); +} + +TEST(DefaultRegistry, CleanPersistentViewsAfterReset) { + entt::DefaultRegistry registry; + auto view = registry.persistent(); + registry.create(0, 'c'); + + ASSERT_EQ(view.size(), entt::DefaultRegistry::size_type{1}); + + registry.reset(); + + ASSERT_EQ(view.size(), entt::DefaultRegistry::size_type{0}); +} + +TEST(DefaultRegistry, CleanTagsAfterReset) { + entt::DefaultRegistry registry; + auto entity = registry.create(); + registry.attach(entity); + + ASSERT_TRUE(registry.has()); + + registry.reset(); + + ASSERT_FALSE(registry.has()); +} + TEST(DefaultRegistry, SortSingle) { entt::DefaultRegistry registry;