orphans/orphan + minor changes
This commit is contained in:
54
README.md
54
README.md
@@ -353,8 +353,8 @@ describe below. For more details, please refer to the
|
||||
|
||||
## The Registry, the Entity and the Component
|
||||
|
||||
A registry is used to store and manage entities as well as to create views to
|
||||
iterate the underlying data structures.<br/>
|
||||
A registry can store and manage entities, as well as create views to iterate the
|
||||
underlying data structures.<br/>
|
||||
`Registry` is a class template that lets the users decide what's the preferred
|
||||
type to represent an entity. Because `std::uint32_t` is large enough for almost
|
||||
all the cases, there exists also an alias named `DefaultRegistry` for
|
||||
@@ -367,7 +367,7 @@ information about the entity itself and its version.
|
||||
A registry can be used both to construct and to destroy entities:
|
||||
|
||||
```cpp
|
||||
// constructs a naked entity with no components ad returns its identifier
|
||||
// constructs a naked entity with no components and returns its identifier
|
||||
auto entity = registry.create();
|
||||
|
||||
// constructs an entity and assigns it default-initialized components
|
||||
@@ -400,8 +400,8 @@ calls to member functions of the registry. As for the entities, the registry
|
||||
offers also a set of functionalities users can use to work with the components.
|
||||
|
||||
The `assign` member function template creates, initializes and assigns to an
|
||||
entity the given component. It accepts a variable number of arguments that are
|
||||
used to construct the component itself if present:
|
||||
entity the given component. It accepts a variable number of arguments to
|
||||
construct the component itself if present:
|
||||
|
||||
```cpp
|
||||
registry.assign<Position>(entity, 0., 0.);
|
||||
@@ -468,7 +468,7 @@ registry.remove<Position>(entity);
|
||||
```
|
||||
|
||||
Otherwise consider to use the `reset` member function. It behaves similarly to
|
||||
`remove` but with a strictly defined behaviour (and a performance penalty is the
|
||||
`remove` but with a strictly defined behavior (and a performance penalty is the
|
||||
price to pay for this). In particular it removes the component if and only if it
|
||||
exists, otherwise it returns safely to the caller:
|
||||
|
||||
@@ -495,7 +495,6 @@ in use and their components are destroyed:
|
||||
Finally, references to components can be retrieved simply by doing this:
|
||||
|
||||
```cpp
|
||||
entt::DefaultRegistry registry;
|
||||
const auto &cregistry = registry;
|
||||
|
||||
// const and non-const reference
|
||||
@@ -548,12 +547,12 @@ bool b = registry.has<PlayingCharacter>();
|
||||
References to tags can be retrieved simply by doing this:
|
||||
|
||||
```cpp
|
||||
const auto &cregistry = registry;
|
||||
|
||||
// either a non-const reference ...
|
||||
entt::DefaultRegistry registry;
|
||||
PlayingCharacter &player = registry.get<PlayingCharacter>();
|
||||
|
||||
// ... or a const one
|
||||
const auto &cregistry = registry;
|
||||
const Camera &camera = cregistry.get<Camera>();
|
||||
```
|
||||
|
||||
@@ -605,15 +604,16 @@ simple.
|
||||
|
||||
`EnTT` comes with an example (actually a test) that shows how to integrate
|
||||
compile-time and runtime components in a stack based JavaScript environment. It
|
||||
uses [`duktape`](https://github.com/svaarala/duktape) under the hood, mainly
|
||||
uses [`Duktape`](https://github.com/svaarala/duktape) under the hood, mainly
|
||||
because I wanted to learn how it works at the time I was writing the code.
|
||||
|
||||
It's not production-ready and overall performance can be highly improved.
|
||||
However, I sacrificed optimizations in favor of a more readable piece of
|
||||
code. I hope I succeeded.<br/>
|
||||
The code is not production-ready and overall performance can be highly improved.
|
||||
However, I sacrificed optimizations in favor of a more readable piece of code. I
|
||||
hope I succeeded.<br/>
|
||||
Note also that this isn't neither the only nor (probably) the best way to do it.
|
||||
In fact, the right way depends on the scripting language and the problem one is
|
||||
facing in general.
|
||||
facing in general.<br/>
|
||||
That being said, feel free to use it at your own risk.
|
||||
|
||||
The basic idea is that of creating a compile-time component aimed to map all the
|
||||
runtime components assigned to an entity.<br/>
|
||||
@@ -895,11 +895,33 @@ registry.each([](auto entity) {
|
||||
});
|
||||
```
|
||||
|
||||
It returns to the caller all the entities that are still in use by means of the
|
||||
given function.<br/>
|
||||
As a rule of thumb, consider using a view if the goal is to iterate entities
|
||||
that have a determinate set of components. A view is usually faster than
|
||||
that have a determinate set of components. A view is usually much faster than
|
||||
combining this function with a bunch of custom tests.<br/>
|
||||
In all the other cases, this is the way to go.
|
||||
|
||||
There exists also another member function to use to retrieve orphans. An orphan
|
||||
is an entity that is still in use and has neither assigned components nor
|
||||
tags.<br/>
|
||||
The signature of the function is the same of `each`:
|
||||
|
||||
```cpp
|
||||
registry.orphans([](auto entity) {
|
||||
// ...
|
||||
});
|
||||
```
|
||||
|
||||
To test the _orphanity_ of a single entity, use the member function `orphan`
|
||||
instead. It accepts a valid entity identifer as an argument and returns true in
|
||||
case the entity is an orphan, false otherwise.
|
||||
|
||||
In general, all these functions can result in poor performance.<br/>
|
||||
`each` is fairly slow because of some checks it performs on each and every
|
||||
entity. For similar reasons, `orphans` can be even slower. Both functions should
|
||||
not be used frequently to avoid the risk of a performance hit.
|
||||
|
||||
## Side notes
|
||||
|
||||
* Entity identifiers are numbers and nothing more. They are not classes and they
|
||||
@@ -925,7 +947,7 @@ In all the other cases, this is the way to go.
|
||||
other entities, destroying them or removing their components isn't
|
||||
allowed and it can result in undefined behavior.
|
||||
|
||||
Iterators are invalidated and the behaviour is undefined if an entity is
|
||||
Iterators are invalidated and the behavior is undefined if an entity is
|
||||
modified or destroyed and it's not the one currently returned by the
|
||||
view.<br/>
|
||||
To work around it, possible approaches are:
|
||||
|
||||
@@ -138,7 +138,6 @@ class Registry {
|
||||
|
||||
if(!handlers[vtype]) {
|
||||
using accumulator_type = int[];
|
||||
|
||||
auto set = std::make_unique<SparseSet<Entity>>();
|
||||
|
||||
for(auto entity: view<Component...>()) {
|
||||
@@ -294,7 +293,7 @@ public:
|
||||
bool valid(entity_type entity) const noexcept {
|
||||
using promotion_type = std::conditional_t<sizeof(size_type) >= sizeof(entity_type), size_type, entity_type>;
|
||||
// explicit promotion to avoid warnings with std::uint16_t
|
||||
const entity_type entt = promotion_type{entity} & traits_type::entity_mask;
|
||||
const auto entt = promotion_type{entity} & traits_type::entity_mask;
|
||||
return (entt < entities.size() && entities[entt] == entity);
|
||||
}
|
||||
|
||||
@@ -487,7 +486,7 @@ public:
|
||||
tags.resize(ttype + 1);
|
||||
}
|
||||
|
||||
tags[ttype].reset(new Attaching<Tag>{entity, { std::forward<Args>(args)... }});
|
||||
tags[ttype].reset(new Attaching<Tag>{entity, Tag{ std::forward<Args>(args)... }});
|
||||
|
||||
return static_cast<Attaching<Tag> *>(tags[ttype].get())->tag;
|
||||
}
|
||||
@@ -906,18 +905,21 @@ public:
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Iterate entities and applies them the given function object.
|
||||
* @brief Iterates all the entities that are still in use.
|
||||
*
|
||||
* The function object is invoked for each entity still in use.<br/>
|
||||
* The function object is invoked for each entity that is still in use.<br/>
|
||||
* The signature of the function should be equivalent to the following:
|
||||
*
|
||||
* @code{.cpp}
|
||||
* void(entity_type);
|
||||
* @endcode
|
||||
*
|
||||
* This function is fairly slow and should not be used frequently.<br/>
|
||||
* Consider using a view if the goal is to iterate entities that have a
|
||||
* determinate set of components. A view is usually faster than combining
|
||||
* this function with a bunch of custom tests.
|
||||
* this function with a bunch of custom tests.<br/>
|
||||
* On the other side, this function can be used to iterate all the entities
|
||||
* that are in use, regardless of their components.
|
||||
*
|
||||
* @tparam Func Type of the function object to invoke.
|
||||
* @param func A valid function object.
|
||||
@@ -940,6 +942,57 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks if an entity is an orphan.
|
||||
*
|
||||
* An orphan is an entity that has neither assigned components nor
|
||||
* tags.
|
||||
*
|
||||
* @param entity A valid entity identifier.
|
||||
* @return True if the entity is an orphan, false otherwise.
|
||||
*/
|
||||
bool orphan(entity_type entity) const {
|
||||
assert(valid(entity));
|
||||
bool orphan = true;
|
||||
|
||||
for(std::size_t i = 0; i < pools.size() && orphan; ++i) {
|
||||
const auto &pool = pools[i];
|
||||
orphan = !(pool && pool->has(entity));
|
||||
}
|
||||
|
||||
for(std::size_t i = 0; i < tags.size() && orphan; ++i) {
|
||||
const auto &tag = tags[i];
|
||||
orphan = !(tag && (tag->entity == entity));
|
||||
}
|
||||
|
||||
return orphan;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Iterates orphans and applies them the given function object.
|
||||
*
|
||||
* The function object is invoked for each entity that is still in use and
|
||||
* has neither assigned components nor tags.<br/>
|
||||
* The signature of the function should be equivalent to the following:
|
||||
*
|
||||
* @code{.cpp}
|
||||
* void(entity_type);
|
||||
* @endcode
|
||||
*
|
||||
* This function can be very slow and should not be used frequently.
|
||||
*
|
||||
* @tparam Func Type of the function object to invoke.
|
||||
* @param func A valid function object.
|
||||
*/
|
||||
template<typename Func>
|
||||
void orphans(Func func) const {
|
||||
each([func = std::move(func), this](auto entity) {
|
||||
if(orphan(entity)) {
|
||||
func(entity);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a standard view for the given components.
|
||||
*
|
||||
|
||||
@@ -18,11 +18,11 @@ TEST(DefaultRegistry, Functionalities) {
|
||||
ASSERT_TRUE(registry.empty<int>());
|
||||
ASSERT_TRUE(registry.empty<char>());
|
||||
|
||||
auto e1 = registry.create();
|
||||
auto e2 = registry.create<int, char>();
|
||||
auto e0 = registry.create();
|
||||
auto e1 = registry.create<int, char>();
|
||||
|
||||
ASSERT_TRUE(registry.has<>(e0));
|
||||
ASSERT_TRUE(registry.has<>(e1));
|
||||
ASSERT_TRUE(registry.has<>(e2));
|
||||
|
||||
ASSERT_EQ(registry.capacity(), entt::DefaultRegistry::size_type{2});
|
||||
ASSERT_EQ(registry.size<int>(), entt::DefaultRegistry::size_type{1});
|
||||
@@ -30,67 +30,67 @@ TEST(DefaultRegistry, Functionalities) {
|
||||
ASSERT_FALSE(registry.empty<int>());
|
||||
ASSERT_FALSE(registry.empty<char>());
|
||||
|
||||
ASSERT_NE(e1, e2);
|
||||
|
||||
ASSERT_FALSE(registry.has<int>(e1));
|
||||
ASSERT_TRUE(registry.has<int>(e2));
|
||||
ASSERT_FALSE(registry.has<char>(e1));
|
||||
ASSERT_TRUE(registry.has<char>(e2));
|
||||
ASSERT_FALSE((registry.has<int, char>(e1)));
|
||||
ASSERT_TRUE((registry.has<int, char>(e2)));
|
||||
|
||||
ASSERT_EQ(registry.assign<int>(e1, 42), 42);
|
||||
ASSERT_EQ(registry.assign<char>(e1, 'c'), 'c');
|
||||
ASSERT_NO_THROW(registry.remove<int>(e2));
|
||||
ASSERT_NO_THROW(registry.remove<char>(e2));
|
||||
ASSERT_NE(e0, e1);
|
||||
|
||||
ASSERT_FALSE(registry.has<int>(e0));
|
||||
ASSERT_TRUE(registry.has<int>(e1));
|
||||
ASSERT_FALSE(registry.has<int>(e2));
|
||||
ASSERT_FALSE(registry.has<char>(e0));
|
||||
ASSERT_TRUE(registry.has<char>(e1));
|
||||
ASSERT_FALSE(registry.has<char>(e2));
|
||||
ASSERT_FALSE((registry.has<int, char>(e0)));
|
||||
ASSERT_TRUE((registry.has<int, char>(e1)));
|
||||
ASSERT_FALSE((registry.has<int, char>(e2)));
|
||||
|
||||
auto e3 = registry.create();
|
||||
ASSERT_EQ(registry.assign<int>(e0, 42), 42);
|
||||
ASSERT_EQ(registry.assign<char>(e0, 'c'), 'c');
|
||||
ASSERT_NO_THROW(registry.remove<int>(e1));
|
||||
ASSERT_NO_THROW(registry.remove<char>(e1));
|
||||
|
||||
registry.accomodate<int>(e3, registry.get<int>(e1));
|
||||
registry.accomodate<char>(e3, registry.get<char>(e1));
|
||||
ASSERT_TRUE(registry.has<int>(e0));
|
||||
ASSERT_FALSE(registry.has<int>(e1));
|
||||
ASSERT_TRUE(registry.has<char>(e0));
|
||||
ASSERT_FALSE(registry.has<char>(e1));
|
||||
ASSERT_TRUE((registry.has<int, char>(e0)));
|
||||
ASSERT_FALSE((registry.has<int, char>(e1)));
|
||||
|
||||
ASSERT_TRUE(registry.has<int>(e3));
|
||||
ASSERT_TRUE(registry.has<char>(e3));
|
||||
ASSERT_EQ(registry.get<int>(e1), 42);
|
||||
ASSERT_EQ(registry.get<char>(e1), 'c');
|
||||
auto e2 = registry.create();
|
||||
|
||||
ASSERT_EQ(std::get<0>(registry.get<int, char>(e1)), 42);
|
||||
ASSERT_EQ(std::get<1>(static_cast<const entt::DefaultRegistry &>(registry).get<int, char>(e1)), 'c');
|
||||
registry.accomodate<int>(e2, registry.get<int>(e0));
|
||||
registry.accomodate<char>(e2, registry.get<char>(e0));
|
||||
|
||||
ASSERT_EQ(registry.get<int>(e1), registry.get<int>(e3));
|
||||
ASSERT_EQ(registry.get<char>(e1), registry.get<char>(e3));
|
||||
ASSERT_NE(®istry.get<int>(e1), ®istry.get<int>(e3));
|
||||
ASSERT_NE(®istry.get<char>(e1), ®istry.get<char>(e3));
|
||||
ASSERT_TRUE(registry.has<int>(e2));
|
||||
ASSERT_TRUE(registry.has<char>(e2));
|
||||
ASSERT_EQ(registry.get<int>(e0), 42);
|
||||
ASSERT_EQ(registry.get<char>(e0), 'c');
|
||||
|
||||
ASSERT_NO_THROW(registry.replace<int>(e1, 0));
|
||||
ASSERT_EQ(registry.get<int>(e1), 0);
|
||||
ASSERT_EQ(std::get<0>(registry.get<int, char>(e0)), 42);
|
||||
ASSERT_EQ(std::get<1>(static_cast<const entt::DefaultRegistry &>(registry).get<int, char>(e0)), 'c');
|
||||
|
||||
ASSERT_EQ(registry.get<int>(e0), registry.get<int>(e2));
|
||||
ASSERT_EQ(registry.get<char>(e0), registry.get<char>(e2));
|
||||
ASSERT_NE(®istry.get<int>(e0), ®istry.get<int>(e2));
|
||||
ASSERT_NE(®istry.get<char>(e0), ®istry.get<char>(e2));
|
||||
|
||||
ASSERT_NO_THROW(registry.replace<int>(e0, 0));
|
||||
ASSERT_EQ(registry.get<int>(e0), 0);
|
||||
|
||||
ASSERT_NO_THROW(registry.accomodate<int>(e0, 1));
|
||||
ASSERT_NO_THROW(registry.accomodate<int>(e1, 1));
|
||||
ASSERT_NO_THROW(registry.accomodate<int>(e2, 1));
|
||||
ASSERT_EQ(static_cast<const entt::DefaultRegistry &>(registry).get<int>(e0), 1);
|
||||
ASSERT_EQ(static_cast<const entt::DefaultRegistry &>(registry).get<int>(e1), 1);
|
||||
ASSERT_EQ(static_cast<const entt::DefaultRegistry &>(registry).get<int>(e2), 1);
|
||||
|
||||
ASSERT_EQ(registry.size(), entt::DefaultRegistry::size_type{3});
|
||||
ASSERT_FALSE(registry.empty());
|
||||
|
||||
ASSERT_EQ(registry.version(e3), entt::DefaultRegistry::version_type{0});
|
||||
ASSERT_EQ(registry.current(e3), entt::DefaultRegistry::version_type{0});
|
||||
ASSERT_EQ(registry.version(e2), entt::DefaultRegistry::version_type{0});
|
||||
ASSERT_EQ(registry.current(e2), entt::DefaultRegistry::version_type{0});
|
||||
ASSERT_EQ(registry.capacity(), entt::DefaultRegistry::size_type{3});
|
||||
ASSERT_NO_THROW(registry.destroy(e3));
|
||||
ASSERT_NO_THROW(registry.destroy(e2));
|
||||
ASSERT_EQ(registry.capacity(), entt::DefaultRegistry::size_type{3});
|
||||
ASSERT_EQ(registry.version(e3), entt::DefaultRegistry::version_type{0});
|
||||
ASSERT_EQ(registry.current(e3), entt::DefaultRegistry::version_type{1});
|
||||
ASSERT_EQ(registry.version(e2), entt::DefaultRegistry::version_type{0});
|
||||
ASSERT_EQ(registry.current(e2), entt::DefaultRegistry::version_type{1});
|
||||
|
||||
ASSERT_TRUE(registry.valid(e0));
|
||||
ASSERT_TRUE(registry.valid(e1));
|
||||
ASSERT_TRUE(registry.valid(e2));
|
||||
ASSERT_FALSE(registry.valid(e3));
|
||||
ASSERT_FALSE(registry.valid(e2));
|
||||
|
||||
ASSERT_EQ(registry.size(), entt::DefaultRegistry::size_type{2});
|
||||
ASSERT_FALSE(registry.empty());
|
||||
@@ -121,11 +121,11 @@ TEST(DefaultRegistry, Functionalities) {
|
||||
ASSERT_TRUE(registry.empty<int>());
|
||||
ASSERT_TRUE(registry.empty<char>());
|
||||
|
||||
e1 = registry.create<int>();
|
||||
e2 = registry.create();
|
||||
e0 = registry.create<int>();
|
||||
e1 = registry.create();
|
||||
|
||||
ASSERT_NO_THROW(registry.reset<int>(e0));
|
||||
ASSERT_NO_THROW(registry.reset<int>(e1));
|
||||
ASSERT_NO_THROW(registry.reset<int>(e2));
|
||||
|
||||
ASSERT_EQ(registry.size<int>(), entt::DefaultRegistry::size_type{0});
|
||||
ASSERT_EQ(registry.size<char>(), entt::DefaultRegistry::size_type{0});
|
||||
@@ -200,6 +200,28 @@ TEST(DefaultRegistry, Each) {
|
||||
registry.each([&](auto) { FAIL(); });
|
||||
}
|
||||
|
||||
TEST(DefaultRegistry, Orphans) {
|
||||
entt::DefaultRegistry registry;
|
||||
entt::DefaultRegistry::size_type tot{};
|
||||
|
||||
registry.create<int>();
|
||||
registry.create();
|
||||
registry.create<int>();
|
||||
registry.create();
|
||||
|
||||
registry.orphans([&](auto) { ++tot; });
|
||||
ASSERT_EQ(tot, 2u);
|
||||
tot = 0u;
|
||||
|
||||
registry.each([&](auto entity) { registry.reset<int>(entity); });
|
||||
registry.orphans([&](auto) { ++tot; });
|
||||
ASSERT_EQ(tot, 4u);
|
||||
registry.reset();
|
||||
tot = 0u;
|
||||
|
||||
registry.orphans([&](auto) { ++tot; });
|
||||
ASSERT_EQ(tot, 0u);
|
||||
}
|
||||
|
||||
TEST(DefaultRegistry, Types) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
Reference in New Issue
Block a user