tests, tags and few other features

This commit is contained in:
Michele Caini
2017-11-15 22:25:37 +01:00
parent 8b89c69d5f
commit b6c950ffc5
6 changed files with 385 additions and 33 deletions

View File

@@ -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<Position>(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.<br/>
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.<br/>
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<PlayingCharacter>(player);
// attaches a tag to an entity and initializes it
registry.attach<Camera>(camera, player);
// removes tags from their owners
registry.remove<PlayingCharacter>();
registry.remove<Camera>();
```
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<PlayingCharacter>();
```
References to tags can be retrieved simply by doing this:
```cpp
// either a non-const reference ...
entt::DefaultRegistry registry;
auto &player = registry.get<PlayingCharacter>();
// ... or a const one
const auto &cregistry = registry;
const auto &camera = cregistry.get<Camera>();
```
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.<br/>
To find out who the owner is, just do the following:
```cpp
auto player = registry.attachee<PlayingCharacter>();
```
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

View File

@@ -7,6 +7,7 @@
#include <utility>
#include <cstddef>
#include <cassert>
#include <algorithm>
#include "../core/family.hpp"
#include "sparse_set.hpp"
#include "traits.hpp"
@@ -28,10 +29,26 @@ namespace entt {
*/
template<typename Entity>
class Registry {
using tag_family = Family<struct InternalRegistryTagFamily>;
using component_family = Family<struct InternalRegistryComponentFamily>;
using view_family = Family<struct InternalRegistryViewFamily>;
using traits_type = entt_traits<Entity>;
struct Attachee {
Entity entity;
};
template<typename Tag>
struct Attaching: Attachee {
// requirements for aggregates are relaxed only since C++17
template<typename... Args>
Attaching(Entity entity, Tag tag)
: Attachee{entity}, tag{std::move(tag)}
{}
Tag tag;
};
template<typename Component>
struct Pool: SparseSet<Entity, Component> {
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<Entity, Component>::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<Entity> &handler, test_fn_type fn) {
inline void append(SparseSet<Entity> *handler, test_fn_type fn) {
listeners.emplace_back(handler, fn);
}
inline void remove(SparseSet<Entity> *handler) {
listeners.erase(std::remove_if(listeners.begin(), listeners.end(), [handler](auto &listener) {
return listener.first == handler;
}), listeners.end());
}
private:
std::vector<std::pair<SparseSet<Entity> &, test_fn_type>> listeners;
std::vector<std::pair<SparseSet<Entity> *, test_fn_type>> listeners;
};
template<typename Component>
@@ -121,7 +144,7 @@ class Registry {
}
accumulator_type accumulator = {
(ensure<Component>().append(*set, &Registry::has<Component...>), 0)...
(ensure<Component>().append(set.get(), &Registry::has<Component...>), 0)...
};
handlers[vtype] = std::move(set);
@@ -358,6 +381,122 @@ public:
}
}
/**
* @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.<br/>
* 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.<br/>
* 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<typename Tag, typename... Args>
Tag & attach(entity_type entity, Args&&... args) {
assert(valid(entity));
assert(!has<Tag>());
const auto ttype = tag_family::type<Tag>();
if(!(ttype < tags.size())) {
tags.resize(ttype + 1);
}
tags[ttype].reset(new Attaching<Tag>{entity, { std::forward<Args>(args)... }});
tags[ttype]->entity = entity;
return static_cast<Attaching<Tag> *>(tags[ttype].get())->tag;
}
/**
* @brief Removes a tag from its owner, if any.
* @tparam Tag Type of tag to remove.
*/
template<typename Tag>
void remove() {
if(has<Tag>()) {
tags[tag_family::type<Tag>()].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<typename Tag>
bool has() const noexcept {
const auto ttype = tag_family::type<Tag>();
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.<br/>
* 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<typename Tag>
const Tag & get() const noexcept {
assert(has<Tag>());
return static_cast<Attaching<Tag> *>(tags[tag_family::type<Tag>()].get())->tag;
}
/**
* @brief Returns a reference to a tag.
*
* @warning
* Attempting to get a tag that hasn't an owner results in undefined
* behavior.<br/>
* 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<typename Tag>
Tag & get() noexcept {
return const_cast<Tag &>(const_cast<const Registry *>(this)->get<Tag>());
}
/**
* @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.<br/>
* 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<typename Tag>
entity_type attachee() const noexcept {
assert(has<Tag>());
return tags[tag_family::type<Tag>()]->entity;
}
/**
* @brief Assigns the given component to an entity.
*
@@ -372,7 +511,7 @@ public:
* 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 @@ public:
* 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<typename Component>
@@ -436,7 +575,7 @@ public:
* 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 @@ public:
* 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 @@ public:
* 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 @@ public:
* 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 @@ public:
*
* 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<typename Component, typename Compare>
@@ -589,8 +728,8 @@ public:
*
* 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<typename To, typename From>
void sort() {
@@ -608,7 +747,7 @@ public:
* 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<typename Component>
@@ -630,7 +769,7 @@ public:
* 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<typename Component>
void reset() {
@@ -673,6 +812,10 @@ public:
pool->reset();
}
}
for(auto &&tag: tags) {
tag.reset();
}
}
/**
@@ -733,6 +876,46 @@ public:
handler<Component...>();
}
/**
* @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.<br/>
* 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<typename... Component>
void discard() {
if(contains<Component...>()) {
using accumulator_type = int[];
const auto vtype = view_family::type<Component...>();
auto *set = handlers[vtype].get();
// if a set exists, pools have already been created for it
accumulator_type accumulator = { (pool<Component>().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<typename... Component>
bool contains() const noexcept {
static_assert(sizeof...(Component) > 1, "!");
const auto vtype = view_family::type<Component...>();
return vtype < handlers.size() && handlers[vtype];
}
/**
* @brief Returns a persistent view for the given components.
*
@@ -772,12 +955,14 @@ public:
*/
template<typename... Component>
PersistentView<Entity, Component...> persistent() {
return PersistentView<Entity, Component...>{handler<Component...>(), ensure<Component>()...};
// after the calls to handler, pools have already been created
return PersistentView<Entity, Component...>{handler<Component...>(), pool<Component>()...};
}
private:
std::vector<std::unique_ptr<SparseSet<Entity>>> handlers;
std::vector<std::unique_ptr<SparseSet<Entity>>> pools;
std::vector<std::unique_ptr<Attachee>> tags;
std::vector<entity_type> available;
std::vector<entity_type> entities;
};

View File

@@ -264,7 +264,7 @@ public:
// 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<Entity, Type>: public SparseSet<Entity> {
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 @@ public:
*
* @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 @@ public:
*
* @return A pointer to the array of objects.
*/
type * raw() noexcept {
object_type * raw() noexcept {
return instances.data();
}
@@ -485,7 +485,7 @@ public:
* @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 @@ public:
* @param entity A valid entity identifier.
* @return The object associated to the entity.
*/
type & get(entity_type entity) noexcept {
return const_cast<type &>(const_cast<const SparseSet *>(this)->get(entity));
object_type & get(entity_type entity) noexcept {
return const_cast<object_type &>(const_cast<const SparseSet *>(this)->get(entity));
}
/**
@@ -520,8 +520,9 @@ public:
* @return The object associated to the entity.
*/
template<typename... Args>
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>(args)... });
return instances.back();
}
@@ -538,7 +539,7 @@ public:
* @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 @@ public:
}
private:
std::vector<type> instances;
std::vector<object_type> instances;
};

View File

@@ -571,7 +571,7 @@ public:
/*! @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.

View File

@@ -292,7 +292,7 @@ public:
* Unless an immediate operation is requested, the abort is scheduled for
* the next tick. Processes won't be executed anymore in any case.<br/>
* 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.
*/

View File

@@ -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<int>());
auto entity = registry.create();
registry.attach<int>(entity, 42);
ASSERT_TRUE(registry.has<int>());
ASSERT_EQ(registry.get<int>(), 42);
ASSERT_EQ(cregistry.get<int>(), 42);
ASSERT_EQ(registry.attachee<int>(), entity);
registry.remove<int>();
ASSERT_FALSE(registry.has<int>());
registry.attach<int>(entity, 42);
registry.destroy(entity);
ASSERT_FALSE(registry.has<int>());
}
TEST(DefaultRegistry, StandardViews) {
entt::DefaultRegistry registry;
auto mview = registry.view<int, char>();
auto iview = registry.view<int>();
auto cview = registry.view<char>();
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<int, char>();
ASSERT_TRUE((registry.contains<int, char>()));
ASSERT_FALSE((registry.contains<int, double>()));
registry.prepare<int, double>();
ASSERT_TRUE((registry.contains<int, double>()));
registry.discard<int, double>();
ASSERT_FALSE((registry.contains<int, double>()));
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<int>();
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<int, char>();
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<int>(entity);
ASSERT_TRUE(registry.has<int>());
registry.reset();
ASSERT_FALSE(registry.has<int>());
}
TEST(DefaultRegistry, SortSingle) {
entt::DefaultRegistry registry;